blob: 3255f3affffe83626a9fb7f3074245a1a105b6e5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Synaptics Incorporated
*
* Author: Jisheng Zhang <jszhang@kernel.org>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/watchdog.h>
#define CONTROL 0x00
#define WD_CONTROL 0x04
#define INT_STAT 0x08
#define TERMINAL_COUNT 0x0c
#define RESET_TERMINAL_COUNT 0x10
#define WD_CMD_WINDOWN 0x14
#define CURRENT_COUNT 0x18
struct as390_wdt {
void __iomem *base;
unsigned long rate;
struct watchdog_device wdd;
struct reset_control *rst;
struct clk *clk;
};
static inline u32 rdl(struct as390_wdt *wdt, u32 offset)
{
return readl_relaxed(wdt->base + offset);
}
static inline void wrl(struct as390_wdt *wdt, u32 offset, u32 data)
{
writel_relaxed(data, wdt->base + offset);
}
static int as390_wdt_ping(struct watchdog_device *wdog)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
local_irq_disable();
wrl(wdt, WD_CONTROL, 0xA5FF3210);
wrl(wdt, WD_CONTROL, 0x44BB77DD);
local_irq_enable();
return 0;
}
static int as390_wdt_stop(struct watchdog_device *wdog)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
local_irq_disable();
writel_relaxed(0x0123a5ff, wdt->base + WD_CONTROL);
writel_relaxed(0xccee6699, wdt->base + WD_CONTROL);
local_irq_enable();
return 0;
}
static void as390_wdt_set(struct as390_wdt *wdt, u32 ticks)
{
u32 val;
val = rdl(wdt, CONTROL);
if (val & (1 << 1)) {
as390_wdt_stop(&wdt->wdd);
reset_control_reset(wdt->rst);
}
val = 1 << 0;
wrl(wdt, CONTROL, val);
if (ticks <= 5000)
ticks = 5000 + 1;
wrl(wdt, TERMINAL_COUNT, ticks);
val |= (1 << 27);
val |= (1 << 6);
val |= (1 << 5);
val |= (1 << 4);
wrl(wdt, CONTROL, val);
wrl(wdt, RESET_TERMINAL_COUNT, 5000);
wrl(wdt, WD_CMD_WINDOWN, 0xffff);
val |= (1 << 1);
wrl(wdt, CONTROL, val);
}
static int as390_wdt_restart(struct watchdog_device *wdog, unsigned long action,
void *data)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
as390_wdt_set(wdt, 1);
/* wait for reset to assert... */
mdelay(500);
return 0;
}
static int as390_wdt_start(struct watchdog_device *wdog)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
as390_wdt_set(wdt, wdog->timeout * wdt->rate);
return 0;
}
static int as390_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
as390_wdt_set(wdt, t * wdt->rate);
wdog->timeout = t;
return 0;
}
static unsigned int as390_wdt_get_timeleft(struct watchdog_device *wdog)
{
struct as390_wdt *wdt = watchdog_get_drvdata(wdog);
u32 count = rdl(wdt, TERMINAL_COUNT) - rdl(wdt, CURRENT_COUNT);
return count / wdt->rate;
}
static irqreturn_t as390_wdt_irq_handler(int irq, void *dev_id)
{
return IRQ_HANDLED;
}
static const struct watchdog_ops as390_wdt_ops = {
.owner = THIS_MODULE,
.start = as390_wdt_start,
.stop = as390_wdt_stop,
.ping = as390_wdt_ping,
.set_timeout = as390_wdt_set_timeout,
.get_timeleft = as390_wdt_get_timeleft,
.restart = as390_wdt_restart,
};
static const struct watchdog_info as390_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE
| WDIOF_KEEPALIVEPING,
.identity = "Synaptics AS390 Watchdog",
};
static int as390_wdt_probe(struct platform_device *pdev)
{
int ret, irq;
struct resource *res;
struct as390_wdt *wdt;
struct watchdog_device *wdd;
struct device *dev = &pdev->dev;
wdt = devm_kzalloc(dev, sizeof(struct as390_wdt), GFP_KERNEL);
if (!wdt)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
wdt->base = devm_ioremap_resource(dev, res);
if (IS_ERR(wdt->base))
return PTR_ERR(wdt->base);
wdt->rst = devm_reset_control_get_optional(&pdev->dev, NULL);
if (IS_ERR(wdt->rst))
return PTR_ERR(wdt->rst);
wdt->clk = devm_clk_get(dev, NULL);
if (IS_ERR(wdt->clk))
return PTR_ERR(wdt->clk);
clk_prepare_enable(wdt->clk);
wdd = &wdt->wdd;
wdd->info = &as390_wdt_info;
wdd->ops = &as390_wdt_ops;
wdt->rate = clk_get_rate(wdt->clk);
wdd->max_timeout = U32_MAX / wdt->rate;
wdd->timeout = wdd->max_timeout;
wdd->parent = dev;
wdd->min_timeout = 1;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto out_disable_clk;
}
ret = devm_request_irq(dev, irq, as390_wdt_irq_handler, 0,
pdev->name, wdt);
if (ret)
goto out_disable_clk;
watchdog_set_drvdata(wdd, wdt);
watchdog_set_restart_priority(wdd, 128);
ret = devm_watchdog_register_device(dev, wdd);
if (ret)
goto out_disable_clk;
return 0;
out_disable_clk:
clk_disable_unprepare(wdt->clk);
return ret;
}
static const struct of_device_id as390_wdt_of_match[] = {
{ .compatible = "syna,as390-wdt", },
{},
};
MODULE_DEVICE_TABLE(of, as390_wdt_of_match);
static struct platform_driver as390_wdt_driver = {
.probe = as390_wdt_probe,
.driver = {
.name = "as390_wdt",
.of_match_table = as390_wdt_of_match,
},
};
module_platform_driver(as390_wdt_driver);
MODULE_AUTHOR("Jisheng Zhang<jszhang@kernel.org>");
MODULE_DESCRIPTION("Synaptics AS390 Watchdog Driver");
MODULE_LICENSE("GPL v2");