[REPOST RFC PATCH 3/3] New "gpio-poweroff" driver to turn off platform devices with GPIOs
From: Kyle Moffett
Date: Tue Dec 13 2011 - 13:24:38 EST
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.7.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/