Merge tag 'v5.15.57' into rpi-5.15.y
[platform/kernel/linux-rpi.git] / drivers / power / reset / gpio-poweroff.c
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 static void (*old_power_off)(void);
28
29 static void gpio_poweroff_do_poweroff(void)
30 {
31         BUG_ON(!reset_gpio);
32
33         /* drive it active, also inactive->active edge */
34         gpiod_direction_output(reset_gpio, 1);
35         mdelay(active_delay);
36
37         /* drive inactive, also active->inactive edge */
38         gpiod_set_value_cansleep(reset_gpio, 0);
39         mdelay(inactive_delay);
40
41         /* drive it active, also inactive->active edge */
42         gpiod_set_value_cansleep(reset_gpio, 1);
43
44         /* give it some time */
45         mdelay(timeout);
46
47         if (old_power_off)
48                 old_power_off();
49
50         WARN_ON(1);
51 }
52
53 static int gpio_poweroff_probe(struct platform_device *pdev)
54 {
55         bool input = false;
56         enum gpiod_flags flags;
57         bool force = false;
58         bool export = false;
59
60         /* If a pm_power_off function has already been added, leave it alone */
61         force = of_property_read_bool(pdev->dev.of_node, "force");
62         if (!force && (pm_power_off != NULL)) {
63                 dev_err(&pdev->dev,
64                         "%s: pm_power_off function already registered\n",
65                        __func__);
66                 return -EBUSY;
67         }
68
69         input = device_property_read_bool(&pdev->dev, "input");
70         if (input)
71                 flags = GPIOD_IN;
72         else
73                 flags = GPIOD_OUT_LOW;
74
75         device_property_read_u32(&pdev->dev, "active-delay-ms", &active_delay);
76         device_property_read_u32(&pdev->dev, "inactive-delay-ms",
77                                  &inactive_delay);
78         device_property_read_u32(&pdev->dev, "timeout-ms", &timeout);
79
80         reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
81         if (IS_ERR(reset_gpio))
82                 return PTR_ERR(reset_gpio);
83
84         export = of_property_read_bool(pdev->dev.of_node, "export");
85         if (export) {
86                 gpiod_export(reset_gpio, false);
87                 gpiod_export_link(&pdev->dev, "poweroff-gpio", reset_gpio);
88         }
89
90         old_power_off = pm_power_off;
91         pm_power_off = &gpio_poweroff_do_poweroff;
92         return 0;
93 }
94
95 static int gpio_poweroff_remove(struct platform_device *pdev)
96 {
97         if (pm_power_off == &gpio_poweroff_do_poweroff)
98                 pm_power_off = old_power_off;
99
100         gpiod_unexport(reset_gpio);
101
102         return 0;
103 }
104
105 static const struct of_device_id of_gpio_poweroff_match[] = {
106         { .compatible = "gpio-poweroff", },
107         {},
108 };
109 MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match);
110
111 static struct platform_driver gpio_poweroff_driver = {
112         .probe = gpio_poweroff_probe,
113         .remove = gpio_poweroff_remove,
114         .driver = {
115                 .name = "poweroff-gpio",
116                 .of_match_table = of_gpio_poweroff_match,
117         },
118 };
119
120 module_platform_driver(gpio_poweroff_driver);
121
122 MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>");
123 MODULE_DESCRIPTION("GPIO poweroff driver");
124 MODULE_LICENSE("GPL v2");
125 MODULE_ALIAS("platform:poweroff-gpio");