cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

meson_gxbb_wdt.c (5770B)


      1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
      2/*
      3 * Copyright (c) 2016 BayLibre, SAS.
      4 * Author: Neil Armstrong <narmstrong@baylibre.com>
      5 *
      6 */
      7#include <linux/clk.h>
      8#include <linux/err.h>
      9#include <linux/io.h>
     10#include <linux/module.h>
     11#include <linux/of.h>
     12#include <linux/platform_device.h>
     13#include <linux/slab.h>
     14#include <linux/types.h>
     15#include <linux/watchdog.h>
     16
     17#define DEFAULT_TIMEOUT	30	/* seconds */
     18
     19#define GXBB_WDT_CTRL_REG			0x0
     20#define GXBB_WDT_TCNT_REG			0x8
     21#define GXBB_WDT_RSET_REG			0xc
     22
     23#define GXBB_WDT_CTRL_CLKDIV_EN			BIT(25)
     24#define GXBB_WDT_CTRL_CLK_EN			BIT(24)
     25#define GXBB_WDT_CTRL_EE_RESET			BIT(21)
     26#define GXBB_WDT_CTRL_EN			BIT(18)
     27#define GXBB_WDT_CTRL_DIV_MASK			(BIT(18) - 1)
     28
     29#define GXBB_WDT_TCNT_SETUP_MASK		(BIT(16) - 1)
     30#define GXBB_WDT_TCNT_CNT_SHIFT			16
     31
     32static bool nowayout = WATCHDOG_NOWAYOUT;
     33module_param(nowayout, bool, 0);
     34MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default="
     35		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     36
     37static unsigned int timeout;
     38module_param(timeout, uint, 0);
     39MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds="
     40		 __MODULE_STRING(DEFAULT_TIMEOUT) ")");
     41
     42struct meson_gxbb_wdt {
     43	void __iomem *reg_base;
     44	struct watchdog_device wdt_dev;
     45	struct clk *clk;
     46};
     47
     48static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
     49{
     50	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
     51
     52	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
     53	       data->reg_base + GXBB_WDT_CTRL_REG);
     54
     55	return 0;
     56}
     57
     58static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
     59{
     60	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
     61
     62	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
     63	       data->reg_base + GXBB_WDT_CTRL_REG);
     64
     65	return 0;
     66}
     67
     68static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
     69{
     70	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
     71
     72	writel(0, data->reg_base + GXBB_WDT_RSET_REG);
     73
     74	return 0;
     75}
     76
     77static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
     78				      unsigned int timeout)
     79{
     80	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
     81	unsigned long tcnt = timeout * 1000;
     82
     83	if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
     84		tcnt = GXBB_WDT_TCNT_SETUP_MASK;
     85
     86	wdt_dev->timeout = timeout;
     87
     88	meson_gxbb_wdt_ping(wdt_dev);
     89
     90	writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
     91
     92	return 0;
     93}
     94
     95static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
     96{
     97	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
     98	unsigned long reg;
     99
    100	reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
    101
    102	return ((reg & GXBB_WDT_TCNT_SETUP_MASK) -
    103		(reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000;
    104}
    105
    106static const struct watchdog_ops meson_gxbb_wdt_ops = {
    107	.start = meson_gxbb_wdt_start,
    108	.stop = meson_gxbb_wdt_stop,
    109	.ping = meson_gxbb_wdt_ping,
    110	.set_timeout = meson_gxbb_wdt_set_timeout,
    111	.get_timeleft = meson_gxbb_wdt_get_timeleft,
    112};
    113
    114static const struct watchdog_info meson_gxbb_wdt_info = {
    115	.identity = "Meson GXBB Watchdog",
    116	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
    117};
    118
    119static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
    120{
    121	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
    122
    123	if (watchdog_active(&data->wdt_dev))
    124		meson_gxbb_wdt_start(&data->wdt_dev);
    125
    126	return 0;
    127}
    128
    129static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
    130{
    131	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
    132
    133	if (watchdog_active(&data->wdt_dev))
    134		meson_gxbb_wdt_stop(&data->wdt_dev);
    135
    136	return 0;
    137}
    138
    139static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
    140	SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
    141};
    142
    143static const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
    144	 { .compatible = "amlogic,meson-gxbb-wdt", },
    145	 { /* sentinel */ },
    146};
    147MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
    148
    149static void meson_clk_disable_unprepare(void *data)
    150{
    151	clk_disable_unprepare(data);
    152}
    153
    154static int meson_gxbb_wdt_probe(struct platform_device *pdev)
    155{
    156	struct device *dev = &pdev->dev;
    157	struct meson_gxbb_wdt *data;
    158	int ret;
    159
    160	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    161	if (!data)
    162		return -ENOMEM;
    163
    164	data->reg_base = devm_platform_ioremap_resource(pdev, 0);
    165	if (IS_ERR(data->reg_base))
    166		return PTR_ERR(data->reg_base);
    167
    168	data->clk = devm_clk_get(dev, NULL);
    169	if (IS_ERR(data->clk))
    170		return PTR_ERR(data->clk);
    171
    172	ret = clk_prepare_enable(data->clk);
    173	if (ret)
    174		return ret;
    175	ret = devm_add_action_or_reset(dev, meson_clk_disable_unprepare,
    176				       data->clk);
    177	if (ret)
    178		return ret;
    179
    180	platform_set_drvdata(pdev, data);
    181
    182	data->wdt_dev.parent = dev;
    183	data->wdt_dev.info = &meson_gxbb_wdt_info;
    184	data->wdt_dev.ops = &meson_gxbb_wdt_ops;
    185	data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
    186	data->wdt_dev.min_timeout = 1;
    187	data->wdt_dev.timeout = DEFAULT_TIMEOUT;
    188	watchdog_init_timeout(&data->wdt_dev, timeout, dev);
    189	watchdog_set_nowayout(&data->wdt_dev, nowayout);
    190	watchdog_set_drvdata(&data->wdt_dev, data);
    191
    192	/* Setup with 1ms timebase */
    193	writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
    194		GXBB_WDT_CTRL_EE_RESET |
    195		GXBB_WDT_CTRL_CLK_EN |
    196		GXBB_WDT_CTRL_CLKDIV_EN,
    197		data->reg_base + GXBB_WDT_CTRL_REG);
    198
    199	meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
    200
    201	return devm_watchdog_register_device(dev, &data->wdt_dev);
    202}
    203
    204static struct platform_driver meson_gxbb_wdt_driver = {
    205	.probe	= meson_gxbb_wdt_probe,
    206	.driver = {
    207		.name = "meson-gxbb-wdt",
    208		.pm = &meson_gxbb_wdt_pm_ops,
    209		.of_match_table	= meson_gxbb_wdt_dt_ids,
    210	},
    211};
    212
    213module_platform_driver(meson_gxbb_wdt_driver);
    214
    215MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
    216MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
    217MODULE_LICENSE("Dual BSD/GPL");