1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Toggles a GPIO pin to restart a device |
4 | * |
5 | * Copyright (C) 2014 Google, Inc. |
6 | * |
7 | * Based on the gpio-poweroff driver. |
8 | */ |
9 | #include <linux/reboot.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | |
18 | struct gpio_restart { |
19 | struct gpio_desc *reset_gpio; |
20 | u32 active_delay_ms; |
21 | u32 inactive_delay_ms; |
22 | u32 wait_delay_ms; |
23 | }; |
24 | |
25 | static int gpio_restart_notify(struct sys_off_data *data) |
26 | { |
27 | struct gpio_restart *gpio_restart = data->cb_data; |
28 | |
29 | /* drive it active, also inactive->active edge */ |
30 | gpiod_direction_output(desc: gpio_restart->reset_gpio, value: 1); |
31 | mdelay(gpio_restart->active_delay_ms); |
32 | |
33 | /* drive inactive, also active->inactive edge */ |
34 | gpiod_set_value(desc: gpio_restart->reset_gpio, value: 0); |
35 | mdelay(gpio_restart->inactive_delay_ms); |
36 | |
37 | /* drive it active, also inactive->active edge */ |
38 | gpiod_set_value(desc: gpio_restart->reset_gpio, value: 1); |
39 | |
40 | /* give it some time */ |
41 | mdelay(gpio_restart->wait_delay_ms); |
42 | |
43 | WARN_ON(1); |
44 | |
45 | return NOTIFY_DONE; |
46 | } |
47 | |
48 | static int gpio_restart_probe(struct platform_device *pdev) |
49 | { |
50 | struct gpio_restart *gpio_restart; |
51 | bool open_source = false; |
52 | int priority = 129; |
53 | u32 property; |
54 | int ret; |
55 | |
56 | gpio_restart = devm_kzalloc(dev: &pdev->dev, size: sizeof(*gpio_restart), |
57 | GFP_KERNEL); |
58 | if (!gpio_restart) |
59 | return -ENOMEM; |
60 | |
61 | open_source = of_property_read_bool(np: pdev->dev.of_node, propname: "open-source" ); |
62 | |
63 | gpio_restart->reset_gpio = devm_gpiod_get(dev: &pdev->dev, NULL, |
64 | flags: open_source ? GPIOD_IN : GPIOD_OUT_LOW); |
65 | ret = PTR_ERR_OR_ZERO(ptr: gpio_restart->reset_gpio); |
66 | if (ret) { |
67 | if (ret != -EPROBE_DEFER) |
68 | dev_err(&pdev->dev, "Could not get reset GPIO\n" ); |
69 | return ret; |
70 | } |
71 | |
72 | gpio_restart->active_delay_ms = 100; |
73 | gpio_restart->inactive_delay_ms = 100; |
74 | gpio_restart->wait_delay_ms = 3000; |
75 | |
76 | ret = of_property_read_u32(np: pdev->dev.of_node, propname: "priority" , out_value: &property); |
77 | if (!ret) { |
78 | if (property > 255) |
79 | dev_err(&pdev->dev, "Invalid priority property: %u\n" , |
80 | property); |
81 | else |
82 | priority = property; |
83 | } |
84 | |
85 | of_property_read_u32(np: pdev->dev.of_node, propname: "active-delay" , |
86 | out_value: &gpio_restart->active_delay_ms); |
87 | of_property_read_u32(np: pdev->dev.of_node, propname: "inactive-delay" , |
88 | out_value: &gpio_restart->inactive_delay_ms); |
89 | of_property_read_u32(np: pdev->dev.of_node, propname: "wait-delay" , |
90 | out_value: &gpio_restart->wait_delay_ms); |
91 | |
92 | ret = devm_register_sys_off_handler(dev: &pdev->dev, |
93 | mode: SYS_OFF_MODE_RESTART, |
94 | priority, |
95 | callback: gpio_restart_notify, |
96 | cb_data: gpio_restart); |
97 | if (ret) { |
98 | dev_err(&pdev->dev, "%s: cannot register restart handler, %d\n" , |
99 | __func__, ret); |
100 | return -ENODEV; |
101 | } |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static const struct of_device_id of_gpio_restart_match[] = { |
107 | { .compatible = "gpio-restart" , }, |
108 | {}, |
109 | }; |
110 | |
111 | static struct platform_driver gpio_restart_driver = { |
112 | .probe = gpio_restart_probe, |
113 | .driver = { |
114 | .name = "restart-gpio" , |
115 | .of_match_table = of_gpio_restart_match, |
116 | }, |
117 | }; |
118 | |
119 | module_platform_driver(gpio_restart_driver); |
120 | |
121 | MODULE_AUTHOR("David Riley <davidriley@chromium.org>" ); |
122 | MODULE_DESCRIPTION("GPIO restart driver" ); |
123 | |