brintos

brintos / linux-shallow public Read only

0
0
Text · 9.6 KiB · 7c61ff3 Raw
430 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 *	exar_wdt.c - Driver for the watchdog present in some4 *		     Exar/MaxLinear UART chips like the XR28V38x.5 *6 *	(c) Copyright 2022 D. Müller <d.mueller@elsoft.ch>.7 *8 */9 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt11 12#include <linux/io.h>13#include <linux/list.h>14#include <linux/module.h>15#include <linux/platform_device.h>16#include <linux/slab.h>17#include <linux/watchdog.h>18 19#define DRV_NAME	"exar_wdt"20 21static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };22static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };23#define EXAR_EXIT_KEY	0xAA24 25#define EXAR_LDN	0x0726#define EXAR_DID	0x2027#define EXAR_VID	0x2328#define EXAR_WDT	0x2629#define EXAR_ACT	0x3030#define EXAR_RTBASE	0x6031 32#define EXAR_WDT_LDEV	0x0833 34#define EXAR_VEN_ID	0x13A835#define EXAR_DEV_382	0x038236#define EXAR_DEV_384	0x038437 38/* WDT runtime registers */39#define WDT_CTRL	0x0040#define WDT_VAL		0x0141 42#define WDT_UNITS_10MS	0x0	/* the 10 millisec unit of the HW is not used */43#define WDT_UNITS_SEC	0x244#define WDT_UNITS_MIN	0x445 46/* default WDT control for WDTOUT signal activ / rearm by read */47#define EXAR_WDT_DEF_CONF	048 49struct wdt_pdev_node {50	struct list_head list;51	struct platform_device *pdev;52	const char name[16];53};54 55struct wdt_priv {56	/* the lock for WDT io operations */57	spinlock_t io_lock;58	struct resource wdt_res;59	struct watchdog_device wdt_dev;60	unsigned short did;61	unsigned short config_port;62	unsigned char enter_key;63	unsigned char unit;64	unsigned char timeout;65};66 67#define WATCHDOG_TIMEOUT 6068 69static int timeout = WATCHDOG_TIMEOUT;70module_param(timeout, int, 0);71MODULE_PARM_DESC(timeout,72		 "Watchdog timeout in seconds. 1<=timeout<=15300, default="73		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");74 75static bool nowayout = WATCHDOG_NOWAYOUT;76module_param(nowayout, bool, 0);77MODULE_PARM_DESC(nowayout,78		 "Watchdog cannot be stopped once started (default="79		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");80 81static int exar_sio_enter(const unsigned short config_port,82			  const unsigned char key)83{84	if (!request_muxed_region(config_port, 2, DRV_NAME))85		return -EBUSY;86 87	/* write the ENTER-KEY twice */88	outb(key, config_port);89	outb(key, config_port);90 91	return 0;92}93 94static void exar_sio_exit(const unsigned short config_port)95{96	outb(EXAR_EXIT_KEY, config_port);97	release_region(config_port, 2);98}99 100static unsigned char exar_sio_read(const unsigned short config_port,101				   const unsigned char reg)102{103	outb(reg, config_port);104	return inb(config_port + 1);105}106 107static void exar_sio_write(const unsigned short config_port,108			   const unsigned char reg, const unsigned char val)109{110	outb(reg, config_port);111	outb(val, config_port + 1);112}113 114static unsigned short exar_sio_read16(const unsigned short config_port,115				      const unsigned char reg)116{117	unsigned char msb, lsb;118 119	msb = exar_sio_read(config_port, reg);120	lsb = exar_sio_read(config_port, reg + 1);121 122	return (msb << 8) | lsb;123}124 125static void exar_sio_select_wdt(const unsigned short config_port)126{127	exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);128}129 130static void exar_wdt_arm(const struct wdt_priv *priv)131{132	unsigned short rt_base = priv->wdt_res.start;133 134	/* write timeout value twice to arm watchdog */135	outb(priv->timeout, rt_base + WDT_VAL);136	outb(priv->timeout, rt_base + WDT_VAL);137}138 139static void exar_wdt_disarm(const struct wdt_priv *priv)140{141	unsigned short rt_base = priv->wdt_res.start;142 143	/*144	 * use two accesses with different values to make sure145	 * that a combination of a previous single access and146	 * the ones below with the same value are not falsely147	 * interpreted as "arm watchdog"148	 */149	outb(0xFF, rt_base + WDT_VAL);150	outb(0, rt_base + WDT_VAL);151}152 153static int exar_wdt_start(struct watchdog_device *wdog)154{155	struct wdt_priv *priv = watchdog_get_drvdata(wdog);156	unsigned short rt_base = priv->wdt_res.start;157 158	spin_lock(&priv->io_lock);159 160	exar_wdt_disarm(priv);161	outb(priv->unit, rt_base + WDT_CTRL);162	exar_wdt_arm(priv);163 164	spin_unlock(&priv->io_lock);165	return 0;166}167 168static int exar_wdt_stop(struct watchdog_device *wdog)169{170	struct wdt_priv *priv = watchdog_get_drvdata(wdog);171 172	spin_lock(&priv->io_lock);173 174	exar_wdt_disarm(priv);175 176	spin_unlock(&priv->io_lock);177	return 0;178}179 180static int exar_wdt_keepalive(struct watchdog_device *wdog)181{182	struct wdt_priv *priv = watchdog_get_drvdata(wdog);183	unsigned short rt_base = priv->wdt_res.start;184 185	spin_lock(&priv->io_lock);186 187	/* reading the WDT_VAL reg will feed the watchdog */188	inb(rt_base + WDT_VAL);189 190	spin_unlock(&priv->io_lock);191	return 0;192}193 194static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)195{196	struct wdt_priv *priv = watchdog_get_drvdata(wdog);197	bool unit_min = false;198 199	/*200	 * if new timeout is bigger then 255 seconds, change the201	 * unit to minutes and round the timeout up to the next whole minute202	 */203	if (t > 255) {204		unit_min = true;205		t = DIV_ROUND_UP(t, 60);206	}207 208	/* save for later use in exar_wdt_start() */209	priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;210	priv->timeout = t;211 212	wdog->timeout = unit_min ? t * 60 : t;213 214	if (watchdog_hw_running(wdog))215		exar_wdt_start(wdog);216 217	return 0;218}219 220static const struct watchdog_info exar_wdt_info = {221	.options	= WDIOF_KEEPALIVEPING |222			  WDIOF_SETTIMEOUT |223			  WDIOF_MAGICCLOSE,224	.identity	= "Exar/MaxLinear XR28V38x Watchdog",225};226 227static const struct watchdog_ops exar_wdt_ops = {228	.owner		= THIS_MODULE,229	.start		= exar_wdt_start,230	.stop		= exar_wdt_stop,231	.ping		= exar_wdt_keepalive,232	.set_timeout	= exar_wdt_set_timeout,233};234 235static int exar_wdt_config(struct watchdog_device *wdog,236			   const unsigned char conf)237{238	struct wdt_priv *priv = watchdog_get_drvdata(wdog);239	int ret;240 241	ret = exar_sio_enter(priv->config_port, priv->enter_key);242	if (ret)243		return ret;244 245	exar_sio_select_wdt(priv->config_port);246	exar_sio_write(priv->config_port, EXAR_WDT, conf);247 248	exar_sio_exit(priv->config_port);249 250	return 0;251}252 253static int __init exar_wdt_probe(struct platform_device *pdev)254{255	struct device *dev = &pdev->dev;256	struct wdt_priv *priv = dev->platform_data;257	struct watchdog_device *wdt_dev = &priv->wdt_dev;258	struct resource *res;259	int ret;260 261	res = platform_get_resource(pdev, IORESOURCE_IO, 0);262	if (!res)263		return -ENXIO;264 265	spin_lock_init(&priv->io_lock);266 267	wdt_dev->info = &exar_wdt_info;268	wdt_dev->ops = &exar_wdt_ops;269	wdt_dev->min_timeout = 1;270	wdt_dev->max_timeout = 255 * 60;271 272	watchdog_init_timeout(wdt_dev, timeout, NULL);273	watchdog_set_nowayout(wdt_dev, nowayout);274	watchdog_stop_on_reboot(wdt_dev);275	watchdog_stop_on_unregister(wdt_dev);276	watchdog_set_drvdata(wdt_dev, priv);277 278	ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);279	if (ret)280		return ret;281 282	exar_wdt_set_timeout(wdt_dev, timeout);283	/* Make sure that the watchdog is not running */284	exar_wdt_stop(wdt_dev);285 286	ret = devm_watchdog_register_device(dev, wdt_dev);287	if (ret)288		return ret;289 290	dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",291		 priv->did, timeout, nowayout);292 293	return 0;294}295 296static unsigned short __init exar_detect(const unsigned short config_port,297					 const unsigned char key,298					 unsigned short *rt_base)299{300	int ret;301	unsigned short base = 0;302	unsigned short vid, did;303 304	ret = exar_sio_enter(config_port, key);305	if (ret)306		return 0;307 308	vid = exar_sio_read16(config_port, EXAR_VID);309	did = exar_sio_read16(config_port, EXAR_DID);310 311	/* check for the vendor and device IDs we currently know about */312	if (vid == EXAR_VEN_ID &&313	    (did == EXAR_DEV_382 ||314	     did == EXAR_DEV_384)) {315		exar_sio_select_wdt(config_port);316		/* is device active? */317		if (exar_sio_read(config_port, EXAR_ACT) == 0x01)318			base = exar_sio_read16(config_port, EXAR_RTBASE);319	}320 321	exar_sio_exit(config_port);322 323	if (base) {324		pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",325			 did, config_port, base);326		*rt_base = base;327		return did;328	}329 330	return 0;331}332 333static struct platform_driver exar_wdt_driver = {334	.driver = {335		.name = DRV_NAME,336	},337};338 339static LIST_HEAD(pdev_list);340 341static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)342{343	struct wdt_pdev_node *n;344 345	n = kzalloc(sizeof(*n), GFP_KERNEL);346	if (!n)347		return -ENOMEM;348 349	INIT_LIST_HEAD(&n->list);350 351	scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);352	priv->wdt_res.name = n->name;353 354	n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,355						    &priv->wdt_res, 1,356						    priv, sizeof(*priv));357	if (IS_ERR(n->pdev)) {358		int err = PTR_ERR(n->pdev);359 360		kfree(n);361		return err;362	}363 364	list_add_tail(&n->list, &pdev_list);365 366	return 0;367}368 369static void exar_wdt_unregister(void)370{371	struct wdt_pdev_node *n, *t;372 373	list_for_each_entry_safe(n, t, &pdev_list, list) {374		platform_device_unregister(n->pdev);375		list_del(&n->list);376		kfree(n);377	}378}379 380static int __init exar_wdt_init(void)381{382	int ret, i, j, idx = 0;383 384	/* search for active Exar watchdogs on all possible locations */385	for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {386		for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {387			unsigned short did, rt_base = 0;388 389			did = exar_detect(sio_config_ports[i],390					  sio_enter_keys[j],391					  &rt_base);392 393			if (did) {394				struct wdt_priv priv = {395					.wdt_res = DEFINE_RES_IO(rt_base, 2),396					.did = did,397					.config_port = sio_config_ports[i],398					.enter_key = sio_enter_keys[j],399				};400 401				ret = exar_wdt_register(&priv, idx);402				if (!ret)403					idx++;404			}405		}406	}407 408	if (!idx)409		return -ENODEV;410 411	ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);412	if (ret)413		exar_wdt_unregister();414 415	return ret;416}417 418static void __exit exar_wdt_exit(void)419{420	exar_wdt_unregister();421	platform_driver_unregister(&exar_wdt_driver);422}423 424module_init(exar_wdt_init);425module_exit(exar_wdt_exit);426 427MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>");428MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");429MODULE_LICENSE("GPL");430