diff options
Diffstat (limited to 'drivers/power/reset/gpio-poweroff.c')
| -rw-r--r-- | drivers/power/reset/gpio-poweroff.c | 135 |
1 files changed, 65 insertions, 70 deletions
diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c index e290d48ddd99..3eaae352ffb9 100644 --- a/drivers/power/reset/gpio-poweroff.c +++ b/drivers/power/reset/gpio-poweroff.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Toggles a GPIO pin to power down a device * @@ -5,100 +6,96 @@ * Andrew Lunn <andrew@lunn.ch> * * Copyright (C) 2012 Jamie Lentin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/platform_device.h> -#include <linux/gpio.h> -#include <linux/of_platform.h> -#include <linux/of_gpio.h> +#include <linux/property.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/reboot.h> -/* - * Hold configuration here, cannot be more than one instance of the driver - * since pm_power_off itself is global. - */ -static int gpio_num = -1; -static int gpio_active_low; +#define DEFAULT_TIMEOUT_MS 3000 -static void gpio_poweroff_do_poweroff(void) +struct gpio_poweroff { + struct gpio_desc *reset_gpio; + u32 timeout_ms; + u32 active_delay_ms; + u32 inactive_delay_ms; +}; + +static int gpio_poweroff_do_poweroff(struct sys_off_data *data) { - BUG_ON(!gpio_is_valid(gpio_num)); + struct gpio_poweroff *gpio_poweroff = data->cb_data; /* drive it active, also inactive->active edge */ - gpio_direction_output(gpio_num, !gpio_active_low); - mdelay(100); + gpiod_direction_output(gpio_poweroff->reset_gpio, 1); + mdelay(gpio_poweroff->active_delay_ms); + /* drive inactive, also active->inactive edge */ - gpio_set_value(gpio_num, gpio_active_low); - mdelay(100); + gpiod_set_value_cansleep(gpio_poweroff->reset_gpio, 0); + mdelay(gpio_poweroff->inactive_delay_ms); /* drive it active, also inactive->active edge */ - gpio_set_value(gpio_num, !gpio_active_low); + gpiod_set_value_cansleep(gpio_poweroff->reset_gpio, 1); /* give it some time */ - mdelay(3000); + mdelay(gpio_poweroff->timeout_ms); - WARN_ON(1); + /* + * If code reaches this point, it means that gpio-poweroff has failed + * to actually power off the system. + * Warn the user that the attempt to poweroff via gpio-poweroff + * has gone wrong. + */ + WARN(1, "Failed to poweroff via gpio-poweroff mechanism\n"); + + return NOTIFY_DONE; } static int gpio_poweroff_probe(struct platform_device *pdev) { - enum of_gpio_flags flags; + struct gpio_poweroff *gpio_poweroff; bool input = false; + enum gpiod_flags flags; + int priority = SYS_OFF_PRIO_DEFAULT; int ret; - /* If a pm_power_off function has already been added, leave it alone */ - if (pm_power_off != NULL) { - pr_err("%s: pm_power_off function already registered", - __func__); - return -EBUSY; - } - - gpio_num = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); - if (!gpio_is_valid(gpio_num)) - return gpio_num; - - gpio_active_low = flags & OF_GPIO_ACTIVE_LOW; - - input = of_property_read_bool(pdev->dev.of_node, "input"); - - ret = gpio_request(gpio_num, "poweroff-gpio"); - if (ret) { - pr_err("%s: Could not get GPIO %d", __func__, gpio_num); - return ret; - } - if (input) { - if (gpio_direction_input(gpio_num)) { - pr_err("Could not set direction of GPIO %d to input", - gpio_num); - goto err; - } - } else { - if (gpio_direction_output(gpio_num, gpio_active_low)) { - pr_err("Could not set direction of GPIO %d", gpio_num); - goto err; - } + gpio_poweroff = devm_kzalloc(&pdev->dev, sizeof(*gpio_poweroff), GFP_KERNEL); + if (!gpio_poweroff) + return -ENOMEM; + + input = device_property_read_bool(&pdev->dev, "input"); + if (input) + flags = GPIOD_IN; + else + flags = GPIOD_OUT_LOW; + + + gpio_poweroff->active_delay_ms = 100; + gpio_poweroff->inactive_delay_ms = 100; + gpio_poweroff->timeout_ms = DEFAULT_TIMEOUT_MS; + + device_property_read_u32(&pdev->dev, "active-delay-ms", &gpio_poweroff->active_delay_ms); + device_property_read_u32(&pdev->dev, "inactive-delay-ms", + &gpio_poweroff->inactive_delay_ms); + device_property_read_u32(&pdev->dev, "timeout-ms", &gpio_poweroff->timeout_ms); + device_property_read_u32(&pdev->dev, "priority", &priority); + if (priority > 255) { + dev_err(&pdev->dev, "Invalid priority property: %u\n", priority); + return -EINVAL; } - pm_power_off = &gpio_poweroff_do_poweroff; - return 0; - -err: - gpio_free(gpio_num); - return -ENODEV; -} + gpio_poweroff->reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags); + if (IS_ERR(gpio_poweroff->reset_gpio)) + return PTR_ERR(gpio_poweroff->reset_gpio); -static int gpio_poweroff_remove(struct platform_device *pdev) -{ - gpio_free(gpio_num); - if (pm_power_off == &gpio_poweroff_do_poweroff) - pm_power_off = NULL; + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, + priority, gpio_poweroff_do_poweroff, gpio_poweroff); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Cannot register poweroff handler\n"); return 0; } @@ -107,13 +104,12 @@ static const struct of_device_id of_gpio_poweroff_match[] = { { .compatible = "gpio-poweroff", }, {}, }; +MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match); static struct platform_driver gpio_poweroff_driver = { .probe = gpio_poweroff_probe, - .remove = gpio_poweroff_remove, .driver = { .name = "poweroff-gpio", - .owner = THIS_MODULE, .of_match_table = of_gpio_poweroff_match, }, }; @@ -122,5 +118,4 @@ module_platform_driver(gpio_poweroff_driver); MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>"); MODULE_DESCRIPTION("GPIO poweroff driver"); -MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:poweroff-gpio"); |
