blob: 39fb9768ccfe675d24e8e7880c2c10669ea73bf1 [file] [log] [blame]
/*
* linux/drivers/mmc/host/ambarella_sd.c
*
* Copyright (C) 2006-2007, Ambarella, Inc.
* Anthony Ginger, <hfjiang@ambarella.com>
*
* Ambarella Media Processor Watch Dog Timer
*
*/
#include <linux/module.h>
#include <linux/watchdog.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <plat/wdt.h>
#include <plat/rct.h>
#define AMBARELLA_WDT_MAX_CYCLE 0xffffffff
static int heartbeat = -1; /* -1 means get value from FDT */
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
struct ambarella_wdt {
void __iomem *regbase;
struct clk *clk;
int irq;
struct watchdog_device wdd;
bool enabled;
u32 timeout; /* in cycle */
};
static int ambarella_wdt_start(struct watchdog_device *wdd)
{
struct ambarella_wdt *ambwdt;
u32 ctrl_val;
ambwdt = watchdog_get_drvdata(wdd);
ambwdt->enabled = true;
ctrl_val = ambwdt->irq > 0 ? WDOG_CTR_INT_EN : WDOG_CTR_RST_EN;
ctrl_val |= WDOG_CTR_EN;
amba_writel(ambwdt->regbase + WDOG_CONTROL_OFFSET, ctrl_val);
return 0;
}
static int ambarella_wdt_stop(struct watchdog_device *wdd)
{
struct ambarella_wdt *ambwdt;
ambwdt = watchdog_get_drvdata(wdd);
ambwdt->enabled = false;
amba_writel(ambwdt->regbase + WDOG_CONTROL_OFFSET, 0);
return 0;
}
static int ambarella_wdt_keepalive(struct watchdog_device *wdd)
{
struct ambarella_wdt *ambwdt = watchdog_get_drvdata(wdd);
amba_writel(ambwdt->regbase + WDOG_RELOAD_OFFSET, ambwdt->timeout);
amba_writel(ambwdt->regbase + WDOG_RESTART_OFFSET, WDT_RESTART_VAL);
return 0;
}
static int ambarella_wdt_set_timeout(struct watchdog_device *wdd, u32 time)
{
struct ambarella_wdt *ambwdt = watchdog_get_drvdata(wdd);
u32 freq;
wdd->timeout = time;
freq = clk_get_rate(clk_get(NULL, "gclk_apb"));
ambwdt->timeout = time * freq;
amba_writel(ambwdt->regbase + WDOG_RELOAD_OFFSET, ambwdt->timeout);
return 0;
}
static irqreturn_t ambarella_wdt_irq(int irq, void *devid)
{
struct ambarella_wdt *ambwdt = devid;
amba_writel(ambwdt->regbase + WDOG_CLR_TMO_OFFSET, 0x01);
dev_info(ambwdt->wdd.dev, "Watchdog timer expired!\n");
return IRQ_HANDLED;
}
static const struct watchdog_info ambwdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.identity = "Ambarella Watchdog",
};
static const struct watchdog_ops ambwdt_ops = {
.owner = THIS_MODULE,
.start = ambarella_wdt_start,
.stop = ambarella_wdt_stop,
.ping = ambarella_wdt_keepalive,
.set_timeout = ambarella_wdt_set_timeout,
};
static int ambarella_wdt_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct ambarella_wdt *ambwdt;
struct resource *mem;
void __iomem *reg;
int max_timeout, rval = 0;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL) {
dev_err(&pdev->dev, "Get WDT mem resource failed!\n");
return -ENXIO;
}
reg = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
if (!reg) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
return -ENOMEM;
}
ambwdt = devm_kzalloc(&pdev->dev, sizeof(*ambwdt), GFP_KERNEL);
if (ambwdt == NULL) {
dev_err(&pdev->dev, "Out of memory!\n");
return -ENOMEM;
}
ambwdt->regbase = reg;
/* irq < 0 means to disable interrupt mode */
ambwdt->irq = platform_get_irq(pdev, 0);
if (ambwdt->irq > 0) {
rval = devm_request_irq(&pdev->dev, ambwdt->irq,
ambarella_wdt_irq, IRQF_TRIGGER_RISING,
dev_name(&pdev->dev), ambwdt);
if (rval < 0) {
dev_err(&pdev->dev, "Request IRQ failed!\n");
return -ENXIO;
}
}
ambwdt->clk = clk_get(NULL, "gclk_apb");
if (IS_ERR(ambwdt->clk)) {
dev_err(&pdev->dev, "no clk for wdt!\n");
return -ENODEV;
}
max_timeout = AMBARELLA_WDT_MAX_CYCLE / clk_get_rate(ambwdt->clk);
ambwdt->wdd.info = &ambwdt_info;
ambwdt->wdd.ops = &ambwdt_ops;
ambwdt->wdd.min_timeout = 0;
ambwdt->wdd.max_timeout = max_timeout;
ambwdt->wdd.bootstatus = amba_readl(WDT_RST_L_REG) ? 0 : WDIOF_CARDRESET;
if (!of_find_property(np, "amb,non-bootstatus", NULL)) {
/* WDT_RST_L_REG cannot be restored by "reboot" command,
* so reset it manually */
amba_setbitsl(UNLOCK_WDT_RST_L_REG, UNLOCK_WDT_RST_L_VAL);
amba_writel(WDT_RST_L_REG, 0x1);
amba_clrbitsl(UNLOCK_WDT_RST_L_REG, UNLOCK_WDT_RST_L_VAL);
}
watchdog_init_timeout(&ambwdt->wdd, heartbeat, &pdev->dev);
watchdog_set_nowayout(&ambwdt->wdd, nowayout);
watchdog_set_drvdata(&ambwdt->wdd, ambwdt);
ambarella_wdt_set_timeout(&ambwdt->wdd, ambwdt->wdd.timeout);
ambarella_wdt_stop(&ambwdt->wdd);
rval = watchdog_register_device(&ambwdt->wdd);
if (rval < 0) {
dev_err(&pdev->dev, "failed to register wdt!\n");
return rval;
}
platform_set_drvdata(pdev, ambwdt);
dev_notice(&pdev->dev, "Ambarella Watchdog Timer Probed.\n");
return 0;
}
static int ambarella_wdt_remove(struct platform_device *pdev)
{
struct ambarella_wdt *ambwdt;
int rval = 0;
ambwdt = platform_get_drvdata(pdev);
ambarella_wdt_stop(&ambwdt->wdd);
watchdog_unregister_device(&ambwdt->wdd);
dev_notice(&pdev->dev, "Remove Ambarella Watchdog Timer.\n");
return rval;
}
static void ambarella_wdt_shutdown(struct platform_device *pdev)
{
struct ambarella_wdt *ambwdt;
ambwdt = platform_get_drvdata(pdev);
ambarella_wdt_stop(&ambwdt->wdd);
dev_info(&pdev->dev, "%s @ %d.\n", __func__, system_state);
}
#ifdef CONFIG_PM
static int ambarella_wdt_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct ambarella_wdt *ambwdt;
int rval = 0;
ambwdt = platform_get_drvdata(pdev);
if (watchdog_active(&ambwdt->wdd))
ambarella_wdt_stop(&ambwdt->wdd);
dev_dbg(&pdev->dev, "%s exit with %d @ %d\n",
__func__, rval, state.event);
return rval;
}
static int ambarella_wdt_resume(struct platform_device *pdev)
{
struct ambarella_wdt *ambwdt;
int rval = 0;
ambwdt = platform_get_drvdata(pdev);
if (watchdog_active(&ambwdt->wdd)) {
ambarella_wdt_start(&ambwdt->wdd);
ambarella_wdt_keepalive(&ambwdt->wdd);
}
dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, rval);
return rval;
}
#endif
static const struct of_device_id ambarella_wdt_dt_ids[] = {
{.compatible = "ambarella,wdt", },
{},
};
MODULE_DEVICE_TABLE(of, ambarella_wdt_dt_ids);
static struct platform_driver ambarella_wdt_driver = {
.probe = ambarella_wdt_probe,
.remove = ambarella_wdt_remove,
.shutdown = ambarella_wdt_shutdown,
#ifdef CONFIG_PM
.suspend = ambarella_wdt_suspend,
.resume = ambarella_wdt_resume,
#endif
.driver = {
.owner = THIS_MODULE,
.name = "ambarella-wdt",
.of_match_table = ambarella_wdt_dt_ids,
},
};
module_platform_driver(ambarella_wdt_driver);
MODULE_DESCRIPTION("Ambarella Media Processor Watch Dog Timer");
MODULE_AUTHOR("Anthony Ginger, <hfjiang@ambarella.com>");
MODULE_LICENSE("GPL");