first commit for chrg

This commit is contained in:
wmano
2025-08-16 22:58:22 +08:00
commit 52a3ed5862
2306 changed files with 1021208 additions and 0 deletions
@@ -0,0 +1,20 @@
menuconfig RT_USING_WDT
bool "Using Watch Dog device drivers"
default n
config RT_WDT_DW
bool "Synopsys DesignWare watchdog"
depends on RT_USING_DM
depends on RT_USING_WDT
depends on RT_USING_RESET
default n
config RT_WDT_I6300ESB
bool "Intel 6300ESB Timer/Watchdog"
depends on RT_USING_DM
depends on RT_USING_WDT
depends on RT_USING_PCI
if RT_USING_DM && RT_USING_WDT
osource "$(SOC_DM_WDT_DIR)/Kconfig"
endif
@@ -0,0 +1,22 @@
from building import *
group = []
if not GetDepend(['RT_USING_WDT']):
Return('group')
cwd = GetCurrentDir()
list = os.listdir(cwd)
CPPPATH = [cwd + '/../include']
src = ['dev_watchdog.c']
if GetDepend(['RT_WDT_DW']):
src += ['watchdog-dw.c']
if GetDepend(['RT_WDT_I6300ESB']):
src += ['watchdog-i6300esb.c']
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
@@ -0,0 +1,110 @@
/*
* COPYRIGHT (C) 2011-2023, Real-Thread Information Technology Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2012-09-12 heyuanjie87 first version.
* 2014-03-04 Bernard code cleanup
*/
#include "drivers/dev_watchdog.h"
/* RT-Thread Device Interface */
/*
* This function initializes watchdog
*/
static rt_err_t rt_watchdog_init(struct rt_device *dev)
{
rt_watchdog_t *wtd;
RT_ASSERT(dev != RT_NULL);
wtd = (rt_watchdog_t *)dev;
if (wtd->ops->init)
{
return (wtd->ops->init(wtd));
}
return (-RT_ENOSYS);
}
static rt_err_t rt_watchdog_open(struct rt_device *dev, rt_uint16_t oflag)
{
return (RT_EOK);
}
static rt_err_t rt_watchdog_close(struct rt_device *dev)
{
rt_watchdog_t *wtd;
RT_ASSERT(dev != RT_NULL);
wtd = (rt_watchdog_t *)dev;
if (wtd->ops->control(wtd, RT_DEVICE_CTRL_WDT_STOP, RT_NULL) != RT_EOK)
{
rt_kprintf(" This watchdog can not be stoped\n");
return (-RT_ERROR);
}
return (RT_EOK);
}
static rt_err_t rt_watchdog_control(struct rt_device *dev,
int cmd,
void *args)
{
rt_watchdog_t *wtd;
RT_ASSERT(dev != RT_NULL);
wtd = (rt_watchdog_t *)dev;
return (wtd->ops->control(wtd, cmd, args));
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops wdt_ops =
{
rt_watchdog_init,
rt_watchdog_open,
rt_watchdog_close,
RT_NULL,
RT_NULL,
rt_watchdog_control,
};
#endif
/**
* This function register a watchdog device
*/
rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd,
const char *name,
rt_uint32_t flag,
void *data)
{
struct rt_device *device;
RT_ASSERT(wtd != RT_NULL);
device = &(wtd->parent);
device->type = RT_Device_Class_WDT;
device->rx_indicate = RT_NULL;
device->tx_complete = RT_NULL;
#ifdef RT_USING_DEVICE_OPS
device->ops = &wdt_ops;
#else
device->init = rt_watchdog_init;
device->open = rt_watchdog_open;
device->close = rt_watchdog_close;
device->read = RT_NULL;
device->write = RT_NULL;
device->control = rt_watchdog_control;
#endif
device->user_data = data;
/* register a character device */
return rt_device_register(device, name, flag);
}
@@ -0,0 +1,621 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define WDOG_CONTROL_REG_OFFSET 0x00
#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
#define WDOG_CONTROL_REG_RESP_MODE_MASK 0x02
#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04
#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4
#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
#define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10
#define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14
#define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4
#define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8
#define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec
#define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0
#define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
#define WDOG_COMP_PARAMS_1_USE_FIX_TOP (1 << 6)
#define WDOG_COMP_VERSION_REG_OFFSET 0xf8
#define WDOG_COMP_TYPE_REG_OFFSET 0xfc
/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
#define DW_WDT_NUM_TOPS 16
#define DW_WDT_FIX_TOP(idx) (1U << (16 + idx))
#define DW_WDT_DEFAULT_SECONDS 30
#define MSEC_PER_SEC 1000L
enum dw_wdt_rmod
{
DW_WDT_RMOD_RESET = 1,
DW_WDT_RMOD_IRQ
};
struct dw_wdt_timeout
{
rt_uint32_t top_val;
rt_uint32_t sec;
rt_uint32_t msec;
};
struct dw_wdt
{
rt_watchdog_t parent;
void *base;
int irq;
struct rt_clk *clk;
struct rt_reset_control *rstc;
rt_ubase_t rate;
enum dw_wdt_rmod rmod;
struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
/* Save/Restore */
rt_uint32_t user;
rt_uint32_t timeout;
rt_uint32_t pretimeout;
rt_uint32_t max_hw_heartbeat_ms;
struct rt_device_notify pretimeout_notify;
};
#define raw_to_dw_wdt(raw) rt_container_of(raw, struct dw_wdt, parent)
static const rt_uint32_t dw_wdt_fix_tops[DW_WDT_NUM_TOPS] =
{
DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2), DW_WDT_FIX_TOP(3),
DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5), DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7),
DW_WDT_FIX_TOP(8), DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11),
DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14), DW_WDT_FIX_TOP(15)
};
rt_inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
{
return HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) & WDOG_CONTROL_REG_WDT_EN_MASK;
}
static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod)
{
rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
if (rmod == DW_WDT_RMOD_IRQ)
{
val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
}
else
{
val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
}
HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val;
dw_wdt->rmod = rmod;
}
static rt_uint32_t dw_wdt_find_best_top(struct dw_wdt *dw_wdt, rt_uint32_t timeout, rt_uint32_t *top_val)
{
int idx;
/*
* Find a TOP with timeout greater or equal to the requested number. Note
* we'll select a TOP with maximum timeout if the requested timeout couldn't
* be reached.
*/
for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx)
{
if (dw_wdt->timeouts[idx].sec >= timeout)
{
break;
}
}
if (idx == DW_WDT_NUM_TOPS)
{
--idx;
}
*top_val = dw_wdt->timeouts[idx].top_val;
return dw_wdt->timeouts[idx].sec;
}
static rt_uint32_t dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt)
{
rt_uint64_t msec;
struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1];
msec = (rt_uint64_t)timeout->sec * MSEC_PER_SEC + timeout->msec;
return msec < RT_UINT32_MAX ? msec : RT_UINT32_MAX;
}
static rt_uint32_t dw_wdt_get_timeout(struct dw_wdt *dw_wdt)
{
int idx;
int top_val = HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx)
{
if (dw_wdt->timeouts[idx].top_val == top_val)
{
break;
}
}
/*
* In IRQ mode due to the two stages counter, the actual timeout is twice
* greater than the TOP setting.
*/
return dw_wdt->timeouts[idx].sec * dw_wdt->rmod;
}
static int dw_wdt_keep_alive(struct dw_wdt *dw_wdt)
{
HWREG32(dw_wdt->base + WDOG_COUNTER_RESTART_REG_OFFSET) = WDOG_COUNTER_RESTART_KICK_VALUE;
return 0;
}
static void dw_wdt_set_timeout(struct dw_wdt *dw_wdt, rt_uint32_t top_s)
{
rt_uint32_t top_val;
rt_uint32_t timeout;
/*
* Note IRQ mode being enabled means having a non-zero pre-timeout setup.
* In this case we try to find a TOP as close to the half of the requested
* timeout as possible since DW Watchdog IRQ mode is designed in two stages
* way - first timeout rises the pre-timeout interrupt, second timeout
* performs the system reset. So basically the effective watchdog-caused
* reset happens after two watchdog TOPs elapsed.
*/
timeout = dw_wdt_find_best_top(dw_wdt, RT_DIV_ROUND_UP(top_s, dw_wdt->rmod), &top_val);
if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
{
dw_wdt->pretimeout = timeout;
}
else
{
dw_wdt->pretimeout = 0;
}
/*
* Set the new value in the watchdog. Some versions of dw_wdt have have
* TOPINIT in the TIMEOUT_RANGE register (as per CP_WDT_DUAL_TOP in
* WDT_COMP_PARAMS_1). On those we effectively get a pat of the watchdog
* right here.
*/
HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) = top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT;
dw_wdt_keep_alive(dw_wdt);
/*
* In case users set bigger timeout value than HW can support,
* kernel(watchdog_dev.c) helps to feed watchdog before wdd->max_hw_heartbeat_ms
*/
if (top_s * 1000 <= dw_wdt->max_hw_heartbeat_ms)
{
dw_wdt->timeout = timeout * dw_wdt->rmod;
}
else
{
dw_wdt->timeout = top_s;
}
}
static void dw_wdt_set_pretimeout(struct dw_wdt *dw_wdt, rt_uint32_t req)
{
/*
* We ignore actual value of the timeout passed from user-space using it as
* a flag whether the pretimeout functionality is intended to be activated.
*/
dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET);
dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout);
}
static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
{
rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
/* Disable/enable interrupt mode depending on the RMOD flag. */
if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
{
val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
}
else
{
val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
}
/* Enable watchdog. */
HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val | WDOG_CONTROL_REG_WDT_EN_MASK;
}
static int dw_wdt_start(struct dw_wdt *dw_wdt)
{
rt_clk_enable(dw_wdt->clk);
dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout);
dw_wdt_keep_alive(dw_wdt);
dw_wdt_arm_system_reset(dw_wdt);
return 0;
}
static int dw_wdt_stop(struct dw_wdt *dw_wdt)
{
/*
* The DesignWare watchdog cannot be stopped once it has been started so we
* do not implement a stop function. The watchdog core will continue to send
* heartbeat requests after the watchdog device has been closed.
*/
rt_clk_disable(dw_wdt->clk);
rt_reset_control_assert(dw_wdt->rstc);
rt_reset_control_deassert(dw_wdt->rstc);
return 0;
}
static rt_uint32_t dw_wdt_get_timeleft(struct dw_wdt *dw_wdt)
{
rt_uint32_t val, sec;
val = HWREG32(dw_wdt->base + WDOG_CURRENT_COUNT_REG_OFFSET);
sec = val / dw_wdt->rate;
if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
{
val = HWREG32(dw_wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET);
if (!val)
{
sec += dw_wdt->pretimeout;
}
}
return sec;
}
static rt_err_t dw_wdt_init_timeouts(struct dw_wdt *dw_wdt)
{
int val, tidx;
rt_uint64_t msec;
struct dw_wdt_timeout tout, *dst;
const rt_uint32_t *tops = dw_wdt_fix_tops;
/*
* Convert the specified TOPs into an array of watchdog timeouts. We walk
* over the passed TOPs array and calculate corresponding timeouts in
* seconds and milliseconds. The milliseconds granularity is needed to
* distinguish the TOPs with very close timeouts and to set the watchdog max
* heartbeat setting further.
*/
for (val = 0; val < DW_WDT_NUM_TOPS; ++val)
{
tout.top_val = val;
tout.sec = tops[val] / dw_wdt->rate;
msec = (rt_uint64_t)tops[val] * MSEC_PER_SEC;
rt_do_div(msec, dw_wdt->rate);
tout.msec = msec - ((rt_uint64_t)tout.sec * MSEC_PER_SEC);
/*
* Find a suitable place for the current TOP in the timeouts array so
* that the list is remained in the ascending order.
*/
for (tidx = 0; tidx < val; ++tidx)
{
dst = &dw_wdt->timeouts[tidx];
if (tout.sec > dst->sec || (tout.sec == dst->sec && tout.msec >= dst->msec))
{
continue;
}
else
{
struct dw_wdt_timeout tmp = *dst;
*dst = tout;
tout = tmp;
}
}
dw_wdt->timeouts[val] = tout;
}
if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec)
{
return -RT_ENOSYS;
}
return RT_EOK;
}
static rt_err_t dw_wdt_init(rt_watchdog_t *wdt)
{
rt_err_t status = RT_EOK;
struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
/* Enable normal reset without pre-timeout by default. */
dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
if (dw_wdt_init_timeouts(dw_wdt))
{
return -RT_ERROR;
}
dw_wdt->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);
/*
* If the watchdog is already running, use its already configured timeout.
* Otherwise use the default or the value provided through devicetree.
*/
if (dw_wdt_is_enabled(dw_wdt))
{
dw_wdt->timeout = dw_wdt_get_timeout(dw_wdt);
}
else
{
dw_wdt->timeout = DW_WDT_DEFAULT_SECONDS;
}
return status;
}
static rt_err_t dw_wdt_control(rt_watchdog_t *wdt, int cmd, void *args)
{
rt_err_t status = RT_EOK;
struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
switch (cmd)
{
case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
*(rt_uint32_t *)args = dw_wdt_get_timeout(dw_wdt);
break;
case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
dw_wdt_set_timeout(dw_wdt, *(rt_uint32_t *)args);
break;
case RT_DEVICE_CTRL_WDT_GET_TIMELEFT:
*(rt_uint32_t *)args = dw_wdt_get_timeleft(dw_wdt);
break;
case RT_DEVICE_CTRL_WDT_KEEPALIVE:
dw_wdt_set_pretimeout(dw_wdt, dw_wdt->pretimeout);
dw_wdt_keep_alive(dw_wdt);
break;
case RT_DEVICE_CTRL_WDT_START:
dw_wdt_start(dw_wdt);
dw_wdt->user++;
break;
case RT_DEVICE_CTRL_WDT_STOP:
dw_wdt_stop(dw_wdt);
dw_wdt->user--;
break;
case RT_DEVICE_CTRL_NOTIFY_SET:
rt_hw_interrupt_mask(dw_wdt->irq);
if (args)
{
rt_memcpy(&dw_wdt->pretimeout_notify, args, sizeof(dw_wdt->pretimeout_notify));
}
else
{
rt_memset(&dw_wdt->pretimeout_notify, 0, sizeof(dw_wdt->pretimeout_notify));
}
rt_hw_interrupt_umask(dw_wdt->irq);
break;
default:
status = -RT_EINVAL;
}
return status;
}
static const struct rt_watchdog_ops dw_wdt_ops =
{
.init = dw_wdt_init,
.control = dw_wdt_control,
};
#ifdef RT_USING_PM
static rt_err_t dw_wdt_pm_suspend(const struct rt_device *device, rt_uint8_t mode)
{
rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent);
struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
dw_wdt->timeout = dw_wdt_get_timeleft(dw_wdt) / dw_wdt->rate;
dw_wdt_stop(dw_wdt);
return RT_EOK;
}
static void dw_wdt_pm_resume(const struct rt_device *device, rt_uint8_t mode)
{
rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent);
struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
if (!dw_wdt->user)
{
return;
}
if (!dw_wdt_init(wdt))
{
dw_wdt_start(dw_wdt);
}
}
static const struct rt_device_pm_ops dw_wdt_pm_ops =
{
.suspend = dw_wdt_pm_suspend,
.resume = dw_wdt_pm_resume,
};
#endif /* RT_USING_PM */
static void dw_wdt_isr(int irqno, void *param)
{
struct dw_wdt *wdt = (struct dw_wdt *)param;
if (!HWREG32(wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET))
{
return;
}
/* Clear the IRQ status (EOI) */
(void)HWREG32(wdt->base + WDOG_INTERRUPT_CLEAR_REG_OFFSET);
if (wdt->pretimeout_notify.notify)
{
wdt->pretimeout_notify.notify(wdt->pretimeout_notify.dev);
}
}
static void dw_wdt_free(struct dw_wdt *dw_wdt)
{
if (dw_wdt->base)
{
rt_iounmap(dw_wdt->base);
}
if (!rt_is_err_or_null(dw_wdt->clk))
{
rt_clk_disable(dw_wdt->clk);
}
if (!rt_is_err_or_null(dw_wdt->rstc))
{
rt_reset_control_assert(dw_wdt->rstc);
rt_reset_control_put(dw_wdt->rstc);
}
rt_free(dw_wdt);
}
static rt_err_t dw_wdt_probe(struct rt_platform_device *pdev)
{
rt_err_t err = RT_EOK;
const char *dev_name;
struct rt_device *dev = &pdev->parent;
struct dw_wdt *dw_wdt = rt_calloc(1, sizeof(*dw_wdt));
if (!dw_wdt)
{
return -RT_ENOMEM;
}
dw_wdt->base = rt_dm_dev_iomap(dev, 0);
if (!dw_wdt->base)
{
err = -RT_EIO;
goto _free_res;
}
dw_wdt->irq = rt_dm_dev_get_irq(dev, 0);
if (dw_wdt->irq < 0)
{
err = dw_wdt->irq;
goto _free_res;
}
dw_wdt->clk = rt_clk_get_by_name(dev, "pclk");
if (rt_is_err(dw_wdt->clk))
{
dw_wdt->clk = rt_clk_get_by_index(dev, 0);
if (rt_is_err(dw_wdt->clk))
{
err = rt_ptr_err(dw_wdt->clk);
goto _free_res;
}
}
dw_wdt->rstc = rt_reset_control_get_by_index(dev, 0);
if (rt_is_err(dw_wdt->rstc))
{
err = rt_ptr_err(dw_wdt->rstc);
goto _free_res;
}
rt_reset_control_deassert(dw_wdt->rstc);
dev->user_data = dw_wdt;
dw_wdt->rate = rt_clk_get_rate(dw_wdt->clk);
dw_wdt->parent.ops = &dw_wdt_ops;
rt_dm_dev_set_name_auto(&dw_wdt->parent.parent, "wdt");
dev_name = rt_dm_dev_get_name(&dw_wdt->parent.parent);
rt_hw_interrupt_install(dw_wdt->irq, dw_wdt_isr, dw_wdt, dev_name);
rt_hw_interrupt_umask(dw_wdt->irq);
#ifdef RT_USING_PM
rt_pm_device_register(&dw_wdt->parent.parent, &dw_wdt_pm_ops);
#endif
rt_hw_watchdog_register(&dw_wdt->parent, dev_name, 0, dw_wdt);
return RT_EOK;
_free_res:
dw_wdt_free(dw_wdt);
return err;
}
static rt_err_t dw_wdt_remove(struct rt_platform_device *pdev)
{
struct dw_wdt *dw_wdt = pdev->parent.user_data;
rt_hw_interrupt_mask(dw_wdt->irq);
rt_pic_detach_irq(dw_wdt->irq, dw_wdt);
#ifdef RT_USING_PM
rt_pm_device_unregister(&dw_wdt->parent.parent);
#endif
rt_device_unregister(&dw_wdt->parent.parent);
dw_wdt_free(dw_wdt);
return RT_EOK;
}
static const struct rt_ofw_node_id dw_wdt_ofw_ids[] =
{
{ .compatible = "snps,dw-wdt" },
{ /* sentinel */ }
};
static struct rt_platform_driver dw_wdt_driver =
{
.name = "dw-wdt",
.ids = dw_wdt_ofw_ids,
.probe = dw_wdt_probe,
.remove = dw_wdt_remove,
};
RT_PLATFORM_DRIVER_EXPORT(dw_wdt_driver);
@@ -0,0 +1,293 @@
/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-11-26 GuEe-GUI first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "wdt.i6300esb"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#define I6300ESB_REG_BAR 0
/* PCI configuration registers */
#define ESB_CONFIG_PCI_REG 0x60 /* Config register */
#define ESB_LOCK_PCI_REG 0x68 /* WDT lock register */
/* Memory mapped registers */
#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
#define ESB_GINTSR_REG 0x08 /* General Interrupt Status Reg */
#define ESB_RELOAD_REG 0x0c /* Reload register */
/* Lock register bits */
#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
/* Config register bits */
#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
#define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */
/* Reload register bits */
#define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */
#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
/* Magic constants */
#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
#define ESB_HEARTBEAT_MIN 1
#define ESB_HEARTBEAT_MAX 2046
#define ESB_HEARTBEAT_DEFAULT 30
struct i6300esb_wdt
{
rt_watchdog_t parent;
void *regs;
rt_uint32_t timeout;
struct rt_pci_device *pdev;
};
#define raw_to_i6300esb_wdt(raw) rt_container_of(raw, struct i6300esb_wdt, parent)
/*
* Prepare for reloading the timer by unlocking the proper registers.
* This is performed by first writing 0x80 followed by 0x86 to the
* reload register. After this the appropriate registers can be written
* to once before they need to be unlocked again.
*/
rt_inline void i6300esb_wdt_unlock_registers(struct i6300esb_wdt *esb)
{
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK1;
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK2;
}
static rt_uint32_t i6300esb_timer_start(struct i6300esb_wdt *esb)
{
i6300esb_wdt_unlock_registers(esb);
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, ESB_WDT_ENABLE);
return RT_EOK;
}
static rt_uint32_t i6300esb_timer_stop(struct i6300esb_wdt *esb)
{
rt_uint8_t val;
/* First, reset timers as suggested by the docs */
i6300esb_wdt_unlock_registers(esb);
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
/* Then disable the WDT */
rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, 0x0);
rt_pci_read_config_u8(esb->pdev, ESB_LOCK_PCI_REG, &val);
/* Returns 0 if the timer was disabled, non-zero otherwise */
return val & ESB_WDT_ENABLE;
}
static rt_err_t esb_timer_keepalive(struct i6300esb_wdt *esb)
{
i6300esb_wdt_unlock_registers(esb);
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
return RT_EOK;
}
static rt_err_t i6300esb_timer_set_heartbeat(struct i6300esb_wdt *esb, rt_uint32_t time)
{
rt_uint32_t val;
/*
* We shift by 9, so if we are passed a value of 1 sec,
* val will be 1 << 9 = 512, then write that to two
* timers => 2 * 512 = 1024 (which is decremented at 1KHz)
*/
val = time << 9;
/* Write timer 1 */
i6300esb_wdt_unlock_registers(esb);
HWREG32(esb->regs + ESB_TIMER1_REG) = val;
/* Write timer 2 */
i6300esb_wdt_unlock_registers(esb);
HWREG32(esb->regs + ESB_TIMER2_REG) = val;
/* Reload */
i6300esb_wdt_unlock_registers(esb);
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD;
esb->timeout = time;
return RT_EOK;
}
static rt_err_t i6300esb_wdt_init(rt_watchdog_t *wdt)
{
return RT_EOK;
}
static rt_err_t i6300esb_wdt_control(rt_watchdog_t *wdt, int cmd, void *args)
{
rt_err_t err = RT_EOK;
struct i6300esb_wdt *esb = raw_to_i6300esb_wdt(wdt);
switch (cmd)
{
case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
*(rt_uint32_t *)args = esb->timeout;
break;
case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
err = i6300esb_timer_set_heartbeat(esb, *(rt_uint32_t *)args);
break;
case RT_DEVICE_CTRL_WDT_KEEPALIVE:
err = esb_timer_keepalive(esb);
break;
case RT_DEVICE_CTRL_WDT_START:
err = i6300esb_timer_start(esb);
break;
case RT_DEVICE_CTRL_WDT_STOP:
err = i6300esb_timer_stop(esb);
break;
default:
err = -RT_EINVAL;
}
return err;
}
static const struct rt_watchdog_ops i6300esb_wdt_ops =
{
.init = i6300esb_wdt_init,
.control = i6300esb_wdt_control,
};
static rt_err_t i6300esb_wdt_probe(struct rt_pci_device *pdev)
{
rt_err_t err;
rt_uint8_t val1;
rt_uint16_t val2;
const char *dev_name;
struct i6300esb_wdt *esb = rt_calloc(1, sizeof(*esb));
if (!esb)
{
return -RT_ENOMEM;
}
esb->regs = rt_pci_iomap(pdev, I6300ESB_REG_BAR);
if (!esb->regs)
{
err = -RT_EIO;
goto _fail;
}
/*
* Config register:
* Bit 5 : 0 = Enable WDT_OUTPUT
* Bit 2 : 0 = set the timer frequency to the PCI clock
* divided by 2^15 (approx 1KHz).
* Bits 1:0 : 11 = WDT_INT_TYPE Disabled.
* The watchdog has two timers, it can be setup so that the expiry of timer1
* results in an interrupt and the expiry of timer2 results in a reboot.
* We set it to not generate any interrupts as there is not much
* we can do with it right now.
*/
rt_pci_write_config_u16(pdev, ESB_CONFIG_PCI_REG, 0x0003);
/* Check that the WDT isn't already locked */
rt_pci_read_config_u8(pdev, ESB_LOCK_PCI_REG, &val1);
if (val1 & ESB_WDT_LOCK)
{
LOG_W("Nowayout already set");
}
/* Set the timer to watchdog mode and disable it for now */
rt_pci_write_config_u8(pdev, ESB_LOCK_PCI_REG, 0x00);
/* Check if the watchdog was previously triggered */
i6300esb_wdt_unlock_registers(esb);
val2 = HWREG16(esb->regs + ESB_RELOAD_REG);
if (val2 & ESB_WDT_TIMEOUT)
{
LOG_D("Card previously reset the CPU");
}
/* Reset WDT_TIMEOUT flag and timers */
i6300esb_wdt_unlock_registers(esb);
HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_TIMEOUT | ESB_WDT_RELOAD;
/* And set the correct timeout value */
i6300esb_timer_set_heartbeat(esb, ESB_HEARTBEAT_DEFAULT);
pdev->parent.user_data = esb;
esb->pdev = pdev;
esb->parent.ops = &i6300esb_wdt_ops;
rt_dm_dev_set_name_auto(&esb->parent.parent, "wdt");
dev_name = rt_dm_dev_get_name(&esb->parent.parent);
rt_hw_watchdog_register(&esb->parent, dev_name, 0, esb);
return RT_EOK;
_fail:
if (esb->regs)
{
rt_iounmap(esb->regs);
}
rt_free(esb);
return err;
}
static rt_err_t i6300esb_wdt_remove(struct rt_pci_device *pdev)
{
struct i6300esb_wdt *esb = pdev->parent.user_data;
i6300esb_timer_stop(esb);
rt_device_unregister(&esb->parent.parent);
rt_iounmap(esb->regs);
rt_free(esb);
return RT_EOK;
}
static const struct rt_pci_device_id i6300esb_wdt_pci_ids[] =
{
{ RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x25ab), },
{ /* sentinel */ }
};
static struct rt_pci_driver i6300esb_wdt_driver =
{
.name = "i6300esb-wdt",
.ids = i6300esb_wdt_pci_ids,
.probe = i6300esb_wdt_probe,
.remove = i6300esb_wdt_remove,
};
RT_PCI_DRIVER_EXPORT(i6300esb_wdt_driver);