blob: ee9ec4cdc3a4f586dfed830d4d4e35dab9ab4ffc [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Starfive Watchdog driver
*
* Copyright (C) 2022 StarFive Technology Co., Ltd.
*/
#include <clk.h>
#include <dm.h>
#include <reset.h>
#include <wdt.h>
#include <linux/iopoll.h>
/* JH7110 Watchdog register define */
#define STARFIVE_WDT_JH7110_LOAD 0x000
#define STARFIVE_WDT_JH7110_VALUE 0x004
#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
* [0]: reset enable;
* [1]: interrupt enable && watchdog enable
* [31:2]: reserved.
*/
#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
#define STARFIVE_WDT_JH7110_IMS 0x014
#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
/* WDOGCONTROL */
#define STARFIVE_WDT_ENABLE 0x1
#define STARFIVE_WDT_EN_SHIFT 0
#define STARFIVE_WDT_RESET_EN 0x1
#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
/* WDOGLOCK */
#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
/* WDOGINTCLR */
#define STARFIVE_WDT_INTCLR 0x1
#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
#define STARFIVE_WDT_MAXCNT 0xffffffff
#define STARFIVE_WDT_DEFAULT_TIME (15)
#define STARFIVE_WDT_DELAY_US 0
#define STARFIVE_WDT_TIMEOUT_US 10000
/* module parameter */
#define STARFIVE_WDT_EARLY_ENA 0
struct starfive_wdt_variant {
unsigned int control; /* Watchdog Control Resgister for reset enable */
unsigned int load; /* Watchdog Load register */
unsigned int reload; /* Watchdog Reload Control register */
unsigned int enable; /* Watchdog Enable Register */
unsigned int value; /* Watchdog Counter Value Register */
unsigned int int_clr; /* Watchdog Interrupt Clear Register */
unsigned int unlock; /* Watchdog Lock Register */
unsigned int int_status; /* Watchdog Interrupt Status Register */
u32 unlock_key;
char enrst_shift;
char en_shift;
bool intclr_check; /* whether need to check it before clearing interrupt */
char intclr_ava_shift;
bool double_timeout; /* The watchdog need twice timeout to reboot */
};
struct starfive_wdt_priv {
void __iomem *base;
struct clk *core_clk;
struct clk *apb_clk;
struct reset_ctl_bulk *rst;
const struct starfive_wdt_variant *variant;
unsigned long freq;
u32 count; /* count of timeout */
u32 reload; /* restore the count */
};
/* Register layout and configuration for the JH7110 */
static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
.control = STARFIVE_WDT_JH7110_CONTROL,
.load = STARFIVE_WDT_JH7110_LOAD,
.enable = STARFIVE_WDT_JH7110_CONTROL,
.value = STARFIVE_WDT_JH7110_VALUE,
.int_clr = STARFIVE_WDT_JH7110_INTCLR,
.unlock = STARFIVE_WDT_JH7110_LOCK,
.unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
.int_status = STARFIVE_WDT_JH7110_IMS,
.enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
.en_shift = STARFIVE_WDT_EN_SHIFT,
.intclr_check = false,
.double_timeout = true,
};
static int starfive_wdt_enable_clock(struct starfive_wdt_priv *wdt)
{
int ret;
ret = clk_enable(wdt->apb_clk);
if (ret)
return ret;
ret = clk_enable(wdt->core_clk);
if (ret) {
clk_disable(wdt->apb_clk);
return ret;
}
return 0;
}
static void starfive_wdt_disable_clock(struct starfive_wdt_priv *wdt)
{
clk_disable(wdt->core_clk);
clk_disable(wdt->apb_clk);
}
/* Write unlock-key to unlock. Write other value to lock. */
static void starfive_wdt_unlock(struct starfive_wdt_priv *wdt)
{
writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
}
static void starfive_wdt_lock(struct starfive_wdt_priv *wdt)
{
writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
}
/* enable watchdog interrupt to reset/reboot */
static void starfive_wdt_enable_reset(struct starfive_wdt_priv *wdt)
{
u32 val;
val = readl(wdt->base + wdt->variant->control);
val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
writel(val, wdt->base + wdt->variant->control);
}
/* waiting interrupt can be free to clear */
static int starfive_wdt_wait_int_free(struct starfive_wdt_priv *wdt)
{
u32 value;
return readl_poll_timeout(wdt->base + wdt->variant->int_clr, value,
!(value & BIT(wdt->variant->intclr_ava_shift)),
STARFIVE_WDT_TIMEOUT_US);
}
/* clear interrupt signal before initialization or reload */
static int starfive_wdt_int_clr(struct starfive_wdt_priv *wdt)
{
int ret;
if (wdt->variant->intclr_check) {
ret = starfive_wdt_wait_int_free(wdt);
if (ret)
return ret;
}
writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
return 0;
}
static inline void starfive_wdt_set_count(struct starfive_wdt_priv *wdt,
u32 val)
{
writel(val, wdt->base + wdt->variant->load);
}
/* enable watchdog */
static inline void starfive_wdt_enable(struct starfive_wdt_priv *wdt)
{
u32 val;
val = readl(wdt->base + wdt->variant->enable);
val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
writel(val, wdt->base + wdt->variant->enable);
}
/* disable watchdog */
static inline void starfive_wdt_disable(struct starfive_wdt_priv *wdt)
{
u32 val;
val = readl(wdt->base + wdt->variant->enable);
val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
writel(val, wdt->base + wdt->variant->enable);
}
static inline void starfive_wdt_set_reload_count(struct starfive_wdt_priv *wdt,
u32 count)
{
starfive_wdt_set_count(wdt, count);
/* 7100 need set any value to reload register and could reload value to counter */
if (wdt->variant->reload)
writel(0x1, wdt->base + wdt->variant->reload);
}
static int starfive_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
{
int ret;
struct starfive_wdt_priv *wdt = dev_get_priv(dev);
starfive_wdt_unlock(wdt);
/* disable watchdog, to be safe */
starfive_wdt_disable(wdt);
starfive_wdt_enable_reset(wdt);
ret = starfive_wdt_int_clr(wdt);
if (ret)
goto exit;
wdt->count = (timeout_ms / 1000) * wdt->freq;
if (wdt->variant->double_timeout)
wdt->count /= 2;
starfive_wdt_set_count(wdt, wdt->count);
starfive_wdt_enable(wdt);
exit:
starfive_wdt_lock(wdt);
return ret;
}
static int starfive_wdt_stop(struct udevice *dev)
{
struct starfive_wdt_priv *wdt = dev_get_priv(dev);
starfive_wdt_unlock(wdt);
starfive_wdt_disable(wdt);
starfive_wdt_lock(wdt);
return 0;
}
static int starfive_wdt_reset(struct udevice *dev)
{
int ret;
struct starfive_wdt_priv *wdt = dev_get_priv(dev);
starfive_wdt_unlock(wdt);
ret = starfive_wdt_int_clr(wdt);
if (ret)
goto exit;
starfive_wdt_set_reload_count(wdt, wdt->count);
exit:
starfive_wdt_lock(wdt);
return ret;
}
static const struct wdt_ops starfive_wdt_ops = {
.start = starfive_wdt_start,
.stop = starfive_wdt_stop,
.reset = starfive_wdt_reset,
};
static int starfive_wdt_probe(struct udevice *dev)
{
struct starfive_wdt_priv *wdt = dev_get_priv(dev);
int ret;
ret = starfive_wdt_enable_clock(wdt);
if (ret)
return ret;
ret = reset_deassert_bulk(wdt->rst);
if (ret)
goto err_reset;
wdt->variant = (const struct starfive_wdt_variant *)dev_get_driver_data(dev);
wdt->freq = clk_get_rate(wdt->core_clk);
if (!wdt->freq) {
ret = -EINVAL;
goto err_get_freq;
}
return 0;
err_get_freq:
reset_assert_bulk(wdt->rst);
err_reset:
starfive_wdt_disable_clock(wdt);
return ret;
}
static int starfive_wdt_of_to_plat(struct udevice *dev)
{
struct starfive_wdt_priv *wdt = dev_get_priv(dev);
wdt->base = (void *)dev_read_addr(dev);
if (!wdt->base)
return -ENODEV;
wdt->apb_clk = devm_clk_get(dev, "apb");
if (IS_ERR(wdt->apb_clk))
return -ENODEV;
wdt->core_clk = devm_clk_get(dev, "core");
if (IS_ERR(wdt->core_clk))
return -ENODEV;
wdt->rst = devm_reset_bulk_get(dev);
if (IS_ERR(wdt->rst))
return -ENODEV;
return 0;
}
static const struct udevice_id starfive_wdt_ids[] = {
{
.compatible = "starfive,jh7110-wdt",
.data = (ulong)&starfive_wdt_jh7110_variant
}, {
/* sentinel */
}
};
U_BOOT_DRIVER(starfive_wdt) = {
.name = "starfive_wdt",
.id = UCLASS_WDT,
.of_match = starfive_wdt_ids,
.priv_auto = sizeof(struct starfive_wdt_priv),
.probe = starfive_wdt_probe,
.of_to_plat = starfive_wdt_of_to_plat,
.ops = &starfive_wdt_ops,
};