This driver is especially useful on systems with an OF device-tree as they can automatically instantiate the driver from GPIOs described in the device-tree. Signed-off-by: Kyle Moffett <Kyle.D.Moffett@xxxxxxxxxx> --- .../devicetree/bindings/gpio/gpio-poweroff.txt | 70 ++++ drivers/gpio/Kconfig | 10 + drivers/gpio/Makefile | 3 + drivers/gpio/gpio-poweroff.c | 360 ++++++++++++++++++++ include/linux/power/gpio-poweroff.h | 43 +++ 5 files changed, 486 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/gpio/gpio-poweroff.txt create mode 100644 drivers/gpio/gpio-poweroff.c create mode 100644 include/linux/power/gpio-poweroff.h diff --git a/Documentation/devicetree/bindings/gpio/gpio-poweroff.txt b/Documentation/devicetree/bindings/gpio/gpio-poweroff.txt new file mode 100644 index 0000000..418662a --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-poweroff.txt @@ -0,0 +1,70 @@ +Simple system and device poweroff via GPIO lines + +This performs powerdown of individual devices (or the entire system) using +generic GPIO lines configured in the device tree. + +Each device can have the following properties: + + * compatible (REQUIRED) + Must be "linux,gpio-poweroff". + + * machine-power-control (OPTIONAL) + Does not have a value. If present, the poweroff device is considered + to affect the entire system instead of just a single physical device. + + NOTE1: If this property is absent, the device-node must be present at + the correct location in the device-tree so the platform_drv->shutdown + callback is executed at the appropriate time during system shutdown. + + NOTE2: In order to trigger devices with this property present, the + platform support code must call gpio_machine_poweroff(). + + * final-delay-msecs (OPTIONAL) + After turning off the final power domain, wait the specified number of + milliseconds before continuing. The default is 0, if not specified. + + +Each device also has a list of power domains to be acted upon, represented as +three separate properties. A power domain is made up of the corresponding +elements in each property array: + + * power-domain-gpios (REQUIRED) + An array of GPIO specifiers of each power domain. All elements must be + valid and available or the device will fail to probe. + + The GPIO binding must support the OF_GPIO_ACTIVE_LOW flag in order to + specify the polarity of the GPIO. If your GPIO controller uses the + standard Linux of_gpio_simple_xlate(), then you can simply specify a 1 + in the second cell to indicate an active-low GPIO. + + * power-domain-names (OPTIONAL) + An array of the humna-readable names of each power domain. If missing + then generic numbers will be used for each domain. Entries present + beyond the number of "power-domain-gpios" will be ignored. + + * power-domain-delays-msec (OPTIONAL) + An array of 32-bit cells, each cell indicating how many milliseconds to + delay before activating the GPIO for the given power domain. If left + unspecified then a default of 0 will be assumed. Entries present beyond + the number of "power-domain-gpios" will be ignored. + +Examples: + +gpios { + compatible = "simple-bus"; + + /* Whole-system power control */ + power-control { + compatible = "linux,gpio-poweroff"; + + // Whole system, not a leaf device + machine-power-control; + + // Power domains are turned off in this order + power-domain-names = "TS", "S", "U"; + power-domain-gpios = <&pca9554a 3 0 + &pca9554a 2 0 + &pca9554a 1 0>; + power-domain-delays-msec = <500 500 500>; + }; +}; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8482a23..b6e1141 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -489,4 +489,14 @@ config GPIO_TPS65910 help Select this option to enable GPIO driver for the TPS65910 chip family. + +comment "Generic GPIO-based devices:" + +config GPIO_POWEROFF + tristate "Generic support for turning off platform devices with GPIOs" + help + This enables a generic "gpio-poweroff" driver which may be used by + custom platform-support code or included in OpenFirmware device + trees to power off hardware using generic GPIO lines. + endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index dbcb0bc..b52d54e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -4,6 +4,9 @@ ccflags-$(CONFIG_DEBUG_GPIO) += -DDEBUG obj-$(CONFIG_GPIOLIB) += gpiolib.o +# Generic GPIO platform drivers. +obj-$(CONFIG_GPIO_POWEROFF) += gpio-poweroff.o + # Device drivers. Generally keep list sorted alphabetically obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o diff --git a/drivers/gpio/gpio-poweroff.c b/drivers/gpio/gpio-poweroff.c new file mode 100644 index 0000000..36ebb3b --- /dev/null +++ b/drivers/gpio/gpio-poweroff.c @@ -0,0 +1,360 @@ +/* + * drivers/power/gpio-poweroff.c - Generic GPIO poweroff driver + * + * Maintainer: Kyle Moffett <Kyle.D.Moffett@xxxxxxxxxx> + * + * Copyright 2010-2011 eXMeritus, A Boeing Company + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License, as + * published by the Free Software Foundation. + */ +#include <linux/power/gpio-poweroff.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/gpio.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/of.h> + +struct gpio_poweroff { + const struct gpio_poweroff_platform_data *pdata; + struct platform_device *pdev; + struct list_head node; +}; + +static struct gpio_poweroff_platform_data * +gpio_poweroff_get_pdata(struct platform_device *pdev) +{ + struct gpio_poweroff_platform_data *pdata; + enum of_gpio_flags *gpio_flags = NULL; + struct of_gpio *of_gpios = NULL; + struct gpio *gpios = NULL; + u32 *gpio_mdelays = NULL; + struct device_node *np; + unsigned long i, nr; + int err; + + /* First check for static platform data */ + if (pdev->dev.platform_data) + return pdev->dev.platform_data; + + /* Then check for an OpenFirmware device node */ + np = pdev->dev.of_node; + if (!np) { + dev_err(&pdev->dev, "No gpio-poweroff pdata or of_node!\n"); + return NULL; + } + + /* Ok, create platform data based on the device node */ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Can't allocate gpio-poweroff pdata!\n"); + return NULL; + } + pdata->dynamic_platform_data = true; + + /* + * If a "machine-power-control" property is present at all, then this + * device will be regarded as a machine-poweroff handler and will not + * be called until the very end when gpio_machine_poweroff() is + * called by the architecture code. + */ + if (of_get_property(np, "machine-power-control", NULL)) + pdata->is_machine_poweroff = true; + else + pdata->is_machine_poweroff = false; + + /* + * If a "final-delay-msecs" property is present, the poweroff + * sequence will continue with the next device after the specified + * delay (which may be zero). + * + * Otherwise it will hang here indefinitely. + */ + pdata->final_mdelay = 0; + of_property_read_u32(np, "final-delay-msecs", &pdata->final_mdelay); + + /* Count the GPIOs */ + pdata->nr_gpios = nr = of_gpio_count_named(np, "power-domain-gpios"); + if (!pdata->nr_gpios) { + dev_warn(&pdev->dev, "No GPIOs to use during poweroff!\n"); + return pdata; + } + + /* Allocate enough memory for the tables */ +#define KCALLOC_ARRAY(ARRAY, NR, FLAGS) \ + ARRAY = kcalloc(NR, sizeof((ARRAY)[0]), FLAGS) + KCALLOC_ARRAY(of_gpios, nr, GFP_KERNEL); + KCALLOC_ARRAY(gpios, nr, GFP_KERNEL); + KCALLOC_ARRAY(gpio_mdelays, nr, GFP_KERNEL); + KCALLOC_ARRAY(gpio_flags, nr, GFP_KERNEL); +#undef KCALLOC_ARRAY + if (!of_gpios || !gpios || !gpio_mdelays || !gpio_flags) { + dev_err(&pdev->dev, "Can't allocate tables for %lu GPIOs\n", nr); + goto err_kfree; + } + + /* Parse the GPIO information from the device-tree */ + for (i = 0; i < nr; i++) { + const char *label; + + /* Initialize values */ + of_gpios[i].propname = "power-domain-gpios"; + of_gpios[i].index = i; + of_gpios[i].gpio_flags = GPIOF_DIR_OUT; + + /* Try to read a label from the device-tree */ + if (!of_property_read_string_index(np, "power-domain-names", + i, &label)) + of_gpios[i].gpio_label = kstrdup(label, GFP_KERNEL); + else + of_gpios[i].gpio_label = NULL; + } + err = of_get_gpio_array_flags(np, of_gpios, gpio_flags, gpios, nr); + if (err) { + dev_err(&pdev->dev, "Unable to parse all %lu GPIOs: %d\n", nr, err); + goto err_kfree_labels; + } + + /* Set initial output values appropriately */ + for (i = 0; i < nr; i++) { + if (gpio_flags[i] & OF_GPIO_ACTIVE_LOW) + gpios[i].flags |= GPIOF_INIT_HIGH; + else + gpios[i].flags |= GPIOF_INIT_LOW; + } + + /* Parse the GPIO delays from the device-tree */ + err = of_property_read_u32_array(np, "power-domain-delays-msec", + gpio_mdelays, nr); + if (err) { + dev_err(&pdev->dev, "Unable to parse all %lu GPIO delays: %d\n", nr, err); + goto err_kfree_labels; + } + + /* Free the temporary data and save the other arrays */ + kfree(of_gpios); + pdata->gpios = gpios; + pdata->gpio_mdelays = gpio_mdelays; + pdata->gpio_flags = gpio_flags; + return pdata; + +err_kfree_labels: + for (i = 0; i < nr; i++) + kfree(of_gpios[i].gpio_label); +err_kfree: + kfree(of_gpios); + kfree(gpios); + kfree(gpio_mdelays); + kfree(gpio_flags); + kfree(pdata); + return NULL; +} + +static void gpio_poweroff_release_pdata(struct platform_device *pdev, + const struct gpio_poweroff_platform_data *pdata) +{ + unsigned long i, nr = pdata->nr_gpios; + + /* Don't free anything unless we own the platform data */ + if (!pdata || !pdata->dynamic_platform_data) + return; + + /* Free any dynamically-allocated power domain labels */ + for (i = 0; i < nr; i++) + kfree(pdata->gpios[i].label); + + /* Free all the tables and then the platform data itself */ + kfree(pdata->gpios); + kfree(pdata->gpio_mdelays); + kfree(pdata->gpio_flags); + kfree(pdata); +} + +/* This list is used for "machine-poweroff" devices */ +static LIST_HEAD(gpio_machine_poweroff_list); +static DEFINE_MUTEX(gpio_machine_poweroff_mutex); + +static int __devinit gpio_poweroff_probe(struct platform_device *pdev) +{ + const struct gpio_poweroff_platform_data *pdata; + struct gpio_poweroff *poweroff; + int ret; + + /* Get the platform data from wherever is handy */ + pdata = gpio_poweroff_get_pdata(pdev); + if (!pdata) + return -ENODEV; + + /* Allocate a driver datastructure */ + poweroff = kzalloc(sizeof(*poweroff), GFP_KERNEL); + if (!poweroff) { + dev_err(&pdev->dev, "Can't allocate gpio-poweroff data!\n"); + return -ENOMEM; + } + + /* + * Request all of the GPIOs. + * + * NOTE: The platform_data must set these to outputs, with the + * correct levels so that the board doesn't power off here. + */ + ret = gpio_request_array(pdata->gpios, pdata->nr_gpios); + if (ret) { + dev_err(&pdev->dev, "Error requesting poweroff GPIOs!\n"); + goto err; + } + + /* Save the data */ + poweroff->pdata = pdata; + poweroff->pdev = pdev; + + /* If this is a machine-poweroff device, add it to the list */ + if (pdata->is_machine_poweroff) { + mutex_lock(&gpio_machine_poweroff_mutex); + list_add_tail(&poweroff->node, &gpio_machine_poweroff_list); + mutex_unlock(&gpio_machine_poweroff_mutex); + } else { + INIT_LIST_HEAD(&poweroff->node); + } + + /* Attach the data to the device */ + dev_set_drvdata(&pdev->dev, poweroff); + dev_info(&pdev->dev, "Successfully initialized gpio-poweroff!\n"); + return 0; + +err: + dev_err(&pdev->dev, "Could not initialize gpio-poweroff: %d\n", ret); + gpio_poweroff_release_pdata(pdev, pdata); + kfree(poweroff); + return ret; +} + +static int __devexit gpio_poweroff_remove(struct platform_device *pdev) +{ + struct gpio_poweroff *poweroff = dev_get_drvdata(&pdev->dev); + + /* Detach the data from the device */ + dev_info(&pdev->dev, "Removing gpio-poweroff device\n"); + dev_set_drvdata(&pdev->dev, NULL); + + /* Remove the poweroff device from the list */ + mutex_lock(&gpio_machine_poweroff_mutex); + list_del(&poweroff->node); + mutex_unlock(&gpio_machine_poweroff_mutex); + + /* Release the GPIOs and free the driver data */ + gpio_free_array(poweroff->pdata->gpios, poweroff->pdata->nr_gpios); + gpio_poweroff_release_pdata(pdev, poweroff->pdata); + kfree(poweroff); + return 0; +} + +/* Turn off power using a given "struct gpio_poweroff" */ +static void do_gpio_poweroff(struct gpio_poweroff *poweroff, bool machine) +{ + const struct gpio_poweroff_platform_data *pdata = poweroff->pdata; + struct device *dev = &poweroff->pdev->dev; + unsigned long i; + + /* + * If this device is a "machine-poweroff" device, only execute + * the powerdown at the very end of the shutdown sequence. + */ + if (pdata->is_machine_poweroff && !machine) + return; + + /* Enable each GPIO in order */ + dev_info(dev, "Performing GPIO-based poweroff...\n"); + for (i = 0; i < pdata->nr_gpios; i++) { + /* Get the label and number of the GPIO */ + const char *label = pdata->gpios[i].label; + int gpio = pdata->gpios[i].gpio; + + enum of_gpio_flags of_flags = 0; + unsigned long msec = 0; + bool active; + + /* Get the flags and delay (if present) */ + if (pdata->gpio_flags) + of_flags = pdata->gpio_flags[i]; + if (pdata->gpio_mdelays) + msec = pdata->gpio_mdelays[i]; + + active = !(of_flags & OF_GPIO_ACTIVE_LOW); + if (label) + dev_info(dev, "Turning off power domain \"%s\" " + "using an active-%s GPIO after %lums", + label, (active?"high":"low"), msec); + else + dev_info(dev, "Turning off power domain %d " + "using an active-%s GPIO after %lums", + gpio, (active?"high":"low"), msec); + + /* Program the GPIO after the delay */ + mdelay(msec); + gpio_set_value_cansleep(gpio, active); + } + + /* Perform the final delay */ + mdelay(pdata->final_mdelay); +} + +/* Per-device shutdown */ +static void gpio_poweroff_shutdown(struct platform_device *pdev) +{ + do_gpio_poweroff(dev_get_drvdata(&pdev->dev), false); +} + +/* Whole-machine shutdown */ +void gpio_machine_poweroff(void) +{ + struct gpio_poweroff *poweroff; + + pr_warning("Performing machine poweroff using GPIOs\n"); + + /* Iterate over each poweroff device in order */ + mutex_lock(&gpio_machine_poweroff_mutex); + list_for_each_entry(poweroff, &gpio_machine_poweroff_list, node) + do_gpio_poweroff(poweroff, true); + mutex_unlock(&gpio_machine_poweroff_mutex); + + pr_crit("Still online! System power off using GPIOs failed?\n"); +} + +static const struct of_device_id of_match_table[] = { + { .compatible = "linux,gpio-poweroff" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match_table); + +static struct platform_driver gpio_poweroff_driver = { + .driver = { + .name = "gpio-poweroff", + .owner = THIS_MODULE, + .of_match_table = of_match_table, + }, + .probe = gpio_poweroff_probe, + .shutdown = gpio_poweroff_shutdown, + .remove = __devexit_p(gpio_poweroff_remove), +}; + +static int __init gpio_poweroff_init(void) +{ + return platform_driver_register(&gpio_poweroff_driver); +} +module_init(gpio_poweroff_init); + +static void __exit gpio_poweroff_exit(void) +{ + platform_driver_unregister(&gpio_poweroff_driver); +} +module_exit(gpio_poweroff_exit); + +MODULE_AUTHOR("Kyle Moffett"); +MODULE_DESCRIPTION("Simple GPIO Power-Off Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/power/gpio-poweroff.h b/include/linux/power/gpio-poweroff.h new file mode 100644 index 0000000..70787f1 --- /dev/null +++ b/include/linux/power/gpio-poweroff.h @@ -0,0 +1,43 @@ +/* + * gpio-poweroff.h - Generic GPIO-based poweroff driver + * + * Maintainer: Kyle Moffett <Kyle.D.Moffett@xxxxxxxxxx> + * + * Copyright (C) 2010-2011 eXMeritus, A Boeing Company + * + */ + +#ifndef LINUX_POWER_GPIO_POWEROFF_H_ +#define LINUX_POWER_GPIO_POWEROFF_H_ + +#include <linux/of_gpio.h> +#include <linux/types.h> +#include <linux/gpio.h> + +struct gpio_poweroff_platform_data { + /* The number of poweroff GPIOs to use */ + size_t nr_gpios; + + /* An array of pre-requested GPIOs, and active-low/high status */ + const struct gpio *gpios; + const enum of_gpio_flags *gpio_flags; + + /* The delay to use before each GPIO is triggered */ + const u32 *gpio_mdelays; + + /* The final delay after all GPIOs have been set */ + u32 final_mdelay; + + /* + * If set, this is excluded from normal platform_device processing + * and only called when gpio_machine_poweroff() is run. + */ + bool is_machine_poweroff; + + /* The platform_data and arrays should be kfree()d during removal */ + bool dynamic_platform_data; +}; + +void gpio_machine_poweroff(void); + +#endif /* not LINUX_POWER_GPIO_POWEROFF_H_ */ -- 1.7.2.5 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html