ckfwq/linux-3.0.4/drivers/watchdog/gsc3280_wdt.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");