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

da9062_wdt.c (7240B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Watchdog device driver for DA9062 and DA9061 PMICs
      4 * Copyright (C) 2015  Dialog Semiconductor Ltd.
      5 *
      6 */
      7
      8#include <linux/kernel.h>
      9#include <linux/module.h>
     10#include <linux/watchdog.h>
     11#include <linux/platform_device.h>
     12#include <linux/uaccess.h>
     13#include <linux/slab.h>
     14#include <linux/i2c.h>
     15#include <linux/delay.h>
     16#include <linux/jiffies.h>
     17#include <linux/mfd/da9062/registers.h>
     18#include <linux/mfd/da9062/core.h>
     19#include <linux/property.h>
     20#include <linux/regmap.h>
     21#include <linux/of.h>
     22
     23static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
     24#define DA9062_TWDSCALE_DISABLE		0
     25#define DA9062_TWDSCALE_MIN		1
     26#define DA9062_TWDSCALE_MAX		(ARRAY_SIZE(wdt_timeout) - 1)
     27#define DA9062_WDT_MIN_TIMEOUT		wdt_timeout[DA9062_TWDSCALE_MIN]
     28#define DA9062_WDT_MAX_TIMEOUT		wdt_timeout[DA9062_TWDSCALE_MAX]
     29#define DA9062_WDG_DEFAULT_TIMEOUT	wdt_timeout[DA9062_TWDSCALE_MAX-1]
     30#define DA9062_RESET_PROTECTION_MS	300
     31
     32struct da9062_watchdog {
     33	struct da9062 *hw;
     34	struct watchdog_device wdtdev;
     35	bool use_sw_pm;
     36};
     37
     38static unsigned int da9062_wdt_read_timeout(struct da9062_watchdog *wdt)
     39{
     40	unsigned int val;
     41
     42	regmap_read(wdt->hw->regmap, DA9062AA_CONTROL_D, &val);
     43
     44	return wdt_timeout[val & DA9062AA_TWDSCALE_MASK];
     45}
     46
     47static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
     48{
     49	unsigned int i;
     50
     51	for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) {
     52		if (wdt_timeout[i] >= secs)
     53			return i;
     54	}
     55
     56	return DA9062_TWDSCALE_MAX;
     57}
     58
     59static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
     60{
     61	return regmap_update_bits(wdt->hw->regmap, DA9062AA_CONTROL_F,
     62				  DA9062AA_WATCHDOG_MASK,
     63				  DA9062AA_WATCHDOG_MASK);
     64}
     65
     66static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
     67					      unsigned int regval)
     68{
     69	struct da9062 *chip = wdt->hw;
     70
     71	regmap_update_bits(chip->regmap,
     72				  DA9062AA_CONTROL_D,
     73				  DA9062AA_TWDSCALE_MASK,
     74				  DA9062_TWDSCALE_DISABLE);
     75
     76	usleep_range(150, 300);
     77
     78	return regmap_update_bits(chip->regmap,
     79				  DA9062AA_CONTROL_D,
     80				  DA9062AA_TWDSCALE_MASK,
     81				  regval);
     82}
     83
     84static int da9062_wdt_start(struct watchdog_device *wdd)
     85{
     86	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
     87	unsigned int selector;
     88	int ret;
     89
     90	selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout);
     91	ret = da9062_wdt_update_timeout_register(wdt, selector);
     92	if (ret)
     93		dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n",
     94			ret);
     95
     96	return ret;
     97}
     98
     99static int da9062_wdt_stop(struct watchdog_device *wdd)
    100{
    101	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    102	int ret;
    103
    104	ret = regmap_update_bits(wdt->hw->regmap,
    105				 DA9062AA_CONTROL_D,
    106				 DA9062AA_TWDSCALE_MASK,
    107				 DA9062_TWDSCALE_DISABLE);
    108	if (ret)
    109		dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n",
    110			ret);
    111
    112	return ret;
    113}
    114
    115static int da9062_wdt_ping(struct watchdog_device *wdd)
    116{
    117	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    118	int ret;
    119
    120	/*
    121	 * Prevent pings from occurring late in system poweroff/reboot sequence
    122	 * and possibly locking out restart handler from accessing i2c bus.
    123	 */
    124	if (system_state > SYSTEM_RUNNING)
    125		return 0;
    126
    127	ret = da9062_reset_watchdog_timer(wdt);
    128	if (ret)
    129		dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n",
    130			ret);
    131
    132	return ret;
    133}
    134
    135static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
    136				  unsigned int timeout)
    137{
    138	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    139	unsigned int selector;
    140	int ret;
    141
    142	selector = da9062_wdt_timeout_to_sel(timeout);
    143	ret = da9062_wdt_update_timeout_register(wdt, selector);
    144	if (ret)
    145		dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n",
    146			ret);
    147	else
    148		wdd->timeout = wdt_timeout[selector];
    149
    150	return ret;
    151}
    152
    153static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
    154			      void *data)
    155{
    156	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    157	struct i2c_client *client = to_i2c_client(wdt->hw->dev);
    158	int ret;
    159
    160	/* Don't use regmap because it is not atomic safe */
    161	ret = i2c_smbus_write_byte_data(client, DA9062AA_CONTROL_F,
    162					DA9062AA_SHUTDOWN_MASK);
    163	if (ret < 0)
    164		dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
    165			  ret);
    166
    167	/* wait for reset to assert... */
    168	mdelay(500);
    169
    170	return ret;
    171}
    172
    173static const struct watchdog_info da9062_watchdog_info = {
    174	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
    175	.identity = "DA9062 WDT",
    176};
    177
    178static const struct watchdog_ops da9062_watchdog_ops = {
    179	.owner = THIS_MODULE,
    180	.start = da9062_wdt_start,
    181	.stop = da9062_wdt_stop,
    182	.ping = da9062_wdt_ping,
    183	.set_timeout = da9062_wdt_set_timeout,
    184	.restart = da9062_wdt_restart,
    185};
    186
    187static const struct of_device_id da9062_compatible_id_table[] = {
    188	{ .compatible = "dlg,da9062-watchdog", },
    189	{ },
    190};
    191
    192MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
    193
    194static int da9062_wdt_probe(struct platform_device *pdev)
    195{
    196	struct device *dev = &pdev->dev;
    197	unsigned int timeout;
    198	struct da9062 *chip;
    199	struct da9062_watchdog *wdt;
    200
    201	chip = dev_get_drvdata(dev->parent);
    202	if (!chip)
    203		return -EINVAL;
    204
    205	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
    206	if (!wdt)
    207		return -ENOMEM;
    208
    209	wdt->use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
    210
    211	wdt->hw = chip;
    212
    213	wdt->wdtdev.info = &da9062_watchdog_info;
    214	wdt->wdtdev.ops = &da9062_watchdog_ops;
    215	wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
    216	wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
    217	wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
    218	wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
    219	wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
    220	wdt->wdtdev.parent = dev;
    221
    222	watchdog_set_restart_priority(&wdt->wdtdev, 128);
    223
    224	watchdog_set_drvdata(&wdt->wdtdev, wdt);
    225	dev_set_drvdata(dev, &wdt->wdtdev);
    226
    227	timeout = da9062_wdt_read_timeout(wdt);
    228	if (timeout)
    229		wdt->wdtdev.timeout = timeout;
    230
    231	/* Set timeout from DT value if available */
    232	watchdog_init_timeout(&wdt->wdtdev, 0, dev);
    233
    234	if (timeout) {
    235		da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
    236		set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
    237	}
    238
    239	return devm_watchdog_register_device(dev, &wdt->wdtdev);
    240}
    241
    242static int __maybe_unused da9062_wdt_suspend(struct device *dev)
    243{
    244	struct watchdog_device *wdd = dev_get_drvdata(dev);
    245	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    246
    247	if (!wdt->use_sw_pm)
    248		return 0;
    249
    250	if (watchdog_active(wdd))
    251		return da9062_wdt_stop(wdd);
    252
    253	return 0;
    254}
    255
    256static int __maybe_unused da9062_wdt_resume(struct device *dev)
    257{
    258	struct watchdog_device *wdd = dev_get_drvdata(dev);
    259	struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
    260
    261	if (!wdt->use_sw_pm)
    262		return 0;
    263
    264	if (watchdog_active(wdd))
    265		return da9062_wdt_start(wdd);
    266
    267	return 0;
    268}
    269
    270static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops,
    271			 da9062_wdt_suspend, da9062_wdt_resume);
    272
    273static struct platform_driver da9062_wdt_driver = {
    274	.probe = da9062_wdt_probe,
    275	.driver = {
    276		.name = "da9062-watchdog",
    277		.pm = &da9062_wdt_pm_ops,
    278		.of_match_table = da9062_compatible_id_table,
    279	},
    280};
    281module_platform_driver(da9062_wdt_driver);
    282
    283MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
    284MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
    285MODULE_LICENSE("GPL");
    286MODULE_ALIAS("platform:da9062-watchdog");