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

asm9260_wdt.c (8636B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * Watchdog driver for Alphascale ASM9260.
      4 *
      5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
      6 */
      7
      8#include <linux/bitops.h>
      9#include <linux/clk.h>
     10#include <linux/delay.h>
     11#include <linux/interrupt.h>
     12#include <linux/io.h>
     13#include <linux/module.h>
     14#include <linux/of.h>
     15#include <linux/platform_device.h>
     16#include <linux/reset.h>
     17#include <linux/watchdog.h>
     18
     19#define CLOCK_FREQ	1000000
     20
     21/* Watchdog Mode register */
     22#define HW_WDMOD			0x00
     23/* Wake interrupt. Set by HW, can't be cleared. */
     24#define BM_MOD_WDINT			BIT(3)
     25/* This bit set if timeout reached. Cleared by SW. */
     26#define BM_MOD_WDTOF			BIT(2)
     27/* HW Reset on timeout */
     28#define BM_MOD_WDRESET			BIT(1)
     29/* WD enable */
     30#define BM_MOD_WDEN			BIT(0)
     31
     32/*
     33 * Watchdog Timer Constant register
     34 * Minimal value is 0xff, the meaning of this value
     35 * depends on used clock: T = WDCLK * (0xff + 1) * 4
     36 */
     37#define HW_WDTC				0x04
     38#define BM_WDTC_MAX(freq)		(0x7fffffff / (freq))
     39
     40/* Watchdog Feed register */
     41#define HW_WDFEED			0x08
     42
     43/* Watchdog Timer Value register */
     44#define HW_WDTV				0x0c
     45
     46#define ASM9260_WDT_DEFAULT_TIMEOUT	30
     47
     48enum asm9260_wdt_mode {
     49	HW_RESET,
     50	SW_RESET,
     51	DEBUG,
     52};
     53
     54struct asm9260_wdt_priv {
     55	struct device		*dev;
     56	struct watchdog_device	wdd;
     57	struct clk		*clk;
     58	struct clk		*clk_ahb;
     59	struct reset_control	*rst;
     60
     61	void __iomem		*iobase;
     62	int			irq;
     63	unsigned long		wdt_freq;
     64	enum asm9260_wdt_mode	mode;
     65};
     66
     67static int asm9260_wdt_feed(struct watchdog_device *wdd)
     68{
     69	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
     70
     71	iowrite32(0xaa, priv->iobase + HW_WDFEED);
     72	iowrite32(0x55, priv->iobase + HW_WDFEED);
     73
     74	return 0;
     75}
     76
     77static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
     78{
     79	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
     80	u32 counter;
     81
     82	counter = ioread32(priv->iobase + HW_WDTV);
     83
     84	return counter / priv->wdt_freq;
     85}
     86
     87static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
     88{
     89	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
     90	u32 counter;
     91
     92	counter = wdd->timeout * priv->wdt_freq;
     93
     94	iowrite32(counter, priv->iobase + HW_WDTC);
     95
     96	return 0;
     97}
     98
     99static int asm9260_wdt_enable(struct watchdog_device *wdd)
    100{
    101	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
    102	u32 mode = 0;
    103
    104	if (priv->mode == HW_RESET)
    105		mode = BM_MOD_WDRESET;
    106
    107	iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
    108
    109	asm9260_wdt_updatetimeout(wdd);
    110
    111	asm9260_wdt_feed(wdd);
    112
    113	return 0;
    114}
    115
    116static int asm9260_wdt_disable(struct watchdog_device *wdd)
    117{
    118	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
    119
    120	/* The only way to disable WD is to reset it. */
    121	reset_control_assert(priv->rst);
    122	reset_control_deassert(priv->rst);
    123
    124	return 0;
    125}
    126
    127static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
    128{
    129	wdd->timeout = to;
    130	asm9260_wdt_updatetimeout(wdd);
    131
    132	return 0;
    133}
    134
    135static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
    136{
    137	/* init WD if it was not started */
    138
    139	iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
    140
    141	iowrite32(0xff, priv->iobase + HW_WDTC);
    142	/* first pass correct sequence */
    143	asm9260_wdt_feed(&priv->wdd);
    144	/*
    145	 * Then write wrong pattern to the feed to trigger reset
    146	 * ASAP.
    147	 */
    148	iowrite32(0xff, priv->iobase + HW_WDFEED);
    149
    150	mdelay(1000);
    151}
    152
    153static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
    154{
    155	struct asm9260_wdt_priv *priv = devid;
    156	u32 stat;
    157
    158	stat = ioread32(priv->iobase + HW_WDMOD);
    159	if (!(stat & BM_MOD_WDINT))
    160		return IRQ_NONE;
    161
    162	if (priv->mode == DEBUG) {
    163		dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
    164	} else {
    165		dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
    166		asm9260_wdt_sys_reset(priv);
    167	}
    168
    169	return IRQ_HANDLED;
    170}
    171
    172static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
    173			   void *data)
    174{
    175	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
    176
    177	asm9260_wdt_sys_reset(priv);
    178
    179	return 0;
    180}
    181
    182static const struct watchdog_info asm9260_wdt_ident = {
    183	.options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
    184				| WDIOF_MAGICCLOSE,
    185	.identity         =	"Alphascale asm9260 Watchdog",
    186};
    187
    188static const struct watchdog_ops asm9260_wdt_ops = {
    189	.owner		= THIS_MODULE,
    190	.start		= asm9260_wdt_enable,
    191	.stop		= asm9260_wdt_disable,
    192	.get_timeleft	= asm9260_wdt_gettimeleft,
    193	.ping		= asm9260_wdt_feed,
    194	.set_timeout	= asm9260_wdt_settimeout,
    195	.restart	= asm9260_restart,
    196};
    197
    198static void asm9260_clk_disable_unprepare(void *data)
    199{
    200	clk_disable_unprepare(data);
    201}
    202
    203static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
    204{
    205	int err;
    206	unsigned long clk;
    207
    208	priv->clk = devm_clk_get(priv->dev, "mod");
    209	if (IS_ERR(priv->clk)) {
    210		dev_err(priv->dev, "Failed to get \"mod\" clk\n");
    211		return PTR_ERR(priv->clk);
    212	}
    213
    214	/* configure AHB clock */
    215	priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
    216	if (IS_ERR(priv->clk_ahb)) {
    217		dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
    218		return PTR_ERR(priv->clk_ahb);
    219	}
    220
    221	err = clk_prepare_enable(priv->clk_ahb);
    222	if (err) {
    223		dev_err(priv->dev, "Failed to enable ahb_clk!\n");
    224		return err;
    225	}
    226	err = devm_add_action_or_reset(priv->dev,
    227				       asm9260_clk_disable_unprepare,
    228				       priv->clk_ahb);
    229	if (err)
    230		return err;
    231
    232	err = clk_set_rate(priv->clk, CLOCK_FREQ);
    233	if (err) {
    234		dev_err(priv->dev, "Failed to set rate!\n");
    235		return err;
    236	}
    237
    238	err = clk_prepare_enable(priv->clk);
    239	if (err) {
    240		dev_err(priv->dev, "Failed to enable clk!\n");
    241		return err;
    242	}
    243	err = devm_add_action_or_reset(priv->dev,
    244				       asm9260_clk_disable_unprepare,
    245				       priv->clk);
    246	if (err)
    247		return err;
    248
    249	/* wdt has internal divider */
    250	clk = clk_get_rate(priv->clk);
    251	if (!clk) {
    252		dev_err(priv->dev, "Failed, clk is 0!\n");
    253		return -EINVAL;
    254	}
    255
    256	priv->wdt_freq = clk / 2;
    257
    258	return 0;
    259}
    260
    261static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
    262{
    263	const char *tmp;
    264	int ret;
    265
    266	/* default mode */
    267	priv->mode = HW_RESET;
    268
    269	ret = of_property_read_string(priv->dev->of_node,
    270				      "alphascale,mode", &tmp);
    271	if (ret < 0)
    272		return;
    273
    274	if (!strcmp(tmp, "hw"))
    275		priv->mode = HW_RESET;
    276	else if (!strcmp(tmp, "sw"))
    277		priv->mode = SW_RESET;
    278	else if (!strcmp(tmp, "debug"))
    279		priv->mode = DEBUG;
    280	else
    281		dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
    282			 tmp);
    283}
    284
    285static int asm9260_wdt_probe(struct platform_device *pdev)
    286{
    287	struct device *dev = &pdev->dev;
    288	struct asm9260_wdt_priv *priv;
    289	struct watchdog_device *wdd;
    290	int ret;
    291	static const char * const mode_name[] = { "hw", "sw", "debug", };
    292
    293	priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
    294	if (!priv)
    295		return -ENOMEM;
    296
    297	priv->dev = dev;
    298
    299	priv->iobase = devm_platform_ioremap_resource(pdev, 0);
    300	if (IS_ERR(priv->iobase))
    301		return PTR_ERR(priv->iobase);
    302
    303	priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
    304	if (IS_ERR(priv->rst))
    305		return PTR_ERR(priv->rst);
    306
    307	ret = asm9260_wdt_get_dt_clks(priv);
    308	if (ret)
    309		return ret;
    310
    311	wdd = &priv->wdd;
    312	wdd->info = &asm9260_wdt_ident;
    313	wdd->ops = &asm9260_wdt_ops;
    314	wdd->min_timeout = 1;
    315	wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
    316	wdd->parent = dev;
    317
    318	watchdog_set_drvdata(wdd, priv);
    319
    320	/*
    321	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second
    322	 * default, unless the max timeout is less than 30 seconds, then use
    323	 * the max instead.
    324	 */
    325	wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
    326	watchdog_init_timeout(wdd, 0, dev);
    327
    328	asm9260_wdt_get_dt_mode(priv);
    329
    330	if (priv->mode != HW_RESET)
    331		priv->irq = platform_get_irq(pdev, 0);
    332
    333	if (priv->irq > 0) {
    334		/*
    335		 * Not all supported platforms specify an interrupt for the
    336		 * watchdog, so let's make it optional.
    337		 */
    338		ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
    339				       pdev->name, priv);
    340		if (ret < 0)
    341			dev_warn(dev, "failed to request IRQ\n");
    342	}
    343
    344	watchdog_set_restart_priority(wdd, 128);
    345
    346	watchdog_stop_on_reboot(wdd);
    347	watchdog_stop_on_unregister(wdd);
    348	ret = devm_watchdog_register_device(dev, wdd);
    349	if (ret)
    350		return ret;
    351
    352	platform_set_drvdata(pdev, priv);
    353
    354	dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
    355		 wdd->timeout, mode_name[priv->mode]);
    356	return 0;
    357}
    358
    359static const struct of_device_id asm9260_wdt_of_match[] = {
    360	{ .compatible = "alphascale,asm9260-wdt"},
    361	{},
    362};
    363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
    364
    365static struct platform_driver asm9260_wdt_driver = {
    366	.driver = {
    367		.name = "asm9260-wdt",
    368		.of_match_table	= asm9260_wdt_of_match,
    369	},
    370	.probe = asm9260_wdt_probe,
    371};
    372module_platform_driver(asm9260_wdt_driver);
    373
    374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
    375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
    376MODULE_LICENSE("GPL");