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

sunxi_wdt.c (8093B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *      sunxi Watchdog Driver
      4 *
      5 *      Copyright (c) 2013 Carlo Caione
      6 *                    2012 Henrik Nordstrom
      7 *
      8 *      Based on xen_wdt.c
      9 *      (c) Copyright 2010 Novell, Inc.
     10 */
     11
     12#include <linux/clk.h>
     13#include <linux/delay.h>
     14#include <linux/err.h>
     15#include <linux/init.h>
     16#include <linux/io.h>
     17#include <linux/kernel.h>
     18#include <linux/module.h>
     19#include <linux/moduleparam.h>
     20#include <linux/of.h>
     21#include <linux/of_device.h>
     22#include <linux/platform_device.h>
     23#include <linux/types.h>
     24#include <linux/watchdog.h>
     25
     26#define WDT_MAX_TIMEOUT         16
     27#define WDT_MIN_TIMEOUT         1
     28#define WDT_TIMEOUT_MASK        0x0F
     29
     30#define WDT_CTRL_RELOAD         ((1 << 0) | (0x0a57 << 1))
     31
     32#define WDT_MODE_EN             (1 << 0)
     33
     34#define DRV_NAME		"sunxi-wdt"
     35#define DRV_VERSION		"1.0"
     36
     37static bool nowayout = WATCHDOG_NOWAYOUT;
     38static unsigned int timeout;
     39
     40/*
     41 * This structure stores the register offsets for different variants
     42 * of Allwinner's watchdog hardware.
     43 */
     44struct sunxi_wdt_reg {
     45	u8 wdt_ctrl;
     46	u8 wdt_cfg;
     47	u8 wdt_mode;
     48	u8 wdt_timeout_shift;
     49	u8 wdt_reset_mask;
     50	u8 wdt_reset_val;
     51	u32 wdt_key_val;
     52};
     53
     54struct sunxi_wdt_dev {
     55	struct watchdog_device wdt_dev;
     56	void __iomem *wdt_base;
     57	const struct sunxi_wdt_reg *wdt_regs;
     58};
     59
     60/*
     61 * wdt_timeout_map maps the watchdog timer interval value in seconds to
     62 * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
     63 *
     64 * [timeout seconds] = register value
     65 *
     66 */
     67
     68static const int wdt_timeout_map[] = {
     69	[1] = 0x1,  /* 1s  */
     70	[2] = 0x2,  /* 2s  */
     71	[3] = 0x3,  /* 3s  */
     72	[4] = 0x4,  /* 4s  */
     73	[5] = 0x5,  /* 5s  */
     74	[6] = 0x6,  /* 6s  */
     75	[8] = 0x7,  /* 8s  */
     76	[10] = 0x8, /* 10s */
     77	[12] = 0x9, /* 12s */
     78	[14] = 0xA, /* 14s */
     79	[16] = 0xB, /* 16s */
     80};
     81
     82
     83static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
     84			     unsigned long action, void *data)
     85{
     86	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
     87	void __iomem *wdt_base = sunxi_wdt->wdt_base;
     88	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
     89	u32 val;
     90
     91	/* Set system reset function */
     92	val = readl(wdt_base + regs->wdt_cfg);
     93	val &= ~(regs->wdt_reset_mask);
     94	val |= regs->wdt_reset_val;
     95	val |= regs->wdt_key_val;
     96	writel(val, wdt_base + regs->wdt_cfg);
     97
     98	/* Set lowest timeout and enable watchdog */
     99	val = readl(wdt_base + regs->wdt_mode);
    100	val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
    101	val |= WDT_MODE_EN;
    102	val |= regs->wdt_key_val;
    103	writel(val, wdt_base + regs->wdt_mode);
    104
    105	/*
    106	 * Restart the watchdog. The default (and lowest) interval
    107	 * value for the watchdog is 0.5s.
    108	 */
    109	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
    110
    111	while (1) {
    112		mdelay(5);
    113		val = readl(wdt_base + regs->wdt_mode);
    114		val |= WDT_MODE_EN;
    115		val |= regs->wdt_key_val;
    116		writel(val, wdt_base + regs->wdt_mode);
    117	}
    118	return 0;
    119}
    120
    121static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
    122{
    123	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
    124	void __iomem *wdt_base = sunxi_wdt->wdt_base;
    125	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
    126
    127	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
    128
    129	return 0;
    130}
    131
    132static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
    133		unsigned int timeout)
    134{
    135	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
    136	void __iomem *wdt_base = sunxi_wdt->wdt_base;
    137	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
    138	u32 reg;
    139
    140	if (wdt_timeout_map[timeout] == 0)
    141		timeout++;
    142
    143	sunxi_wdt->wdt_dev.timeout = timeout;
    144
    145	reg = readl(wdt_base + regs->wdt_mode);
    146	reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
    147	reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
    148	reg |= regs->wdt_key_val;
    149	writel(reg, wdt_base + regs->wdt_mode);
    150
    151	sunxi_wdt_ping(wdt_dev);
    152
    153	return 0;
    154}
    155
    156static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
    157{
    158	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
    159	void __iomem *wdt_base = sunxi_wdt->wdt_base;
    160	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
    161
    162	writel(regs->wdt_key_val, wdt_base + regs->wdt_mode);
    163
    164	return 0;
    165}
    166
    167static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
    168{
    169	u32 reg;
    170	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
    171	void __iomem *wdt_base = sunxi_wdt->wdt_base;
    172	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
    173	int ret;
    174
    175	ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
    176			sunxi_wdt->wdt_dev.timeout);
    177	if (ret < 0)
    178		return ret;
    179
    180	/* Set system reset function */
    181	reg = readl(wdt_base + regs->wdt_cfg);
    182	reg &= ~(regs->wdt_reset_mask);
    183	reg |= regs->wdt_reset_val;
    184	reg |= regs->wdt_key_val;
    185	writel(reg, wdt_base + regs->wdt_cfg);
    186
    187	/* Enable watchdog */
    188	reg = readl(wdt_base + regs->wdt_mode);
    189	reg |= WDT_MODE_EN;
    190	reg |= regs->wdt_key_val;
    191	writel(reg, wdt_base + regs->wdt_mode);
    192
    193	return 0;
    194}
    195
    196static const struct watchdog_info sunxi_wdt_info = {
    197	.identity	= DRV_NAME,
    198	.options	= WDIOF_SETTIMEOUT |
    199			  WDIOF_KEEPALIVEPING |
    200			  WDIOF_MAGICCLOSE,
    201};
    202
    203static const struct watchdog_ops sunxi_wdt_ops = {
    204	.owner		= THIS_MODULE,
    205	.start		= sunxi_wdt_start,
    206	.stop		= sunxi_wdt_stop,
    207	.ping		= sunxi_wdt_ping,
    208	.set_timeout	= sunxi_wdt_set_timeout,
    209	.restart	= sunxi_wdt_restart,
    210};
    211
    212static const struct sunxi_wdt_reg sun4i_wdt_reg = {
    213	.wdt_ctrl = 0x00,
    214	.wdt_cfg = 0x04,
    215	.wdt_mode = 0x04,
    216	.wdt_timeout_shift = 3,
    217	.wdt_reset_mask = 0x02,
    218	.wdt_reset_val = 0x02,
    219};
    220
    221static const struct sunxi_wdt_reg sun6i_wdt_reg = {
    222	.wdt_ctrl = 0x10,
    223	.wdt_cfg = 0x14,
    224	.wdt_mode = 0x18,
    225	.wdt_timeout_shift = 4,
    226	.wdt_reset_mask = 0x03,
    227	.wdt_reset_val = 0x01,
    228};
    229
    230static const struct sunxi_wdt_reg sun20i_wdt_reg = {
    231	.wdt_ctrl = 0x10,
    232	.wdt_cfg = 0x14,
    233	.wdt_mode = 0x18,
    234	.wdt_timeout_shift = 4,
    235	.wdt_reset_mask = 0x03,
    236	.wdt_reset_val = 0x01,
    237	.wdt_key_val = 0x16aa0000,
    238};
    239
    240static const struct of_device_id sunxi_wdt_dt_ids[] = {
    241	{ .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
    242	{ .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
    243	{ .compatible = "allwinner,sun20i-d1-wdt", .data = &sun20i_wdt_reg },
    244	{ /* sentinel */ }
    245};
    246MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
    247
    248static int sunxi_wdt_probe(struct platform_device *pdev)
    249{
    250	struct device *dev = &pdev->dev;
    251	struct sunxi_wdt_dev *sunxi_wdt;
    252	int err;
    253
    254	sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
    255	if (!sunxi_wdt)
    256		return -ENOMEM;
    257
    258	sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
    259	if (!sunxi_wdt->wdt_regs)
    260		return -ENODEV;
    261
    262	sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
    263	if (IS_ERR(sunxi_wdt->wdt_base))
    264		return PTR_ERR(sunxi_wdt->wdt_base);
    265
    266	sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
    267	sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
    268	sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
    269	sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
    270	sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
    271	sunxi_wdt->wdt_dev.parent = dev;
    272
    273	watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
    274	watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
    275	watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
    276
    277	watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
    278
    279	sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
    280
    281	watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
    282	err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
    283	if (unlikely(err))
    284		return err;
    285
    286	dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
    287		 sunxi_wdt->wdt_dev.timeout, nowayout);
    288
    289	return 0;
    290}
    291
    292static struct platform_driver sunxi_wdt_driver = {
    293	.probe		= sunxi_wdt_probe,
    294	.driver		= {
    295		.name		= DRV_NAME,
    296		.of_match_table	= sunxi_wdt_dt_ids,
    297	},
    298};
    299
    300module_platform_driver(sunxi_wdt_driver);
    301
    302module_param(timeout, uint, 0);
    303MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
    304
    305module_param(nowayout, bool, 0);
    306MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
    307		"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
    308
    309MODULE_LICENSE("GPL");
    310MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
    311MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
    312MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
    313MODULE_VERSION(DRV_VERSION);