Branch data Line data Source code
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/gpio/consumer.h>
15 : : #include <linux/of_platform.h>
16 : : #include <linux/module.h>
17 : :
18 : : #define DEFAULT_TIMEOUT_MS 3000
19 : : /*
20 : : * Hold configuration here, cannot be more than one instance of the driver
21 : : * since pm_power_off itself is global.
22 : : */
23 : : static struct gpio_desc *reset_gpio;
24 : : static u32 timeout = DEFAULT_TIMEOUT_MS;
25 : : static u32 active_delay = 100;
26 : : static u32 inactive_delay = 100;
27 : :
28 : 0 : static void gpio_poweroff_do_poweroff(void)
29 : : {
30 [ # # ]: 0 : BUG_ON(!reset_gpio);
31 : :
32 : : /* drive it active, also inactive->active edge */
33 : 0 : gpiod_direction_output(reset_gpio, 1);
34 [ # # # # : 0 : mdelay(active_delay);
# # # # #
# ]
35 : :
36 : : /* drive inactive, also active->inactive edge */
37 : 0 : gpiod_set_value_cansleep(reset_gpio, 0);
38 [ # # # # : 0 : mdelay(inactive_delay);
# # # # #
# ]
39 : :
40 : : /* drive it active, also inactive->active edge */
41 : 0 : gpiod_set_value_cansleep(reset_gpio, 1);
42 : :
43 : : /* give it some time */
44 [ # # # # : 0 : mdelay(timeout);
# # # # #
# ]
45 : :
46 : 0 : WARN_ON(1);
47 : 0 : }
48 : :
49 : 0 : static int gpio_poweroff_probe(struct platform_device *pdev)
50 : : {
51 : : bool input = false;
52 : : enum gpiod_flags flags;
53 : : bool force = false;
54 : : bool export = false;
55 : :
56 : : /* If a pm_power_off function has already been added, leave it alone */
57 : 0 : force = of_property_read_bool(pdev->dev.of_node, "force");
58 [ # # # # ]: 0 : if (!force && (pm_power_off != NULL)) {
59 : 0 : dev_err(&pdev->dev,
60 : : "%s: pm_power_off function already registered",
61 : : __func__);
62 : 0 : return -EBUSY;
63 : : }
64 : :
65 : 0 : input = device_property_read_bool(&pdev->dev, "input");
66 [ # # ]: 0 : if (input)
67 : : flags = GPIOD_IN;
68 : : else
69 : : flags = GPIOD_OUT_LOW;
70 : :
71 : : device_property_read_u32(&pdev->dev, "active-delay-ms", &active_delay);
72 : : device_property_read_u32(&pdev->dev, "inactive-delay-ms",
73 : : &inactive_delay);
74 : : device_property_read_u32(&pdev->dev, "timeout-ms", &timeout);
75 : :
76 : 0 : reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
77 [ # # ]: 0 : if (IS_ERR(reset_gpio))
78 : 0 : return PTR_ERR(reset_gpio);
79 : :
80 : 0 : export = of_property_read_bool(pdev->dev.of_node, "export");
81 [ # # ]: 0 : if (export) {
82 : 0 : gpiod_export(reset_gpio, false);
83 : 0 : gpiod_export_link(&pdev->dev, "poweroff-gpio", reset_gpio);
84 : : }
85 : :
86 : 0 : pm_power_off = &gpio_poweroff_do_poweroff;
87 : 0 : return 0;
88 : : }
89 : :
90 : 0 : static int gpio_poweroff_remove(struct platform_device *pdev)
91 : : {
92 [ # # ]: 0 : if (pm_power_off == &gpio_poweroff_do_poweroff)
93 : 0 : pm_power_off = NULL;
94 : :
95 : 0 : gpiod_unexport(reset_gpio);
96 : :
97 : 0 : return 0;
98 : : }
99 : :
100 : : static const struct of_device_id of_gpio_poweroff_match[] = {
101 : : { .compatible = "gpio-poweroff", },
102 : : {},
103 : : };
104 : :
105 : : static struct platform_driver gpio_poweroff_driver = {
106 : : .probe = gpio_poweroff_probe,
107 : : .remove = gpio_poweroff_remove,
108 : : .driver = {
109 : : .name = "poweroff-gpio",
110 : : .of_match_table = of_gpio_poweroff_match,
111 : : },
112 : : };
113 : :
114 : 404 : module_platform_driver(gpio_poweroff_driver);
115 : :
116 : : MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>");
117 : : MODULE_DESCRIPTION("GPIO poweroff driver");
118 : : MODULE_LICENSE("GPL v2");
119 : : MODULE_ALIAS("platform:poweroff-gpio");
|