1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Toggles a GPIO pin to power down a device |
4 | * |
5 | * Jamie Lentin <jm@lentin.co.uk> |
6 | * Andrew Lunn <andrew@lunn.ch> |
7 | * |
8 | * Copyright (C) 2012 Jamie Lentin |
9 | */ |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/property.h> |
15 | #include <linux/gpio/consumer.h> |
16 | #include <linux/mod_devicetable.h> |
17 | #include <linux/module.h> |
18 | #include <linux/reboot.h> |
19 | |
20 | #define DEFAULT_TIMEOUT_MS 3000 |
21 | |
22 | struct gpio_poweroff { |
23 | struct gpio_desc *reset_gpio; |
24 | u32 timeout_ms; |
25 | u32 active_delay_ms; |
26 | u32 inactive_delay_ms; |
27 | }; |
28 | |
29 | static int gpio_poweroff_do_poweroff(struct sys_off_data *data) |
30 | { |
31 | struct gpio_poweroff *gpio_poweroff = data->cb_data; |
32 | |
33 | /* drive it active, also inactive->active edge */ |
34 | gpiod_direction_output(desc: gpio_poweroff->reset_gpio, value: 1); |
35 | mdelay(gpio_poweroff->active_delay_ms); |
36 | |
37 | /* drive inactive, also active->inactive edge */ |
38 | gpiod_set_value_cansleep(desc: gpio_poweroff->reset_gpio, value: 0); |
39 | mdelay(gpio_poweroff->inactive_delay_ms); |
40 | |
41 | /* drive it active, also inactive->active edge */ |
42 | gpiod_set_value_cansleep(desc: gpio_poweroff->reset_gpio, value: 1); |
43 | |
44 | /* give it some time */ |
45 | mdelay(gpio_poweroff->timeout_ms); |
46 | |
47 | WARN_ON(1); |
48 | |
49 | return NOTIFY_DONE; |
50 | } |
51 | |
52 | static int gpio_poweroff_probe(struct platform_device *pdev) |
53 | { |
54 | struct gpio_poweroff *gpio_poweroff; |
55 | bool input = false; |
56 | enum gpiod_flags flags; |
57 | int priority = SYS_OFF_PRIO_DEFAULT; |
58 | int ret; |
59 | |
60 | gpio_poweroff = devm_kzalloc(dev: &pdev->dev, size: sizeof(*gpio_poweroff), GFP_KERNEL); |
61 | if (!gpio_poweroff) |
62 | return -ENOMEM; |
63 | |
64 | input = device_property_read_bool(dev: &pdev->dev, propname: "input" ); |
65 | if (input) |
66 | flags = GPIOD_IN; |
67 | else |
68 | flags = GPIOD_OUT_LOW; |
69 | |
70 | |
71 | gpio_poweroff->active_delay_ms = 100; |
72 | gpio_poweroff->inactive_delay_ms = 100; |
73 | gpio_poweroff->timeout_ms = DEFAULT_TIMEOUT_MS; |
74 | |
75 | device_property_read_u32(dev: &pdev->dev, propname: "active-delay-ms" , val: &gpio_poweroff->active_delay_ms); |
76 | device_property_read_u32(dev: &pdev->dev, propname: "inactive-delay-ms" , |
77 | val: &gpio_poweroff->inactive_delay_ms); |
78 | device_property_read_u32(dev: &pdev->dev, propname: "timeout-ms" , val: &gpio_poweroff->timeout_ms); |
79 | device_property_read_u32(dev: &pdev->dev, propname: "priority" , val: &priority); |
80 | if (priority > 255) { |
81 | dev_err(&pdev->dev, "Invalid priority property: %u\n" , priority); |
82 | return -EINVAL; |
83 | } |
84 | |
85 | gpio_poweroff->reset_gpio = devm_gpiod_get(dev: &pdev->dev, NULL, flags); |
86 | if (IS_ERR(ptr: gpio_poweroff->reset_gpio)) |
87 | return PTR_ERR(ptr: gpio_poweroff->reset_gpio); |
88 | |
89 | ret = devm_register_sys_off_handler(dev: &pdev->dev, mode: SYS_OFF_MODE_POWER_OFF, |
90 | priority, callback: gpio_poweroff_do_poweroff, cb_data: gpio_poweroff); |
91 | if (ret) |
92 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "Cannot register poweroff handler\n" ); |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static const struct of_device_id of_gpio_poweroff_match[] = { |
98 | { .compatible = "gpio-poweroff" , }, |
99 | {}, |
100 | }; |
101 | MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match); |
102 | |
103 | static struct platform_driver gpio_poweroff_driver = { |
104 | .probe = gpio_poweroff_probe, |
105 | .driver = { |
106 | .name = "poweroff-gpio" , |
107 | .of_match_table = of_gpio_poweroff_match, |
108 | }, |
109 | }; |
110 | |
111 | module_platform_driver(gpio_poweroff_driver); |
112 | |
113 | MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>" ); |
114 | MODULE_DESCRIPTION("GPIO poweroff driver" ); |
115 | MODULE_ALIAS("platform:poweroff-gpio" ); |
116 | |