1042 lines
30 KiB
C
1042 lines
30 KiB
C
|
/*
|
||
|
* Copyright(C) 2016 Ruijie Network. All rights reserved.
|
||
|
*
|
||
|
* rg_wdt.c
|
||
|
* ko for watchdog function
|
||
|
* Original Author: sonic_rd@ruijie.com.cn 2020-09-28
|
||
|
*/
|
||
|
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/watchdog.h>
|
||
|
#include <linux/hrtimer.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/hwmon-sysfs.h>
|
||
|
|
||
|
#include "rg_wdt.h"
|
||
|
|
||
|
#define GPIO_FEED_WDT_MODE (1)
|
||
|
#define LOGIC_FEED_WDT_MODE (2)
|
||
|
|
||
|
#define SYMBOL_I2C_DEV_MODE (1)
|
||
|
#define SYMBOL_PCIE_DEV_MODE (2)
|
||
|
#define SYMBOL_IO_DEV_MODE (3)
|
||
|
#define FILE_MODE (4)
|
||
|
|
||
|
#define ONE_BYTE (1)
|
||
|
|
||
|
#define WDT_OFF (0)
|
||
|
#define WDT_ON (1)
|
||
|
|
||
|
#define MS_TO_S (1000)
|
||
|
#define MS_TO_NS (1000 * 1000)
|
||
|
|
||
|
#define MAX_REG_VAL (255)
|
||
|
|
||
|
extern int i2c_device_func_write(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
extern int i2c_device_func_read(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
extern int pcie_device_func_write(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
extern int pcie_device_func_read(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
extern int io_device_func_write(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
extern int io_device_func_read(const char *path, uint32_t offset, uint8_t *buf, size_t count);
|
||
|
|
||
|
int g_rg_wdt_debug = 0;
|
||
|
int g_rg_wdt_error = 0;
|
||
|
|
||
|
module_param(g_rg_wdt_debug, int, S_IRUGO | S_IWUSR);
|
||
|
module_param(g_rg_wdt_error, int, S_IRUGO | S_IWUSR);
|
||
|
|
||
|
#define WDT_VERBOSE(fmt, args...) do { \
|
||
|
if (g_rg_wdt_debug) { \
|
||
|
printk(KERN_INFO "[WDT][VER][func:%s line:%d]\r\n"fmt, __func__, __LINE__, ## args); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
#define WDT_ERROR(fmt, args...) do { \
|
||
|
if (g_rg_wdt_error) { \
|
||
|
printk(KERN_ERR "[WDT][ERR][func:%s line:%d]\r\n"fmt, __func__, __LINE__, ## args); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
enum {
|
||
|
HW_ALGO_TOGGLE,
|
||
|
HW_ALGO_LEVEL,
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
WATCHDOG_DEVICE_TYPE = 0,
|
||
|
HRTIMER_TYPE,
|
||
|
THREAD_TYPE,
|
||
|
};
|
||
|
|
||
|
typedef struct rg_wdt_priv_s {
|
||
|
|
||
|
struct task_struct *thread;
|
||
|
struct hrtimer hrtimer;
|
||
|
ktime_t m_kt;
|
||
|
const char *config_dev_name;
|
||
|
uint8_t config_mode;
|
||
|
uint8_t hw_algo;
|
||
|
uint8_t enable_val;
|
||
|
uint8_t disable_val;
|
||
|
uint8_t enable_mask;
|
||
|
uint8_t priv_func_mode;
|
||
|
uint8_t feed_wdt_type;
|
||
|
uint32_t enable_reg;
|
||
|
uint32_t timeout_cfg_reg;
|
||
|
uint32_t timeleft_cfg_reg;
|
||
|
uint32_t hw_margin;
|
||
|
uint32_t feed_time;
|
||
|
uint32_t timer_accuracy;
|
||
|
gpio_wdt_info_t gpio_wdt;
|
||
|
logic_wdt_info_t logic_wdt;
|
||
|
struct device *dev;
|
||
|
const struct attribute_group *sysfs_group;
|
||
|
uint8_t sysfs_index;
|
||
|
struct mutex update_lock;
|
||
|
struct watchdog_device wdd;
|
||
|
}rg_wdt_priv_t;
|
||
|
|
||
|
static int wdt_file_read(const char *path, uint32_t pos, uint8_t *val, size_t size)
|
||
|
{
|
||
|
int ret;
|
||
|
struct file *filp;
|
||
|
loff_t tmp_pos;
|
||
|
|
||
|
filp = filp_open(path, O_RDONLY, 0);
|
||
|
if (IS_ERR(filp)) {
|
||
|
WDT_ERROR("read open failed errno = %ld\r\n", -PTR_ERR(filp));
|
||
|
filp = NULL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
tmp_pos = (loff_t)pos;
|
||
|
ret = kernel_read(filp, val, size, &tmp_pos);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("kernel_read failed, path=%s, addr=0x%x, size=%ld, ret=%d\r\n", path, pos, size, ret);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
filp_close(filp, NULL);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
exit:
|
||
|
if (filp != NULL) {
|
||
|
filp_close(filp, NULL);
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int wdt_file_write(const char *path, uint32_t pos, uint8_t *val, size_t size)
|
||
|
{
|
||
|
int ret;
|
||
|
struct file *filp;
|
||
|
loff_t tmp_pos;
|
||
|
|
||
|
filp = filp_open(path, O_RDWR, 777);
|
||
|
if (IS_ERR(filp)) {
|
||
|
WDT_ERROR("write open failed errno = %ld\r\n", -PTR_ERR(filp));
|
||
|
filp = NULL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
tmp_pos = (loff_t)pos;
|
||
|
ret = kernel_write(filp, val, size, &tmp_pos);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("kernel_write failed, path=%s, addr=0x%x, size=%ld, ret=%d\r\n", path, pos, size, ret);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
vfs_fsync(filp, 1);
|
||
|
filp_close(filp, NULL);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
exit:
|
||
|
if (filp != NULL) {
|
||
|
filp_close(filp, NULL);
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_read(uint8_t mode, const char *path,
|
||
|
uint32_t offset, uint8_t *buf, size_t count)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
switch (mode) {
|
||
|
case SYMBOL_I2C_DEV_MODE:
|
||
|
ret = i2c_device_func_read(path, offset, buf, count);
|
||
|
break;
|
||
|
case SYMBOL_PCIE_DEV_MODE:
|
||
|
ret = pcie_device_func_read(path, offset, buf, count);
|
||
|
break;
|
||
|
case SYMBOL_IO_DEV_MODE:
|
||
|
ret = io_device_func_read(path, offset, buf, count);
|
||
|
break;
|
||
|
case FILE_MODE:
|
||
|
ret = wdt_file_read(path, offset, buf, count);
|
||
|
break;
|
||
|
default:
|
||
|
WDT_ERROR("mode %u error, wdt func read failed.\n", mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
WDT_VERBOSE("wdt func read mode:%u,dev_nam:%s, offset:0x%x, read_val:0x%x, size:%lu.\n",
|
||
|
mode, path, offset, *buf, count);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_write(uint8_t mode, const char *path,
|
||
|
uint32_t offset, uint8_t *buf, size_t count)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
switch (mode) {
|
||
|
case SYMBOL_I2C_DEV_MODE:
|
||
|
ret = i2c_device_func_write(path, offset, buf, count);
|
||
|
break;
|
||
|
case SYMBOL_PCIE_DEV_MODE:
|
||
|
ret = pcie_device_func_write(path, offset, buf, count);
|
||
|
break;
|
||
|
case SYMBOL_IO_DEV_MODE:
|
||
|
ret = io_device_func_write(path, offset, buf, count);
|
||
|
break;
|
||
|
case FILE_MODE:
|
||
|
ret = wdt_file_write(path, offset, buf, count);
|
||
|
break;
|
||
|
default:
|
||
|
WDT_ERROR("mode %u error, wdt func write failed.\n", mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
WDT_VERBOSE("wdt func write mode:%u, dev_nam:%s, offset:0x%x, write_val:0x%x, size:%lu.\n",
|
||
|
mode, path, offset, *buf, count);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_enable_ctrl(rg_wdt_priv_t *priv, uint8_t flag)
|
||
|
{
|
||
|
int ret;
|
||
|
uint8_t val;
|
||
|
uint8_t ctrl_val;
|
||
|
|
||
|
switch (flag) {
|
||
|
case WDT_ON:
|
||
|
ctrl_val = priv->enable_val;
|
||
|
break;
|
||
|
case WDT_OFF:
|
||
|
ctrl_val = priv->disable_val;
|
||
|
break;
|
||
|
default:
|
||
|
WDT_ERROR("unsupport wdt enable ctrl:%u.\n", flag);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = rg_wdt_read(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->enable_reg, &val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "read wdt control reg error.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
val &= ~priv->enable_mask;
|
||
|
|
||
|
val |= ctrl_val & priv->enable_mask;
|
||
|
|
||
|
ret = rg_wdt_write(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->enable_reg, &val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "write wdt control reg error.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void wdt_hwping(rg_wdt_priv_t *priv)
|
||
|
{
|
||
|
gpio_wdt_info_t *gpio_wdt;
|
||
|
logic_wdt_info_t *logic_wdt;
|
||
|
uint8_t tmp_val;
|
||
|
int ret;
|
||
|
|
||
|
if (priv->config_mode == GPIO_FEED_WDT_MODE) {
|
||
|
gpio_wdt = &priv->gpio_wdt;
|
||
|
switch (priv->hw_algo) {
|
||
|
case HW_ALGO_TOGGLE:
|
||
|
gpio_wdt = &priv->gpio_wdt;
|
||
|
gpio_wdt->state = !gpio_wdt->state;
|
||
|
gpio_set_value_cansleep(gpio_wdt->gpio, gpio_wdt->state);
|
||
|
WDT_VERBOSE("gpio toggle wdt work. val:%u\n", gpio_wdt->state);
|
||
|
break;
|
||
|
case HW_ALGO_LEVEL:
|
||
|
gpio_wdt = &priv->gpio_wdt;
|
||
|
/* Pulse */
|
||
|
gpio_set_value_cansleep(gpio_wdt->gpio, !gpio_wdt->active_low);
|
||
|
udelay(1);
|
||
|
gpio_set_value_cansleep(gpio_wdt->gpio, gpio_wdt->active_low);
|
||
|
WDT_VERBOSE("gpio level wdt work.\n");
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
logic_wdt = &priv->logic_wdt;
|
||
|
switch (priv->hw_algo) {
|
||
|
case HW_ALGO_TOGGLE:
|
||
|
logic_wdt->active_val = !logic_wdt->active_val;
|
||
|
ret = rg_wdt_write(logic_wdt->logic_func_mode, logic_wdt->feed_dev_name,
|
||
|
logic_wdt->feed_reg, &logic_wdt->active_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("logic toggle wdt write failed.ret = %d\n", ret);
|
||
|
}
|
||
|
WDT_VERBOSE("logic toggle wdt work.\n");
|
||
|
break;
|
||
|
case HW_ALGO_LEVEL:
|
||
|
tmp_val = !logic_wdt->active_val;
|
||
|
ret = rg_wdt_write(logic_wdt->logic_func_mode, logic_wdt->feed_dev_name,
|
||
|
logic_wdt->feed_reg, &tmp_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("logic level wdt write first failed.ret = %d\n", ret);
|
||
|
}
|
||
|
udelay(1);
|
||
|
ret = rg_wdt_write(logic_wdt->logic_func_mode, logic_wdt->feed_dev_name,
|
||
|
logic_wdt->feed_reg, &logic_wdt->active_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("logic level wdt write second failed.ret = %d\n", ret);
|
||
|
}
|
||
|
WDT_VERBOSE("logic level wdt work.\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static enum hrtimer_restart hrtimer_hwping(struct hrtimer *timer)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = container_of(timer, rg_wdt_priv_t, hrtimer);
|
||
|
|
||
|
wdt_hwping(priv);
|
||
|
hrtimer_forward(timer, timer->base->get_time(), priv->m_kt);
|
||
|
return HRTIMER_RESTART;
|
||
|
}
|
||
|
|
||
|
static int thread_timer_cfg(rg_wdt_priv_t *priv, rg_wdt_device_t *rg_wdt_device)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
uint32_t hw_margin;
|
||
|
uint32_t feed_time;
|
||
|
uint32_t accuracy;
|
||
|
uint8_t set_time_val;
|
||
|
int ret;
|
||
|
|
||
|
dev = priv->dev;
|
||
|
|
||
|
ret = 0;
|
||
|
if (dev->of_node) {
|
||
|
ret += of_property_read_u32(dev->of_node, "feed_time", &priv->feed_time);
|
||
|
if (ret != 0) {
|
||
|
dev_err(dev, "thread Failed to priv dts.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
} else {
|
||
|
priv->feed_time = rg_wdt_device->feed_time;
|
||
|
}
|
||
|
WDT_VERBOSE("thread priv->feed_time: %u.\n", priv->feed_time);
|
||
|
|
||
|
hw_margin = priv->hw_margin;
|
||
|
feed_time = priv->feed_time;
|
||
|
accuracy = priv->timer_accuracy;
|
||
|
|
||
|
if ((feed_time > (hw_margin / 2)) || (feed_time == 0)) {
|
||
|
dev_err(dev, "thread timer feed_time[%d] should be less than half hw_margin or zero.\n", feed_time);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
set_time_val = hw_margin / accuracy;
|
||
|
ret = rg_wdt_write(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->timeout_cfg_reg, &set_time_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(dev, "set wdt thread timer reg error.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int wdt_thread_timer(void *data)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = data;
|
||
|
|
||
|
while (!kthread_should_stop()) {
|
||
|
schedule_timeout_uninterruptible(msecs_to_jiffies(priv->feed_time));
|
||
|
wdt_hwping(priv);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int thread_timer_create(rg_wdt_priv_t *priv, rg_wdt_device_t *rg_wdt_device)
|
||
|
{
|
||
|
struct task_struct *p;
|
||
|
int ret;
|
||
|
|
||
|
ret = thread_timer_cfg(priv, rg_wdt_device);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "set wdt thread timer failed.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
p = kthread_create(wdt_thread_timer, (void *)priv, "%s", "rg_wdt");
|
||
|
if (!IS_ERR(p)) {
|
||
|
WDT_VERBOSE("timer thread create success.\n");
|
||
|
priv->thread = p;
|
||
|
wake_up_process(p);
|
||
|
} else {
|
||
|
dev_err(priv->dev, "timer thread create failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_ON);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "thread enable wdt failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int hrtimer_cfg(rg_wdt_priv_t *priv, rg_wdt_device_t *rg_wdt_device)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
struct hrtimer *hrtimer;
|
||
|
uint8_t set_time_val;
|
||
|
uint8_t hrtimer_s;
|
||
|
uint32_t hrtimer_ns;
|
||
|
int ret;
|
||
|
uint32_t hw_margin;
|
||
|
uint32_t feed_time;
|
||
|
uint32_t accuracy;
|
||
|
uint32_t max_timeout;
|
||
|
|
||
|
dev = priv->dev;
|
||
|
|
||
|
ret = 0;
|
||
|
if (dev->of_node) {
|
||
|
ret += of_property_read_u32(dev->of_node, "feed_time", &priv->feed_time);
|
||
|
if (ret != 0) {
|
||
|
dev_err(dev, "hrtimer Failed to priv dts.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
} else {
|
||
|
priv->feed_time = rg_wdt_device->feed_time;
|
||
|
}
|
||
|
WDT_VERBOSE("hrtimer priv->feed_time: %u.\n", priv->feed_time);
|
||
|
|
||
|
hrtimer = &priv->hrtimer;
|
||
|
hw_margin = priv->hw_margin;
|
||
|
feed_time = priv->feed_time;
|
||
|
accuracy = priv->timer_accuracy;
|
||
|
max_timeout = accuracy * 255;
|
||
|
|
||
|
if (hw_margin < accuracy || hw_margin > max_timeout) {
|
||
|
dev_err(dev, "hrtimer_hw_margin should be between %u and %u.\n",
|
||
|
accuracy, max_timeout);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if ((feed_time > (hw_margin / 2)) || (feed_time == 0)) {
|
||
|
dev_err(dev, "feed_time[%d] should be less than half hw_margin or zeor.\n", feed_time);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
hrtimer_s = feed_time / MS_TO_S;
|
||
|
hrtimer_ns = (feed_time % MS_TO_S) * MS_TO_NS;
|
||
|
set_time_val = hw_margin / accuracy;
|
||
|
|
||
|
ret = rg_wdt_write(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->timeout_cfg_reg, &set_time_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(dev, "set wdt time reg error.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
priv->m_kt = ktime_set(hrtimer_s, hrtimer_ns);
|
||
|
hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||
|
hrtimer->function = hrtimer_hwping;
|
||
|
hrtimer_start(hrtimer, priv->m_kt, HRTIMER_MODE_REL);
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_ON);
|
||
|
if (ret < 0) {
|
||
|
dev_err(dev, "hrtimer enable wdt failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_ping(struct watchdog_device *wdd)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = watchdog_get_drvdata(wdd);
|
||
|
|
||
|
wdt_hwping(priv);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_start(struct watchdog_device *wdd)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = watchdog_get_drvdata(wdd);
|
||
|
int ret;
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_ON);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("start wdt enable failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_stop(struct watchdog_device *wdd)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = watchdog_get_drvdata(wdd);
|
||
|
int ret;
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_OFF);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("stop wdt enable failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
clear_bit(WDOG_HW_RUNNING, &wdd->status);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = watchdog_get_drvdata(wdd);
|
||
|
uint32_t timeout_ms;
|
||
|
uint32_t accuracy;
|
||
|
uint8_t set_time_val;
|
||
|
int ret;
|
||
|
|
||
|
accuracy = priv->timer_accuracy;
|
||
|
timeout_ms = t * 1000;
|
||
|
if (timeout_ms > accuracy * 255) {
|
||
|
WDT_ERROR("set wdt timeout too larger error.timeout_ms:%u\n", timeout_ms);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
set_time_val = timeout_ms / accuracy;
|
||
|
ret = rg_wdt_write(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->timeout_cfg_reg, &set_time_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("set wdt timeout reg error, set_time_val:%u ret:%d\n", set_time_val, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
wdd->timeout = t;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static unsigned int rg_wdt_get_timeleft(struct watchdog_device *wdd)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = watchdog_get_drvdata(wdd);
|
||
|
unsigned int time_left;
|
||
|
uint32_t accuracy;
|
||
|
uint8_t get_time_val;
|
||
|
int ret;
|
||
|
|
||
|
accuracy = priv->timer_accuracy;
|
||
|
|
||
|
ret = rg_wdt_read(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->timeleft_cfg_reg, &get_time_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("get wdt timeout reg error.ret:%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
time_left = get_time_val * accuracy / MS_TO_S;
|
||
|
|
||
|
WDT_VERBOSE("get wdt timeleft %d get_time_val %d accuracy=%d\n",
|
||
|
time_left, get_time_val, accuracy);
|
||
|
return time_left;
|
||
|
}
|
||
|
|
||
|
static const struct watchdog_info rg_wdt_ident = {
|
||
|
.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
|
||
|
.firmware_version = 0,
|
||
|
.identity = "CPLD Watchdog",
|
||
|
};
|
||
|
|
||
|
static const struct watchdog_ops rg_wdt_ops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.start = rg_wdt_start,
|
||
|
.stop = rg_wdt_stop,
|
||
|
.ping = rg_wdt_ping,
|
||
|
.set_timeout = rg_wdt_set_timeout,
|
||
|
.get_timeleft = rg_wdt_get_timeleft,
|
||
|
};
|
||
|
|
||
|
static int watchdog_device_cfg(rg_wdt_priv_t *priv)
|
||
|
{
|
||
|
int ret;
|
||
|
uint8_t set_time_val;
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_OFF);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "probe disable wdt failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
set_time_val = priv->hw_margin / priv->timer_accuracy;
|
||
|
ret = rg_wdt_write(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->timeout_cfg_reg, &set_time_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "set wdt time reg error.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
watchdog_set_drvdata(&priv->wdd, priv);
|
||
|
|
||
|
priv->wdd.info = &rg_wdt_ident;
|
||
|
priv->wdd.ops = &rg_wdt_ops;
|
||
|
priv->wdd.bootstatus = 0;
|
||
|
priv->wdd.timeout = priv->hw_margin / MS_TO_S;
|
||
|
priv->wdd.min_timeout = priv->timer_accuracy / MS_TO_S;
|
||
|
priv->wdd.max_timeout = priv->timer_accuracy * MAX_REG_VAL / MS_TO_S;
|
||
|
priv->wdd.parent = priv->dev;
|
||
|
|
||
|
watchdog_stop_on_reboot(&priv->wdd);
|
||
|
|
||
|
ret = devm_watchdog_register_device(priv->dev, &priv->wdd);
|
||
|
if (ret != 0) {
|
||
|
dev_err(priv->dev, "cannot register watchdog device (err=%d)\n", ret);
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int logic_wdt_init(rg_wdt_priv_t *priv, rg_wdt_device_t *rg_wdt_device)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
logic_wdt_info_t *logic_wdt;
|
||
|
int ret;
|
||
|
|
||
|
dev = priv->dev;
|
||
|
logic_wdt = &priv->logic_wdt;
|
||
|
|
||
|
ret = 0;
|
||
|
if (dev->of_node) {
|
||
|
ret += of_property_read_string(dev->of_node, "feed_dev_name", &logic_wdt->feed_dev_name);
|
||
|
ret += of_property_read_u32(dev->of_node, "feed_reg", &logic_wdt->feed_reg);
|
||
|
ret += of_property_read_u8(dev->of_node, "active_val", &logic_wdt->active_val);
|
||
|
ret += of_property_read_u8(dev->of_node, "logic_func_mode", &logic_wdt->logic_func_mode);
|
||
|
if (ret != 0) {
|
||
|
dev_err(dev, "Failed to logic_wdt dts.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
} else {
|
||
|
logic_wdt->feed_dev_name = rg_wdt_device->wdt_config_mode.logic_wdt.feed_dev_name;
|
||
|
logic_wdt->feed_reg = rg_wdt_device->wdt_config_mode.logic_wdt.feed_reg;
|
||
|
logic_wdt->active_val = rg_wdt_device->wdt_config_mode.logic_wdt.active_val;
|
||
|
logic_wdt->logic_func_mode = rg_wdt_device->wdt_config_mode.logic_wdt.logic_func_mode;
|
||
|
}
|
||
|
|
||
|
logic_wdt->state_val = logic_wdt->active_val;
|
||
|
|
||
|
WDT_VERBOSE("feed_dev_name:%s, feed_reg:0x%x, active_val:%u, logic_func_mode:%u\n",
|
||
|
logic_wdt->feed_dev_name, logic_wdt->feed_reg,
|
||
|
logic_wdt->active_val, logic_wdt->logic_func_mode);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int gpio_wdt_init(rg_wdt_priv_t *priv, rg_wdt_device_t *rg_wdt_device)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
gpio_wdt_info_t *gpio_wdt;
|
||
|
enum of_gpio_flags flags;
|
||
|
uint32_t f = 0;
|
||
|
int ret;
|
||
|
|
||
|
dev = priv->dev;
|
||
|
gpio_wdt = &priv->gpio_wdt;
|
||
|
|
||
|
if (dev->of_node) {
|
||
|
gpio_wdt->gpio = of_get_gpio_flags(dev->of_node, 0, &flags);
|
||
|
} else {
|
||
|
gpio_wdt->gpio = rg_wdt_device->wdt_config_mode.gpio_wdt.gpio;
|
||
|
flags = rg_wdt_device->wdt_config_mode.gpio_wdt.flags;
|
||
|
}
|
||
|
if (!gpio_is_valid(gpio_wdt->gpio)) {
|
||
|
dev_err(dev, "gpio is invalid.\n");
|
||
|
return gpio_wdt->gpio;
|
||
|
}
|
||
|
|
||
|
gpio_wdt->active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||
|
|
||
|
if(priv->hw_algo == HW_ALGO_TOGGLE) {
|
||
|
f = GPIOF_IN;
|
||
|
} else {
|
||
|
f = gpio_wdt->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
|
||
|
}
|
||
|
|
||
|
ret = devm_gpio_request_one(dev, gpio_wdt->gpio, f,
|
||
|
dev_name(dev));
|
||
|
if (ret) {
|
||
|
dev_err(dev, "devm_gpio_request_one failed.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
gpio_wdt->state = gpio_wdt->active_low;
|
||
|
gpio_direction_output(gpio_wdt->gpio, gpio_wdt->state);
|
||
|
|
||
|
WDT_VERBOSE("active_low:%d\n", gpio_wdt->active_low);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t set_wdt_sysfs_value(struct device *dev, struct device_attribute *da,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = dev_get_drvdata(dev);
|
||
|
int ret, val;
|
||
|
|
||
|
val = 0;
|
||
|
sscanf(buf, "%d", &val);
|
||
|
WDT_VERBOSE("set wdt, val:%d.\n", val);
|
||
|
|
||
|
if (val < 0 || val > 255) {
|
||
|
WDT_ERROR("set wdt val %d failed.\n", val);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&priv->update_lock);
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, val);
|
||
|
if (ret < 0) {
|
||
|
WDT_ERROR("set wdt sysfs value:%u failed.\n", val);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
WDT_VERBOSE("set wdt sysfs value:%u successed.\n", val);
|
||
|
mutex_unlock(&priv->update_lock);
|
||
|
return count;
|
||
|
|
||
|
fail:
|
||
|
mutex_unlock(&priv->update_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t show_wdt_sysfs_value(struct device *dev,
|
||
|
struct device_attribute *da, char *buf)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = dev_get_drvdata(dev);
|
||
|
uint8_t val, status;
|
||
|
int ret;
|
||
|
|
||
|
mutex_lock(&priv->update_lock);
|
||
|
|
||
|
ret = rg_wdt_read(priv->priv_func_mode, priv->config_dev_name,
|
||
|
priv->enable_reg, &val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(priv->dev, "read wdt enable reg val error.\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
val &= priv->enable_mask;
|
||
|
if (val == priv->enable_val) {
|
||
|
status = WDT_ON;
|
||
|
} else if(val == priv->disable_val) {
|
||
|
status = WDT_OFF;
|
||
|
} else {
|
||
|
WDT_ERROR("enable reg read val not match set val, read val:%u, mask:%u, enable_val:%u, disable_val:%u",
|
||
|
val, priv->enable_mask, priv->enable_val, priv->disable_val);
|
||
|
ret = -EIO;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
WDT_VERBOSE("read_val:%u, mask:%u, enable_val:%u, disable_val:%u, status:%u",
|
||
|
val, priv->enable_mask, priv->enable_val, priv->disable_val, status);
|
||
|
|
||
|
mutex_unlock(&priv->update_lock);
|
||
|
return sprintf(buf, "%u\n", status);
|
||
|
|
||
|
fail:
|
||
|
mutex_unlock(&priv->update_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static SENSOR_DEVICE_ATTR(wdt_status, S_IRUGO | S_IWUSR, show_wdt_sysfs_value, set_wdt_sysfs_value, 0);
|
||
|
|
||
|
static struct attribute *wdt_sysfs_attrs[] = {
|
||
|
&sensor_dev_attr_wdt_status.dev_attr.attr,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group wdt_sysfs_group = {
|
||
|
.attrs = wdt_sysfs_attrs,
|
||
|
};
|
||
|
|
||
|
struct wdt_attr_match_group {
|
||
|
uint8_t index;
|
||
|
const struct attribute_group *attr_group_ptr;
|
||
|
};
|
||
|
|
||
|
static struct wdt_attr_match_group g_wdt_attr_match[] = {
|
||
|
{0, &wdt_sysfs_group},
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *wdt_get_attr_group(uint32_t index)
|
||
|
{
|
||
|
int i;
|
||
|
struct wdt_attr_match_group *group;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(g_wdt_attr_match); i++) {
|
||
|
group = &g_wdt_attr_match[i];
|
||
|
if (index == group->index) {
|
||
|
WDT_VERBOSE("get wdt attr, index:%u.\n", index);
|
||
|
return group->attr_group_ptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv;
|
||
|
int ret;
|
||
|
const char *algo;
|
||
|
rg_wdt_device_t *rg_wdt_device;
|
||
|
|
||
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||
|
if (!priv) {
|
||
|
dev_err(&pdev->dev, "devm_kzalloc failed.\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
platform_set_drvdata(pdev, priv);
|
||
|
|
||
|
if (pdev->dev.of_node) {
|
||
|
ret = 0;
|
||
|
ret += of_property_read_string(pdev->dev.of_node, "config_dev_name", &priv->config_dev_name);
|
||
|
ret += of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node, "config_mode", &priv->config_mode);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node, "priv_func_mode", &priv->priv_func_mode);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node, "enable_val", &priv->enable_val);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node, "disable_val", &priv->disable_val);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node, "enable_mask", &priv->enable_mask);
|
||
|
ret += of_property_read_u32(pdev->dev.of_node, "enable_reg", &priv->enable_reg);
|
||
|
ret += of_property_read_u32(pdev->dev.of_node, "timeout_cfg_reg", &priv->timeout_cfg_reg);
|
||
|
ret += of_property_read_u32(pdev->dev.of_node,"hw_margin_ms", &priv->hw_margin);
|
||
|
ret += of_property_read_u8(pdev->dev.of_node,"feed_wdt_type", &priv->feed_wdt_type);
|
||
|
ret += of_property_read_u32(pdev->dev.of_node,"timer_accuracy", &priv->timer_accuracy);
|
||
|
if (ret != 0) {
|
||
|
dev_err(&pdev->dev, "Failed to priv dts.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
priv->sysfs_index = SYSFS_NO_CFG;
|
||
|
of_property_read_u8(pdev->dev.of_node,"sysfs_index", &priv->sysfs_index);
|
||
|
|
||
|
priv->timeleft_cfg_reg = priv->timeout_cfg_reg;
|
||
|
of_property_read_u32(pdev->dev.of_node,"timeleft_cfg_reg", &priv->timeleft_cfg_reg);
|
||
|
} else {
|
||
|
if (pdev->dev.platform_data == NULL) {
|
||
|
dev_err(&pdev->dev, "Failed to get platform data config.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
rg_wdt_device = pdev->dev.platform_data;
|
||
|
priv->config_dev_name = rg_wdt_device->config_dev_name;
|
||
|
algo = rg_wdt_device->hw_algo;
|
||
|
priv->config_mode = rg_wdt_device->config_mode;
|
||
|
priv->priv_func_mode = rg_wdt_device->priv_func_mode;
|
||
|
priv->enable_val = rg_wdt_device->enable_val;
|
||
|
priv->disable_val = rg_wdt_device->disable_val;
|
||
|
priv->enable_mask = rg_wdt_device->enable_mask;
|
||
|
priv->enable_reg = rg_wdt_device->enable_reg;
|
||
|
priv->timeout_cfg_reg = rg_wdt_device->timeout_cfg_reg;
|
||
|
priv->hw_margin = rg_wdt_device->hw_margin;
|
||
|
priv->timer_accuracy = rg_wdt_device->timer_accuracy;
|
||
|
priv->feed_wdt_type = rg_wdt_device->feed_wdt_type;
|
||
|
priv->sysfs_index = rg_wdt_device->sysfs_index;
|
||
|
priv->timeleft_cfg_reg = rg_wdt_device->timeleft_cfg_reg;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(algo, "toggle")) {
|
||
|
priv->hw_algo = HW_ALGO_TOGGLE;
|
||
|
} else if (!strcmp(algo, "level")) {
|
||
|
priv->hw_algo = HW_ALGO_LEVEL;
|
||
|
} else {
|
||
|
dev_err(&pdev->dev, "hw_algo config error.must be toggle or level.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
WDT_VERBOSE("config_dev_name:%s, config_mode:%u, priv_func_mode:%u, enable_reg:0x%x, timeout_cfg_reg:0x%x\n",
|
||
|
priv->config_dev_name, priv->config_mode, priv->priv_func_mode, priv->enable_reg, priv->timeout_cfg_reg);
|
||
|
WDT_VERBOSE("timeout_cfg_reg:0x%x, enable_val:%u, disable_val:%u, enable_mask:%u, hw_margin:%u, feed_wdt_type:%u\n",
|
||
|
priv->timeleft_cfg_reg, priv->enable_val, priv->disable_val, priv->enable_mask, priv->hw_margin, priv->feed_wdt_type);
|
||
|
|
||
|
priv->dev = &pdev->dev;
|
||
|
if (priv->config_mode == GPIO_FEED_WDT_MODE) {
|
||
|
ret = gpio_wdt_init(priv, rg_wdt_device);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&pdev->dev, "init gpio mode wdt failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
} else if (priv->config_mode == LOGIC_FEED_WDT_MODE) {
|
||
|
ret = logic_wdt_init(priv, rg_wdt_device);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&pdev->dev, "init func mode wdt failed.\n");
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
} else {
|
||
|
dev_err(&pdev->dev, "unsupport %u config_mode, dts configure error.\n",
|
||
|
priv->config_mode);
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
switch (priv->feed_wdt_type) {
|
||
|
case WATCHDOG_DEVICE_TYPE:
|
||
|
ret = watchdog_device_cfg(priv);
|
||
|
break;
|
||
|
case HRTIMER_TYPE:
|
||
|
ret = hrtimer_cfg(priv, rg_wdt_device);
|
||
|
break;
|
||
|
case THREAD_TYPE:
|
||
|
ret = thread_timer_create(priv, rg_wdt_device);
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(&pdev->dev, "timer type %u unsupport.\n", priv->feed_wdt_type);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (ret < 0) {
|
||
|
dev_err(&pdev->dev, "init timer feed_wdt_type %u failed.\n", priv->feed_wdt_type);
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
dev_info(&pdev->dev, "register %s mode, config_mode %u, func_mode %u, %u ms overtime wdt success\n",
|
||
|
algo, priv->config_mode, priv->priv_func_mode, priv->hw_margin);
|
||
|
|
||
|
if (priv->sysfs_index != SYSFS_NO_CFG) {
|
||
|
|
||
|
priv->sysfs_group = wdt_get_attr_group(priv->sysfs_index);
|
||
|
if (priv->sysfs_group) {
|
||
|
ret = sysfs_create_group(&pdev->dev.kobj, priv->sysfs_group);
|
||
|
if (ret != 0) {
|
||
|
dev_err(&pdev->dev, "sysfs_create_group failed. ret:%d.\n", ret);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
dev_info(&pdev->dev, "sysfs create group success\n");
|
||
|
} else {
|
||
|
dev_err(&pdev->dev, "failed to find %u index wdt, return NULL.\n", priv->sysfs_index);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
mutex_init(&priv->update_lock);
|
||
|
|
||
|
dev_info(&pdev->dev, "register %u index wdt sysfs success." ,priv->sysfs_index);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void unregister_action(struct platform_device *pdev)
|
||
|
{
|
||
|
rg_wdt_priv_t *priv = platform_get_drvdata(pdev);
|
||
|
gpio_wdt_info_t *gpio_wdt;
|
||
|
logic_wdt_info_t *logic_wdt;
|
||
|
int ret;
|
||
|
|
||
|
ret = rg_wdt_enable_ctrl(priv, WDT_OFF);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&pdev->dev, "remove disable wdt failed.\n");
|
||
|
}
|
||
|
|
||
|
if (priv->sysfs_index != SYSFS_NO_CFG) {
|
||
|
sysfs_remove_group(&pdev->dev.kobj, priv->sysfs_group);
|
||
|
}
|
||
|
|
||
|
if (priv->feed_wdt_type == HRTIMER_TYPE) {
|
||
|
hrtimer_cancel(&priv->hrtimer);
|
||
|
} else if (priv->feed_wdt_type == THREAD_TYPE) {
|
||
|
kthread_stop(priv->thread);
|
||
|
priv->thread = NULL;
|
||
|
} else {
|
||
|
WDT_VERBOSE("wdd type, do nothing.\n");
|
||
|
}
|
||
|
|
||
|
if (priv->config_mode == GPIO_FEED_WDT_MODE) {
|
||
|
gpio_wdt = &priv->gpio_wdt;
|
||
|
gpio_set_value_cansleep(gpio_wdt->gpio, !gpio_wdt->active_low);
|
||
|
|
||
|
if (priv->hw_algo == HW_ALGO_TOGGLE) {
|
||
|
gpio_direction_input(gpio_wdt->gpio);
|
||
|
}
|
||
|
} else {
|
||
|
logic_wdt = &priv->logic_wdt;
|
||
|
logic_wdt->state_val = !logic_wdt->state_val;
|
||
|
ret = rg_wdt_write(logic_wdt->logic_func_mode, logic_wdt->feed_dev_name,
|
||
|
logic_wdt->feed_reg, &logic_wdt->state_val, ONE_BYTE);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&pdev->dev, "set wdt control reg error.\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int rg_wdt_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
WDT_VERBOSE("enter remove wdt.\n");
|
||
|
unregister_action(pdev);
|
||
|
dev_info(&pdev->dev, "remove wdt finish.\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void rg_wdt_shutdown(struct platform_device *pdev)
|
||
|
{
|
||
|
WDT_VERBOSE("enter shutdown wdt.\n");
|
||
|
unregister_action(pdev);
|
||
|
dev_info(&pdev->dev, "shutdown wdt finish.\n");
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id rg_wdt_dt_ids[] = {
|
||
|
{ .compatible = "ruijie,rg_wdt", },
|
||
|
{ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, rg_wdt_dt_ids);
|
||
|
|
||
|
static struct platform_driver rg_wdt_driver = {
|
||
|
.driver = {
|
||
|
.name = "rg_wdt",
|
||
|
.of_match_table = rg_wdt_dt_ids,
|
||
|
},
|
||
|
.probe = rg_wdt_probe,
|
||
|
.remove = rg_wdt_remove,
|
||
|
.shutdown = rg_wdt_shutdown,
|
||
|
};
|
||
|
|
||
|
#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
|
||
|
static int __init rg_wdt_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&rg_wdt_driver);
|
||
|
}
|
||
|
arch_initcall(rg_wdt_init);
|
||
|
#else
|
||
|
module_platform_driver(rg_wdt_driver);
|
||
|
#endif
|
||
|
|
||
|
MODULE_AUTHOR("sonic_rd <sonic_rd@ruijie.com.cn>");
|
||
|
MODULE_DESCRIPTION("watchdog driver");
|
||
|
MODULE_LICENSE("GPL");
|