sonic-buildimage/platform/broadcom/sonic-platform-modules-tencent/common/modules/rg_wdt.c

1042 lines
30 KiB
C
Raw Normal View History

/*
* 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");