461 lines
12 KiB
C
461 lines
12 KiB
C
/*
|
|
* GSC3280 SoC WATCHDOG Controller Driver
|
|
*
|
|
* Copyright (C) 2013 BLX IC Design Corp.,Ltd.
|
|
* Author: Davied, apple_guet@126.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* the formulate is pclkOne = T*55555555/2(tor + 16)
|
|
*
|
|
* the watchdog default T = 9.66s,tor = 0x05, pclkOne = 0x08
|
|
*
|
|
* torr pclkOne T(s) Set value
|
|
* 0x04 0x08 4.83 0x48
|
|
* 0x05 0x08 9.66 0x58
|
|
* 0x06 0x08 19.33 0x68
|
|
*
|
|
*/
|
|
#include <linux/io.h>
|
|
#include <linux/time.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/clock.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/platform_device.h>
|
|
#include <gsc3280/gsc3280_regs.h>
|
|
|
|
#include "gsc3280_wdt.h"
|
|
|
|
|
|
#ifdef GSC3280_WDT_DEBUG
|
|
#define DBG(msg...) do { \
|
|
printk(KERN_INFO msg); \
|
|
} while (0)
|
|
#else
|
|
#define DBG(msg...) do { } while(0)
|
|
#endif
|
|
|
|
|
|
static LIST_HEAD(wdt_device_list);
|
|
static DEFINE_MUTEX(wdt_device_list_lock);
|
|
|
|
|
|
static void gsc3280wdt_keepalive(struct gsc3280_wdt *wdt)
|
|
{
|
|
spin_lock(&wdt->wdt_lock);
|
|
writel(WDT_FEED_VALUE, wdt->wdt_base + WDT_CRR);
|
|
spin_unlock(&wdt->wdt_lock);
|
|
DBG("feed watchdog\n");
|
|
}
|
|
|
|
static void gsc3280wdt_stop(struct gsc3280_wdt *wdt)
|
|
{
|
|
unsigned int tmp_value = 0;
|
|
|
|
spin_lock(&wdt->wdt_lock);
|
|
tmp_value = readl(GSC3280_WDT_SYS_CFG);
|
|
writel(tmp_value | GSC3280_WDT_SYS_CFG_PAUSE, GSC3280_WDT_SYS_CFG);
|
|
spin_unlock(&wdt->wdt_lock);
|
|
}
|
|
|
|
static int gsc3280_watchdog_init(struct gsc3280_wdt *wdt)
|
|
{
|
|
unsigned int tmp_value = 0;
|
|
|
|
DBG("Enter watchdog_init\n");
|
|
spin_lock(&wdt->wdt_lock);
|
|
tmp_value = readl(GSC3280_WDT_SYS_CFG);
|
|
writel((tmp_value & 0xf0) | wdt->pclkOne, GSC3280_WDT_SYS_CFG);
|
|
tmp_value = readl(GSC3280_WDT_SYS_CFG);
|
|
DBG("wdt_pclk = %x\n", tmp_value & 0x0f);
|
|
writel(wdt->torr & 0xffff , wdt->wdt_base + WDT_TORR);
|
|
//0x1d:reset directly, 0x1f: reset after interrupt
|
|
writel(wdt->wdt_cr & 0x1d, wdt->wdt_base + WDT_CR);
|
|
DBG("wdt_ccvr = %x\n", readl(wdt->wdt_base + WDT_CCVR));
|
|
DBG("wdt_cr = %x\n", readl(wdt->wdt_base + WDT_CR));
|
|
|
|
tmp_value = readl(GSC3280_WDT_SYS_CFG);
|
|
writel(tmp_value & ~GSC3280_WDT_SYS_CFG_PAUSE, GSC3280_WDT_SYS_CFG); //start watchdog
|
|
spin_unlock(&wdt->wdt_lock);
|
|
return 0;
|
|
}
|
|
|
|
static void gsc3280_watchdog_enable(struct gsc3280_wdt *wdt)
|
|
{
|
|
spin_lock(&wdt->wdt_lock);
|
|
//0x1d:reset directly, 0x1f: reset after interrupt
|
|
writel(0x1d, wdt->wdt_base + WDT_CR);
|
|
spin_unlock(&wdt->wdt_lock);
|
|
}
|
|
|
|
static void gsc3280wdt_start(struct gsc3280_wdt *wdt)
|
|
{
|
|
DBG("watchdog start\n");
|
|
wdt->wdt_cr = 0x1f;
|
|
wdt->torr = 0x05;
|
|
wdt->pclkOne = 0x08;
|
|
gsc3280_watchdog_init(wdt);
|
|
gsc3280_watchdog_enable(wdt);
|
|
gsc3280wdt_keepalive(wdt);
|
|
}
|
|
|
|
static void gsc3280wdt_set_par(struct gsc3280_wdt *wdt)
|
|
{
|
|
unsigned int tmp_value = 0;
|
|
|
|
spin_lock(&wdt->wdt_lock);
|
|
tmp_value = readl(GSC3280_WDT_SYS_CFG);
|
|
writel((tmp_value & 0xf0) | wdt->pclkOne, GSC3280_WDT_SYS_CFG);
|
|
DBG("sys_wdt_cfg = %x\n", readl(GSC3280_WDT_SYS_CFG));
|
|
|
|
writel(wdt->torr, wdt->wdt_base + WDT_TORR);
|
|
DBG("wdt_torr = %x\n", readl(wdt->wdt_base + WDT_TORR));
|
|
spin_unlock(&wdt->wdt_lock);
|
|
}
|
|
|
|
static int gsc3280wdt_read_par(struct gsc3280_wdt *wdt)
|
|
{
|
|
unsigned int cfg = 0, torr = 0;
|
|
cfg = readl(GSC3280_WDT_SYS_CFG) & 0x000f;
|
|
torr = readl(wdt->wdt_base + WDT_TORR);
|
|
cfg |= (torr << 4) & 0xfffffff0;
|
|
|
|
DBG("%x\n", cfg);
|
|
return cfg;
|
|
}
|
|
|
|
static int gsc3280wdt_open(struct inode *inode, struct file *file)
|
|
{
|
|
int status = -ENXIO;
|
|
struct gsc3280_wdt *wdt;
|
|
|
|
mutex_lock(&wdt_device_list_lock);
|
|
list_for_each_entry(wdt, &wdt_device_list, device_entry) {
|
|
if(strcmp(wdt->name, GSC3280_WDT_NAME) == 0) {
|
|
status = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (status == 0) {
|
|
file->private_data = wdt;
|
|
}
|
|
mutex_unlock(&wdt_device_list_lock);
|
|
if (test_and_set_bit(0, &wdt->open_lock)) {
|
|
DBG("ERROR:gsc3280 open err, busy\n");
|
|
return -EBUSY;
|
|
}
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static int gsc3280wdt_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct gsc3280_wdt *wdt = file->private_data;
|
|
|
|
gsc3280wdt_stop(wdt);
|
|
clear_bit(0, &wdt->open_lock);
|
|
DBG("gsc3280 release ok\n");
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t gsc3280wdt_write(struct file *file, const char __user *data,
|
|
size_t len, loff_t *ppos)
|
|
{
|
|
//struct gsc3280_wdt *wdt = file->private_data;
|
|
DBG("This is in write\n");
|
|
return 0;
|
|
}
|
|
|
|
static void ReadDebug(struct gsc3280_wdt *wdt)
|
|
{
|
|
unsigned int tmp_value = 0;
|
|
|
|
DBG("read register\n");
|
|
tmp_value = readl((unsigned int *)(GSC3280_SYSCTL_BASEADDR + SYS_WDT_EN_OFFSET));
|
|
DBG("sys_wdt_en = %x\n", tmp_value & 0x04000000);
|
|
DBG("sys_wdt_cfg = %x\n", readl(GSC3280_WDT_SYS_CFG));
|
|
DBG("ccvr = %x\n", readl(wdt->wdt_base + WDT_CCVR));
|
|
DBG("cr = %x\n", readl(wdt->wdt_base + WDT_CR));
|
|
DBG("torr = %x\n", readl(wdt->wdt_base + WDT_TORR));
|
|
printk(KERN_INFO "clk rate is %ld\n", clk_get_rate(wdt->clk));
|
|
}
|
|
|
|
static long gsc3280wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int new_margin;
|
|
void __user *argp = (void __user *)arg;
|
|
int __user *p = argp;
|
|
struct gsc3280_wdt *wdt;
|
|
|
|
if (_IOC_TYPE(cmd) != WDT_IOC_MAGIC)
|
|
return -ENOTTY;
|
|
if (_IOC_NR(cmd) > WDT_IOC_MAXNR)
|
|
return -ENOTTY;
|
|
|
|
wdt = file->private_data;
|
|
switch (cmd) {
|
|
case WDT_START:
|
|
gsc3280wdt_start(wdt);
|
|
return 0;
|
|
case WDT_KEEPALIVE:
|
|
gsc3280wdt_keepalive(wdt);
|
|
return 0;
|
|
case WDT_PAUSE:
|
|
gsc3280wdt_stop(wdt);
|
|
return 0;
|
|
case WDT_READ:
|
|
ReadDebug(wdt);
|
|
return 0;
|
|
case WDT_SETTIMEOUT:
|
|
if (get_user(new_margin, p))
|
|
return -EFAULT;
|
|
wdt->pclkOne= new_margin & 0x000f;
|
|
wdt->torr = (new_margin >> 4) & 0xffff;
|
|
gsc3280wdt_set_par(wdt);
|
|
gsc3280wdt_keepalive(wdt);
|
|
return 0;
|
|
case WDT_GETTIMEOUT:
|
|
return put_user(gsc3280wdt_read_par(wdt), p);
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
static const struct file_operations gsc3280wdt_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.write = gsc3280wdt_write,
|
|
.unlocked_ioctl = gsc3280wdt_ioctl,
|
|
.open = gsc3280wdt_open,
|
|
.release = gsc3280wdt_release,
|
|
};
|
|
static struct miscdevice gsc3280wdt_miscdev = {
|
|
.minor = WATCHDOG_MINOR,
|
|
.name = "watchdog",
|
|
.fops = &gsc3280wdt_fops,
|
|
};
|
|
|
|
static irqreturn_t gsc3280wdt_irq(int irqno, void *param)
|
|
{
|
|
#if 0
|
|
struct gsc3280_wdt *wdt = param;
|
|
dev_info(wdt->dev, "watchdog timer expired (irq)\n");
|
|
gsc3280wdt_keepalive(wdt);
|
|
#endif
|
|
|
|
DBG("This is in irq handle\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int __devinit gsc3280wdt_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0, size = 0;
|
|
struct gsc3280_wdt *gsc = NULL;
|
|
|
|
DBG("############\n");
|
|
printk(KERN_INFO "gsc3280 watchdog probe start\n");
|
|
gsc = kzalloc(sizeof(struct gsc3280_wdt), GFP_KERNEL);
|
|
if (!gsc) {
|
|
DBG("no memory error!!!!\n");
|
|
return -ENOMEM;
|
|
}
|
|
/* Initialize the driver data */
|
|
gsc->suspend_flg = 0;
|
|
gsc->dev = &pdev->dev;
|
|
spin_lock_init(&gsc->wdt_lock);
|
|
INIT_LIST_HEAD(&gsc->device_entry);
|
|
strlcpy(gsc->name, GSC3280_WDT_NAME, sizeof(gsc->name));
|
|
/* get the memory region for the watchdog timer */
|
|
gsc->wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (gsc->wdt_mem == NULL) {
|
|
DBG("no memory resource specified!!!!\n");
|
|
ret = -ENOENT;
|
|
goto free;
|
|
}
|
|
size = resource_size(gsc->wdt_mem);
|
|
if (!request_mem_region(gsc->wdt_mem->start, size, pdev->name)) {
|
|
DBG("failed to get memory region!!!!\n");
|
|
ret = -EBUSY;
|
|
goto free;
|
|
}
|
|
gsc->wdt_base = ioremap(gsc->wdt_mem->start, size);
|
|
if (gsc->wdt_base == NULL) {
|
|
DBG("failed to ioremap() region!!!!\n");
|
|
ret = -EINVAL;
|
|
goto err_req;
|
|
}
|
|
DBG("probe: mapped wdt_base=%p\n", gsc->wdt_base);
|
|
|
|
gsc->wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (gsc->wdt_irq == NULL) {
|
|
DBG("no irq resource specified!!!!\n");
|
|
ret = -ENOENT;
|
|
goto err_map;
|
|
}
|
|
ret = request_irq(gsc->wdt_irq->start, gsc3280wdt_irq, 0, pdev->name, gsc);
|
|
if (ret != 0) {
|
|
DBG("failed to install irq (%d)!!!!\n", ret);
|
|
goto err_map;
|
|
}
|
|
|
|
gsc->clk = clk_get(NULL, "wdt");
|
|
if (IS_ERR(gsc->clk)) {
|
|
DBG("failed to find watchdog clock source!!!!\n");
|
|
ret = PTR_ERR(gsc->clk);
|
|
goto err_irq;
|
|
}
|
|
DBG("clk rate is %ld\n", clk_get_rate(gsc->clk));
|
|
clk_enable(gsc->clk);
|
|
ret = misc_register(&gsc3280wdt_miscdev);
|
|
if(ret < 0) {
|
|
DBG("misc register watchdog error!!!!\n");
|
|
goto err_clk;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
/* set pm runtime power state and register with power system */
|
|
pm_runtime_set_active(gsc->dev);
|
|
pm_runtime_enable(gsc->dev);
|
|
pm_runtime_resume(gsc->dev);
|
|
//device_init_wakeup(gsc->dev, 1);
|
|
#endif
|
|
|
|
mutex_lock(&wdt_device_list_lock);
|
|
list_add(&gsc->device_entry, &wdt_device_list);
|
|
mutex_unlock(&wdt_device_list_lock);
|
|
platform_set_drvdata(pdev, gsc);
|
|
printk(KERN_INFO "gsc3280 watchdog probe success\n");
|
|
DBG("############\n");
|
|
return ret;
|
|
|
|
err_clk:
|
|
clk_disable(gsc->clk);
|
|
clk_put(gsc->clk);
|
|
err_irq:
|
|
free_irq(gsc->wdt_irq->start, pdev);
|
|
err_map:
|
|
iounmap(gsc->wdt_base);
|
|
err_req:
|
|
release_mem_region(gsc->wdt_mem->start, size);
|
|
gsc->wdt_mem = NULL;
|
|
free:
|
|
kfree(gsc);
|
|
|
|
printk(KERN_INFO "!!!!!!gsc3280 watchdog probe err!!!!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit gsc3280wdt_remove(struct platform_device *dev)
|
|
{
|
|
struct gsc3280_wdt *gsc = platform_get_drvdata(dev);
|
|
|
|
misc_deregister(&gsc3280wdt_miscdev);
|
|
clk_disable(gsc->clk);
|
|
clk_put(gsc->clk);
|
|
gsc->clk = NULL;
|
|
free_irq(gsc->wdt_irq->start, dev);
|
|
gsc->wdt_irq = NULL;
|
|
iounmap(gsc->wdt_base);
|
|
release_mem_region(gsc->wdt_mem->start, resource_size(gsc->wdt_mem));
|
|
gsc->wdt_mem = NULL;
|
|
kfree(gsc);
|
|
printk(KERN_INFO "gsc3280 remove ok.\n");
|
|
return 0;
|
|
}
|
|
|
|
static void gsc3280wdt_shutdown(struct platform_device *dev)
|
|
{
|
|
struct gsc3280_wdt *gsc = platform_get_drvdata(dev);
|
|
|
|
gsc3280wdt_stop(gsc);
|
|
printk(KERN_INFO "gsc3280 shutdown ok.\n");
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int gsc3280wdt_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct gsc3280_wdt *gsc = platform_get_drvdata(pdev);
|
|
|
|
printk(KERN_INFO "gsc3280 wdt suspend\n");
|
|
if (gsc->suspend_flg == 1) {
|
|
spin_lock(&gsc->wdt_lock);
|
|
/* Save watchdog state, and turn it off. */
|
|
gsc->pclkOne = readl(GSC3280_WDT_SYS_CFG) & 0x0f;
|
|
gsc->torr = readl(gsc->wdt_base + WDT_TORR) & 0x0000ffff;
|
|
gsc->wdt_cr = readl(gsc->wdt_base + WDT_CR);
|
|
spin_unlock(&gsc->wdt_lock);
|
|
/* Note that WTCNT doesn't need to be saved. */
|
|
gsc3280wdt_stop(gsc);
|
|
clk_disable(gsc->clk);
|
|
} else {
|
|
gsc->suspend_flg = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int gsc3280wdt_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct gsc3280_wdt *gsc = platform_get_drvdata(pdev);
|
|
|
|
printk(KERN_INFO "gsc3280 wdt resume\n");
|
|
clk_enable(gsc->clk);
|
|
gsc3280_watchdog_init(gsc);
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
#define gsc3280wdt_suspend NULL
|
|
#define gsc3280wdt_resume NULL
|
|
#endif //end #ifdef CONFIG_PM
|
|
|
|
|
|
static const struct dev_pm_ops gsc3280wdt_pm_ops = {
|
|
.suspend = gsc3280wdt_suspend,
|
|
.resume = gsc3280wdt_resume,
|
|
.runtime_suspend = gsc3280wdt_suspend,
|
|
.runtime_resume = gsc3280wdt_resume,
|
|
};
|
|
|
|
static struct platform_driver gsc3280wdt_driver = {
|
|
.probe = gsc3280wdt_probe,
|
|
.remove = __devexit_p(gsc3280wdt_remove),
|
|
.shutdown = gsc3280wdt_shutdown,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "gsc3280-watchdog",
|
|
.pm = &gsc3280wdt_pm_ops,
|
|
},
|
|
};
|
|
|
|
static int __init watchdog_init(void)
|
|
{
|
|
return platform_driver_register(&gsc3280wdt_driver);
|
|
}
|
|
static void __exit watchdog_exit(void)
|
|
{
|
|
platform_driver_unregister(&gsc3280wdt_driver);
|
|
}
|
|
module_init(watchdog_init);
|
|
module_exit(watchdog_exit);
|
|
|
|
|
|
MODULE_AUTHOR("Davied<apple_guet@126.com>");
|
|
MODULE_DESCRIPTION("gsc3280 Watchdog Device Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|
|
MODULE_ALIAS("platform:gsc3280-wdt");
|
|
|