|
| 1 | +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| 2 | +From: HackingGate < [email protected]> |
| 3 | +Date: Sat, 25 Oct 2025 23:14:12 +0900 |
| 4 | +Subject: [PATCH 2/2] arm64: rockchip: rk3576: add photonicat usb watchdog |
| 5 | + |
| 6 | +--- |
| 7 | + drivers/staging/Kconfig | 2 + |
| 8 | + drivers/staging/Makefile | 1 + |
| 9 | + drivers/staging/photonicat-usb-wdt/Kconfig | 10 + |
| 10 | + drivers/staging/photonicat-usb-wdt/Makefile | 1 + |
| 11 | + .../photonicat-usb-wdt/photonicat-usb-wdt.c | 278 ++++++++++++++++++ |
| 12 | + 5 files changed, 292 insertions(+) |
| 13 | + create mode 100644 drivers/staging/photonicat-usb-wdt/Kconfig |
| 14 | + create mode 100644 drivers/staging/photonicat-usb-wdt/Makefile |
| 15 | + create mode 100644 drivers/staging/photonicat-usb-wdt/photonicat-usb-wdt.c |
| 16 | + |
| 17 | +diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig |
| 18 | +index 3fe77abdb77c..dc853abae222 100644 |
| 19 | +--- a/drivers/staging/Kconfig |
| 20 | ++++ b/drivers/staging/Kconfig |
| 21 | +@@ -52,4 +52,6 @@ source "drivers/staging/gpib/Kconfig" |
| 22 | + |
| 23 | + source "drivers/staging/photonicat-pm/Kconfig" |
| 24 | + |
| 25 | ++source "drivers/staging/photonicat-usb-wdt/Kconfig" |
| 26 | ++ |
| 27 | + endif # STAGING |
| 28 | +diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile |
| 29 | +index dce556962331..7c4b66691f17 100644 |
| 30 | +--- a/drivers/staging/Makefile |
| 31 | ++++ b/drivers/staging/Makefile |
| 32 | +@@ -16,3 +16,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ |
| 33 | + obj-$(CONFIG_GPIB) += gpib/ |
| 34 | + obj-$(CONFIG_RTL8723CS) += rtl8723cs/ |
| 35 | + obj-$(CONFIG_PHOTONICAT_PM) += photonicat-pm/ |
| 36 | ++obj-$(CONFIG_PHOTONICAT_USB_WDT) += photonicat-usb-wdt/ |
| 37 | +diff --git a/drivers/staging/photonicat-usb-wdt/Kconfig b/drivers/staging/photonicat-usb-wdt/Kconfig |
| 38 | +new file mode 100644 |
| 39 | +index 000000000000..d5a03b176f81 |
| 40 | +--- /dev/null |
| 41 | ++++ b/drivers/staging/photonicat-usb-wdt/Kconfig |
| 42 | +@@ -0,0 +1,10 @@ |
| 43 | ++# SPDX-License-Identifier: GPL-2.0 |
| 44 | ++config PHOTONICAT_USB_WDT |
| 45 | ++ tristate "photonicat USB watchdog" |
| 46 | ++ depends on OF && GPIOLIB |
| 47 | ++ depends on USB |
| 48 | ++ select WATCHDOG_CORE |
| 49 | ++ help |
| 50 | ++ USB watchdog for photonicat board. Unless you have the platform, |
| 51 | ++ you will want to say 'N'. |
| 52 | ++ |
| 53 | +diff --git a/drivers/staging/photonicat-usb-wdt/Makefile b/drivers/staging/photonicat-usb-wdt/Makefile |
| 54 | +new file mode 100644 |
| 55 | +index 000000000000..b61ef615473d |
| 56 | +--- /dev/null |
| 57 | ++++ b/drivers/staging/photonicat-usb-wdt/Makefile |
| 58 | +@@ -0,0 +1 @@ |
| 59 | ++obj-$(CONFIG_PHOTONICAT_USB_WDT) += photonicat-usb-wdt.o |
| 60 | +diff --git a/drivers/staging/photonicat-usb-wdt/photonicat-usb-wdt.c b/drivers/staging/photonicat-usb-wdt/photonicat-usb-wdt.c |
| 61 | +new file mode 100644 |
| 62 | +index 000000000000..5e3a38a1cdb5 |
| 63 | +--- /dev/null |
| 64 | ++++ b/drivers/staging/photonicat-usb-wdt/photonicat-usb-wdt.c |
| 65 | +@@ -0,0 +1,278 @@ |
| 66 | ++#include <linux/module.h> |
| 67 | ++#include <linux/kernel.h> |
| 68 | ++#include <linux/init.h> |
| 69 | ++#include <linux/timer.h> |
| 70 | ++#include <linux/usb.h> |
| 71 | ++#include <linux/gpio/consumer.h> |
| 72 | ++#include <linux/delay.h> |
| 73 | ++#include <linux/workqueue.h> |
| 74 | ++#include <linux/of.h> |
| 75 | ++#include <linux/platform_device.h> |
| 76 | ++#include <linux/watchdog.h> |
| 77 | ++ |
| 78 | ++#define PCAT_USB_WATCHDOG_TIMEOUT_DEFAULT 15 |
| 79 | ++#define PCAT_USB_WATCHDOG_TIMEOUT_MIN 1 |
| 80 | ++#define PCAT_USB_WATCHDOG_TIMEOUT_MAX 120 |
| 81 | ++#define PCAT_USB_WATCHDOG_RESET_MS_DEFAULT 500 |
| 82 | ++ |
| 83 | ++static bool nowayout = WATCHDOG_NOWAYOUT; |
| 84 | ++module_param(nowayout, bool, 0); |
| 85 | ++MODULE_PARM_DESC(nowayout, |
| 86 | ++ "Watchdog cannot be stopped once started (default=" |
| 87 | ++ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
| 88 | ++ |
| 89 | ++struct pcat_usb_watchdog_data { |
| 90 | ++ struct device *dev; |
| 91 | ++ int target_vid; |
| 92 | ++ int target_pid; |
| 93 | ++ u32 reset_ms; |
| 94 | ++ u32 scan_interval_ms; |
| 95 | ++ struct gpio_desc *reset_gpio; |
| 96 | ++ bool target_live; |
| 97 | ++ struct watchdog_device wdd; |
| 98 | ++ struct timer_list monitor_timer; |
| 99 | ++ struct work_struct gpio_work; |
| 100 | ++ struct notifier_block usb_nb; |
| 101 | ++}; |
| 102 | ++ |
| 103 | ++static void pcat_usb_watchdog_gpio_work(struct work_struct *work) |
| 104 | ++{ |
| 105 | ++ struct pcat_usb_watchdog_data *data = container_of(work, |
| 106 | ++ struct |
| 107 | ++ pcat_usb_watchdog_data, |
| 108 | ++ gpio_work); |
| 109 | ++ |
| 110 | ++ gpiod_set_value(data->reset_gpio, 0); |
| 111 | ++ msleep(data->reset_ms); |
| 112 | ++ gpiod_set_value(data->reset_gpio, 1); |
| 113 | ++} |
| 114 | ++ |
| 115 | ++static int pcat_usb_watchdog_usb_notify(struct notifier_block *self, |
| 116 | ++ unsigned long action, void *dev) |
| 117 | ++{ |
| 118 | ++ struct pcat_usb_watchdog_data *data = container_of(self, |
| 119 | ++ struct pcat_usb_watchdog_data, |
| 120 | ++ usb_nb); |
| 121 | ++ struct usb_device *udev = (struct usb_device *)dev; |
| 122 | ++ |
| 123 | ++ if (!udev) |
| 124 | ++ return NOTIFY_OK; |
| 125 | ++ |
| 126 | ++ if (le16_to_cpu(udev->descriptor.idVendor) == data->target_vid && |
| 127 | ++ le16_to_cpu(udev->descriptor.idProduct) == data->target_pid) { |
| 128 | ++ |
| 129 | ++ switch (action) { |
| 130 | ++ case USB_DEVICE_ADD: |
| 131 | ++ data->target_live = true; |
| 132 | ++ dev_info(data->dev, "Target device added.\n"); |
| 133 | ++ break; |
| 134 | ++ case USB_DEVICE_REMOVE: |
| 135 | ++ data->target_live = false; |
| 136 | ++ dev_info(data->dev, "Target device removed!\n"); |
| 137 | ++ break; |
| 138 | ++ } |
| 139 | ++ } |
| 140 | ++ |
| 141 | ++ return NOTIFY_OK; |
| 142 | ++} |
| 143 | ++ |
| 144 | ++static int pcat_usb_watchdog_ping(struct watchdog_device *wdd) |
| 145 | ++{ |
| 146 | ++ /* Monitoring is handled by internal timer, this is just for userspace pings */ |
| 147 | ++ return 0; |
| 148 | ++} |
| 149 | ++ |
| 150 | ++static void pcat_usb_watchdog_monitor_callback(struct timer_list *timer) |
| 151 | ++{ |
| 152 | ++ struct pcat_usb_watchdog_data *data = container_of(timer, |
| 153 | ++ struct pcat_usb_watchdog_data, |
| 154 | ++ monitor_timer); |
| 155 | ++ |
| 156 | ++ if (!data->target_live) { |
| 157 | ++ dev_warn(data->dev, "Target device is not live, triggering reset!\n"); |
| 158 | ++ schedule_work(&data->gpio_work); |
| 159 | ++ } |
| 160 | ++ |
| 161 | ++ mod_timer(&data->monitor_timer, |
| 162 | ++ jiffies + msecs_to_jiffies(data->scan_interval_ms)); |
| 163 | ++} |
| 164 | ++ |
| 165 | ++static int pcat_usb_watchdog_start(struct watchdog_device *wdd) |
| 166 | ++{ |
| 167 | ++ struct pcat_usb_watchdog_data *data = watchdog_get_drvdata(wdd); |
| 168 | ++ |
| 169 | ++ dev_info(data->dev, "Watchdog started (timeout=%u sec)\n", wdd->timeout); |
| 170 | ++ |
| 171 | ++ return 0; |
| 172 | ++} |
| 173 | ++ |
| 174 | ++static int pcat_usb_watchdog_stop(struct watchdog_device *wdd) |
| 175 | ++{ |
| 176 | ++ struct pcat_usb_watchdog_data *data = watchdog_get_drvdata(wdd); |
| 177 | ++ |
| 178 | ++ dev_info(data->dev, "Watchdog stopped\n"); |
| 179 | ++ |
| 180 | ++ return 0; |
| 181 | ++} |
| 182 | ++ |
| 183 | ++static const struct watchdog_info pcat_usb_watchdog_info = { |
| 184 | ++ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
| 185 | ++ .identity = "Photonicat USB Watchdog", |
| 186 | ++}; |
| 187 | ++ |
| 188 | ++static const struct watchdog_ops pcat_usb_watchdog_ops = { |
| 189 | ++ .owner = THIS_MODULE, |
| 190 | ++ .start = pcat_usb_watchdog_start, |
| 191 | ++ .stop = pcat_usb_watchdog_stop, |
| 192 | ++ .ping = pcat_usb_watchdog_ping, |
| 193 | ++}; |
| 194 | ++ |
| 195 | ++static const struct of_device_id pcat_usb_watchdog_of_match[] = { |
| 196 | ++ {.compatible = "pcat-usb-watchdog" }, |
| 197 | ++ { /* sentinel */ } |
| 198 | ++}; |
| 199 | ++ |
| 200 | ++MODULE_DEVICE_TABLE(of, pcat_usb_watchdog_of_match); |
| 201 | ++ |
| 202 | ++static int pcat_usb_watchdog_probe(struct platform_device *pdev) |
| 203 | ++{ |
| 204 | ++ struct pcat_usb_watchdog_data *wdt_data; |
| 205 | ++ struct device *dev = &pdev->dev; |
| 206 | ++ struct watchdog_device *wdd; |
| 207 | ++ u32 vid, pid, timeout_sec; |
| 208 | ++ int ret; |
| 209 | ++ |
| 210 | ++ wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL); |
| 211 | ++ if (!wdt_data) |
| 212 | ++ return -ENOMEM; |
| 213 | ++ |
| 214 | ++ wdt_data->dev = dev; |
| 215 | ++ platform_set_drvdata(pdev, wdt_data); |
| 216 | ++ |
| 217 | ++ if (of_property_read_u32(dev->of_node, "target-vid", &vid)) { |
| 218 | ++ dev_err(dev, "No valid USB target-vid configured!\n"); |
| 219 | ++ return -EINVAL; |
| 220 | ++ } |
| 221 | ++ wdt_data->target_vid = vid; |
| 222 | ++ |
| 223 | ++ if (of_property_read_u32(dev->of_node, "target-pid", &pid)) { |
| 224 | ++ dev_err(dev, "No valid USB target-pid configured!\n"); |
| 225 | ++ return -EINVAL; |
| 226 | ++ } |
| 227 | ++ wdt_data->target_pid = pid; |
| 228 | ++ |
| 229 | ++ if (of_property_read_u32(dev->of_node, "reset-ms", &wdt_data->reset_ms)) { |
| 230 | ++ wdt_data->reset_ms = PCAT_USB_WATCHDOG_RESET_MS_DEFAULT; |
| 231 | ++ } |
| 232 | ++ |
| 233 | ++ if (wdt_data->reset_ms < 1 || wdt_data->reset_ms > 10000) { |
| 234 | ++ dev_warn(dev, "reset-ms out of range, using default\n"); |
| 235 | ++ wdt_data->reset_ms = PCAT_USB_WATCHDOG_RESET_MS_DEFAULT; |
| 236 | ++ } |
| 237 | ++ |
| 238 | ++ wdt_data->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); |
| 239 | ++ if (IS_ERR(wdt_data->reset_gpio)) { |
| 240 | ++ ret = PTR_ERR(wdt_data->reset_gpio); |
| 241 | ++ dev_err(dev, "Failed to get reset GPIO: %d\n", ret); |
| 242 | ++ return ret; |
| 243 | ++ } |
| 244 | ++ |
| 245 | ++ INIT_WORK(&wdt_data->gpio_work, pcat_usb_watchdog_gpio_work); |
| 246 | ++ |
| 247 | ++ /* Read scan interval from DT */ |
| 248 | ++ if (of_property_read_u32(dev->of_node, "scan-interval", &timeout_sec)) { |
| 249 | ++ timeout_sec = PCAT_USB_WATCHDOG_TIMEOUT_DEFAULT * 1000; |
| 250 | ++ } |
| 251 | ++ |
| 252 | ++ /* Convert to milliseconds if in seconds */ |
| 253 | ++ if (timeout_sec < 1000) { |
| 254 | ++ timeout_sec = timeout_sec * 1000; |
| 255 | ++ } |
| 256 | ++ |
| 257 | ++ if (timeout_sec < 1000 || timeout_sec > PCAT_USB_WATCHDOG_TIMEOUT_MAX * 1000) { |
| 258 | ++ dev_warn(dev, "scan-interval out of range, using default\n"); |
| 259 | ++ timeout_sec = PCAT_USB_WATCHDOG_TIMEOUT_DEFAULT * 1000; |
| 260 | ++ } |
| 261 | ++ |
| 262 | ++ wdt_data->scan_interval_ms = timeout_sec; |
| 263 | ++ |
| 264 | ++ /* Setup watchdog device */ |
| 265 | ++ wdd = &wdt_data->wdd; |
| 266 | ++ wdd->info = &pcat_usb_watchdog_info; |
| 267 | ++ wdd->ops = &pcat_usb_watchdog_ops; |
| 268 | ++ wdd->min_timeout = PCAT_USB_WATCHDOG_TIMEOUT_MIN; |
| 269 | ++ wdd->max_timeout = PCAT_USB_WATCHDOG_TIMEOUT_MAX; |
| 270 | ++ wdd->timeout = timeout_sec / 1000; |
| 271 | ++ wdd->parent = dev; |
| 272 | ++ watchdog_set_drvdata(wdd, wdt_data); |
| 273 | ++ watchdog_set_nowayout(wdd, nowayout); |
| 274 | ++ watchdog_stop_on_reboot(wdd); |
| 275 | ++ |
| 276 | ++ /* Register USB notifier */ |
| 277 | ++ wdt_data->usb_nb.notifier_call = pcat_usb_watchdog_usb_notify; |
| 278 | ++ usb_register_notify(&wdt_data->usb_nb); |
| 279 | ++ |
| 280 | ++ /* Setup and start monitor timer */ |
| 281 | ++ timer_setup(&wdt_data->monitor_timer, pcat_usb_watchdog_monitor_callback, 0); |
| 282 | ++ mod_timer(&wdt_data->monitor_timer, jiffies + msecs_to_jiffies(30000)); /* 30s grace */ |
| 283 | ++ |
| 284 | ++ /* Register watchdog device */ |
| 285 | ++ ret = devm_watchdog_register_device(dev, wdd); |
| 286 | ++ if (ret) { |
| 287 | ++ dev_err(dev, "Failed to register watchdog device: %d\n", ret); |
| 288 | ++ timer_delete_sync(&wdt_data->monitor_timer); |
| 289 | ++ usb_unregister_notify(&wdt_data->usb_nb); |
| 290 | ++ return ret; |
| 291 | ++ } |
| 292 | ++ |
| 293 | ++ dev_info(dev, "Photonicat USB watchdog initialized (VID:0x%04x PID:0x%04x, scan=%ums)\n", |
| 294 | ++ wdt_data->target_vid, wdt_data->target_pid, wdt_data->scan_interval_ms); |
| 295 | ++ |
| 296 | ++ return 0; |
| 297 | ++} |
| 298 | ++ |
| 299 | ++static void pcat_usb_watchdog_remove(struct platform_device *pdev) |
| 300 | ++{ |
| 301 | ++ struct pcat_usb_watchdog_data *wdt_data = platform_get_drvdata(pdev); |
| 302 | ++ |
| 303 | ++ /* Unregister USB notifier */ |
| 304 | ++ usb_unregister_notify(&wdt_data->usb_nb); |
| 305 | ++ |
| 306 | ++ /* Stop monitor timer */ |
| 307 | ++ timer_delete_sync(&wdt_data->monitor_timer); |
| 308 | ++ |
| 309 | ++ /* Cancel any pending work */ |
| 310 | ++ cancel_work_sync(&wdt_data->gpio_work); |
| 311 | ++ |
| 312 | ++ /* Watchdog device is automatically unregistered by devm */ |
| 313 | ++ dev_info(wdt_data->dev, "Photonicat USB watchdog removed\n"); |
| 314 | ++} |
| 315 | ++ |
| 316 | ++static struct platform_driver pcat_usb_watchdog_driver = { |
| 317 | ++ .probe = pcat_usb_watchdog_probe, |
| 318 | ++ .remove = pcat_usb_watchdog_remove, |
| 319 | ++ .driver = { |
| 320 | ++ .name = "pcat-usb-watchdog", |
| 321 | ++ .of_match_table = pcat_usb_watchdog_of_match, |
| 322 | ++ }, |
| 323 | ++}; |
| 324 | ++ |
| 325 | ++static int __init pcat_usb_watchdog_init(void) |
| 326 | ++{ |
| 327 | ++ printk(KERN_INFO "usb_watchdog: Loading USB watchdog driver\n"); |
| 328 | ++ return platform_driver_register(&pcat_usb_watchdog_driver); |
| 329 | ++} |
| 330 | ++ |
| 331 | ++static void __exit pcat_usb_watchdog_exit(void) |
| 332 | ++{ |
| 333 | ++ platform_driver_unregister(&pcat_usb_watchdog_driver); |
| 334 | ++} |
| 335 | ++ |
| 336 | ++module_init(pcat_usb_watchdog_init); |
| 337 | ++module_exit(pcat_usb_watchdog_exit); |
| 338 | ++ |
| 339 | ++MODULE_AUTHOR("Kyosuke Nekoyashiki <[email protected]>"); |
| 340 | ++MODULE_AUTHOR("HackingGate <[email protected]>"); |
| 341 | ++MODULE_DESCRIPTION("photonicat USB device watchdog"); |
| 342 | ++MODULE_LICENSE("GPL v2"); |
| 343 | ++MODULE_VERSION("1.0"); |
| 344 | +-- |
| 345 | +2.47.3 |
| 346 | + |
0 commit comments