1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Watchdog driver for the MEN z069 IP-Core |
4 | * |
5 | * Copyright (C) 2018 Johannes Thumshirn <jth@kernel.org> |
6 | */ |
7 | #include <linux/io.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/mcb.h> |
10 | #include <linux/module.h> |
11 | #include <linux/watchdog.h> |
12 | |
13 | struct men_z069_drv { |
14 | struct watchdog_device wdt; |
15 | void __iomem *base; |
16 | struct resource *mem; |
17 | }; |
18 | |
19 | #define MEN_Z069_WTR 0x10 |
20 | #define MEN_Z069_WTR_WDEN BIT(15) |
21 | #define MEN_Z069_WTR_WDET_MASK 0x7fff |
22 | #define MEN_Z069_WVR 0x14 |
23 | |
24 | #define MEN_Z069_TIMER_FREQ 500 /* 500 Hz */ |
25 | #define MEN_Z069_WDT_COUNTER_MIN 1 |
26 | #define MEN_Z069_WDT_COUNTER_MAX 0x7fff |
27 | #define MEN_Z069_DEFAULT_TIMEOUT 30 |
28 | |
29 | static bool nowayout = WATCHDOG_NOWAYOUT; |
30 | module_param(nowayout, bool, 0); |
31 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
32 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
33 | |
34 | static int men_z069_wdt_start(struct watchdog_device *wdt) |
35 | { |
36 | struct men_z069_drv *drv = watchdog_get_drvdata(wdd: wdt); |
37 | u16 val; |
38 | |
39 | val = readw(addr: drv->base + MEN_Z069_WTR); |
40 | val |= MEN_Z069_WTR_WDEN; |
41 | writew(val, addr: drv->base + MEN_Z069_WTR); |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static int men_z069_wdt_stop(struct watchdog_device *wdt) |
47 | { |
48 | struct men_z069_drv *drv = watchdog_get_drvdata(wdd: wdt); |
49 | u16 val; |
50 | |
51 | val = readw(addr: drv->base + MEN_Z069_WTR); |
52 | val &= ~MEN_Z069_WTR_WDEN; |
53 | writew(val, addr: drv->base + MEN_Z069_WTR); |
54 | |
55 | return 0; |
56 | } |
57 | |
58 | static int men_z069_wdt_ping(struct watchdog_device *wdt) |
59 | { |
60 | struct men_z069_drv *drv = watchdog_get_drvdata(wdd: wdt); |
61 | u16 val; |
62 | |
63 | /* The watchdog trigger value toggles between 0x5555 and 0xaaaa */ |
64 | val = readw(addr: drv->base + MEN_Z069_WVR); |
65 | val ^= 0xffff; |
66 | writew(val, addr: drv->base + MEN_Z069_WVR); |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static int men_z069_wdt_set_timeout(struct watchdog_device *wdt, |
72 | unsigned int timeout) |
73 | { |
74 | struct men_z069_drv *drv = watchdog_get_drvdata(wdd: wdt); |
75 | u16 reg, val, ena; |
76 | |
77 | wdt->timeout = timeout; |
78 | val = timeout * MEN_Z069_TIMER_FREQ; |
79 | |
80 | reg = readw(addr: drv->base + MEN_Z069_WTR); |
81 | ena = reg & MEN_Z069_WTR_WDEN; |
82 | reg = ena | val; |
83 | writew(val: reg, addr: drv->base + MEN_Z069_WTR); |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static const struct watchdog_info men_z069_info = { |
89 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
90 | .identity = "MEN z069 Watchdog" , |
91 | }; |
92 | |
93 | static const struct watchdog_ops men_z069_ops = { |
94 | .owner = THIS_MODULE, |
95 | .start = men_z069_wdt_start, |
96 | .stop = men_z069_wdt_stop, |
97 | .ping = men_z069_wdt_ping, |
98 | .set_timeout = men_z069_wdt_set_timeout, |
99 | }; |
100 | |
101 | static int men_z069_probe(struct mcb_device *dev, |
102 | const struct mcb_device_id *id) |
103 | { |
104 | struct men_z069_drv *drv; |
105 | struct resource *mem; |
106 | |
107 | drv = devm_kzalloc(dev: &dev->dev, size: sizeof(struct men_z069_drv), GFP_KERNEL); |
108 | if (!drv) |
109 | return -ENOMEM; |
110 | |
111 | mem = mcb_request_mem(dev, name: "z069-wdt" ); |
112 | if (IS_ERR(ptr: mem)) |
113 | return PTR_ERR(ptr: mem); |
114 | |
115 | drv->base = devm_ioremap(dev: &dev->dev, offset: mem->start, size: resource_size(res: mem)); |
116 | if (drv->base == NULL) |
117 | goto release_mem; |
118 | |
119 | drv->mem = mem; |
120 | drv->wdt.info = &men_z069_info; |
121 | drv->wdt.ops = &men_z069_ops; |
122 | drv->wdt.timeout = MEN_Z069_DEFAULT_TIMEOUT; |
123 | drv->wdt.min_timeout = 1; |
124 | drv->wdt.max_timeout = MEN_Z069_WDT_COUNTER_MAX / MEN_Z069_TIMER_FREQ; |
125 | |
126 | watchdog_init_timeout(wdd: &drv->wdt, timeout_parm: 0, dev: &dev->dev); |
127 | watchdog_set_nowayout(wdd: &drv->wdt, nowayout); |
128 | watchdog_set_drvdata(wdd: &drv->wdt, data: drv); |
129 | drv->wdt.parent = &dev->dev; |
130 | mcb_set_drvdata(dev, data: drv); |
131 | |
132 | return watchdog_register_device(&drv->wdt); |
133 | |
134 | release_mem: |
135 | mcb_release_mem(mem); |
136 | return -ENOMEM; |
137 | } |
138 | |
139 | static void men_z069_remove(struct mcb_device *dev) |
140 | { |
141 | struct men_z069_drv *drv = mcb_get_drvdata(dev); |
142 | |
143 | watchdog_unregister_device(&drv->wdt); |
144 | mcb_release_mem(mem: drv->mem); |
145 | } |
146 | |
147 | static const struct mcb_device_id men_z069_ids[] = { |
148 | { .device = 0x45 }, |
149 | { } |
150 | }; |
151 | MODULE_DEVICE_TABLE(mcb, men_z069_ids); |
152 | |
153 | static struct mcb_driver men_z069_driver = { |
154 | .driver = { |
155 | .name = "z069-wdt" , |
156 | }, |
157 | .probe = men_z069_probe, |
158 | .remove = men_z069_remove, |
159 | .id_table = men_z069_ids, |
160 | }; |
161 | module_mcb_driver(men_z069_driver); |
162 | |
163 | MODULE_AUTHOR("Johannes Thumshirn <jth@kernel.org>" ); |
164 | MODULE_LICENSE("GPL v2" ); |
165 | MODULE_ALIAS("mcb:16z069" ); |
166 | MODULE_IMPORT_NS(MCB); |
167 | |