diff --git a/Documentation/ABI/testing/sysfs-driver-gpio-reset b/Documentation/ABI/testing/sysfs-driver-gpio-reset new file mode 100644 index 000000000000..6c141441bea7 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-gpio-reset @@ -0,0 +1,18 @@ +In the descriptions below is the name of the device tree node +describing the reset line. + +What: /sys/bus/platform/devices/gpio-reset.//duration_ms +Date: February 2014 +Contact: Martin Fuzzey +Description: Allows reset duration to be configured + Read: returns reset duration in ms (decimal) + Write: sets new resst duration in ms (decimal) + +What: /sys/bus/platform/devices/gpio-reset.//control +Date: February 2014 +Contact: Martin Fuzzey +Description: Allows reset line state to be controlled + Write only accepting the following strings: + "reset" : Line will be asserted during the reset duration then deasserted + "assert" : Line will be asserted until next write operation + "deassert" : Line will be deasserted until the next write operation diff --git a/Documentation/devicetree/bindings/reset/gpio.txt b/Documentation/devicetree/bindings/reset/gpio.txt new file mode 100644 index 000000000000..1a36821cce7d --- /dev/null +++ b/Documentation/devicetree/bindings/reset/gpio.txt @@ -0,0 +1,44 @@ +Generic GPIO based Reset Controller +====================================== + +Please also refer to reset.txt in this directory for common reset +controller binding usage. + +The parent node only needs a compatible property "linux,gpio-reset". + +Eaach reset line is described by a child node with the following properties: +Required properties: +- gpios : phandle of the GPIO line to use + +Optional properties: +- asserted-state: 0 => line low to reset, 1 => line high to reset. Defalut 0 +- duration-ms : Number of ms the line should be asserted while resetting (default 1) +- auto : boolean property - if present a reset will be done on boot +- #reset-cells: 0 if drivers can trigger reset via phandle + +example: + +gpio-reset { + compatible = "linux,gpio-reset"; + + wifi { + gpios = <&gpio2 1 0>; + asserted-state = <0>; + duration-ms = <100>; + auto; + }; + + ethernet_phy_reset:ethernet_phy { + #reset-cells = <0>; + gpios = <&gpio7 6 0>; + asserted-state = <0>; + duration-ms = <100>; + }; +}; + +Note that, although this controller may be used as part of the reset +framework, meaning that a device driver may request the reset using the +child node's phandle it (ethernet example above) it can also be used +in a standalone mode where the reset is performed automatically at boot +or from userspace by sysfs. This is particularly for devices that require +reset but are on discoverable busses (eg SDIO, USB). diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 06d9fa2f3bc0..205fe96d0023 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -14,6 +14,19 @@ menuconfig RESET_CONTROLLER if RESET_CONTROLLER +config GPIO_RESET_CONTROLLER + tristate "GPIO reset controller" + help + Reset controller using GPIO lines + + There are two main methods of using this controller: + * As a reset controller within the reset framework allowing device + drivers to request a hardware reset of their devices. + * As a standalone driver performing reset at boot or upon userspace + request (via sysfs) + The second method is suitable for devices on discoverable busses + (SDIO, USB) + config RESET_ATH79 bool "AR71xx Reset Driver" if COMPILE_TEST default ATH79 diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index bbe7026617fc..fa376fe3a8ec 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o obj-$(CONFIG_TI_SYSCON_RESET) += reset-ti-syscon.o obj-$(CONFIG_RESET_UNIPHIER) += reset-uniphier.o obj-$(CONFIG_RESET_ZYNQ) += reset-zynq.o +obj-$(CONFIG_GPIO_RESET_CONTROLLER) += gpio-reset.o diff --git a/drivers/reset/gpio-reset.c b/drivers/reset/gpio-reset.c new file mode 100644 index 000000000000..b374e72a1a9e --- /dev/null +++ b/drivers/reset/gpio-reset.c @@ -0,0 +1,463 @@ +/* + * Copyright 2014 Parkeon + * Martin Fuzzey + * + * 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. + * + * Driver allowing arbitrary hardware to be reset by GPIO signals. + * The reset may be triggered in several ways: + * At boot time (if configured in DT) + * On userspace request via sysfs + * By a driver using the reset controller framework + * + * The first two methods are supplied for devices on discoverable busses + * needing an external reset (eg some SDIO modules, USB hub chips) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct gpio_reset_priv; + +struct gpio_reset_line { + struct kobject kobj; + struct gpio_reset_priv *priv; + const char *name; + int gpio; + uint32_t asserted_value; + uint32_t duration_ms; +#ifdef CONFIG_RESET_CONTROLLER + struct reset_controller_dev rcdev; +#endif +}; +#define kobj_to_gpio_reset_line(x) container_of(x, struct gpio_reset_line, kobj) + + +struct gpio_reset_priv { + struct kref refcount; + struct device *dev; + int num_lines; + struct gpio_reset_line lines[]; +}; + +struct gpio_reset_attribute { + struct attribute attr; + ssize_t (*show)(struct gpio_reset_line *line, + struct gpio_reset_attribute *attr, char *buf); + ssize_t (*store)(struct gpio_reset_line *line, + struct gpio_reset_attribute *attr, + const char *buf, size_t count); +}; +#define to_gpio_reset_attr(x) container_of(x, struct gpio_reset_attribute, attr) + + +static void gpio_reset_assert(struct gpio_reset_line *line) +{ + gpio_set_value(line->gpio, line->asserted_value); +} + +static void gpio_reset_deassert(struct gpio_reset_line *line) +{ + gpio_set_value(line->gpio, !line->asserted_value); +} + +static void gpio_reset_reset(struct gpio_reset_line *line) +{ + dev_info(line->priv->dev, "Resetting '%s' (%dms)", + line->name, line->duration_ms); + gpio_reset_assert(line); + msleep(line->duration_ms); + gpio_reset_deassert(line); +} + + +static void gpio_reset_free_priv(struct kref *ref) +{ + struct gpio_reset_priv *priv = container_of(ref, + struct gpio_reset_priv, refcount); + + kfree(priv); +} + +static void gpio_reset_release_kobj(struct kobject *kobj) +{ + struct gpio_reset_line *line; + + line = kobj_to_gpio_reset_line(kobj); + + kref_put(&line->priv->refcount, gpio_reset_free_priv); +} + + +#ifdef CONFIG_SYSFS + +static ssize_t gpio_reset_attr_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct gpio_reset_attribute *attribute; + struct gpio_reset_line *line; + + attribute = to_gpio_reset_attr(attr); + line = kobj_to_gpio_reset_line(kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(line, attribute, buf); +} + +static ssize_t gpio_reset_attr_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t len) +{ + struct gpio_reset_attribute *attribute; + struct gpio_reset_line *line; + + attribute = to_gpio_reset_attr(attr); + line = kobj_to_gpio_reset_line(kobj); + + if (!attribute->store) + return -EIO; + + return attribute->store(line, attribute, buf, len); +} + +static ssize_t control_store(struct gpio_reset_line *line, + struct gpio_reset_attribute *attr, const char *buf, size_t count) +{ + char action[10]; + char *eol; + + strncpy(action, buf, min(count, sizeof(action))); + action[sizeof(action) - 1] = '\0'; + eol = strrchr(action, '\n'); + if (eol) + *eol = '\0'; + + if (!strcmp("reset", action)) + gpio_reset_reset(line); + else if (!strcmp("assert", action)) + gpio_reset_assert(line); + else if (!strcmp("deassert", action)) + gpio_reset_deassert(line); + else + return -EINVAL; + + return count; +} + +static ssize_t duration_ms_show(struct gpio_reset_line *line, + struct gpio_reset_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", line->duration_ms); +} + +static ssize_t duration_ms_store(struct gpio_reset_line *line, + struct gpio_reset_attribute *attr, const char *buf, size_t count) +{ + int ret; + + ret = kstrtouint(buf, 0, &line->duration_ms); + if (ret != 1) + return -EINVAL; + + return count; +} + +static struct gpio_reset_attribute control_attribute = __ATTR_WO(control); +static struct gpio_reset_attribute duration_attribute = __ATTR_RW(duration_ms); + +static struct attribute *gpio_reset_attrs[] = { + &control_attribute.attr, + &duration_attribute.attr, + NULL +}; + + +static const struct sysfs_ops gpio_reset_sysfs_ops = { + .show = gpio_reset_attr_show, + .store = gpio_reset_attr_store, +}; + + +static struct kobj_type gpio_reset_ktype = { + .release = gpio_reset_release_kobj, + .sysfs_ops = &gpio_reset_sysfs_ops, + .default_attrs = gpio_reset_attrs, +}; + +static int gpio_reset_create_sysfs(struct gpio_reset_line *line) +{ + int ret; + + ret = kobject_init_and_add(&line->kobj, &gpio_reset_ktype, + &line->priv->dev->kobj, "reset-%s", line->name); + + kref_get(&line->priv->refcount); /* kobject part of private structure */ + + if (ret) { + kobject_put(&line->kobj); + return ret; + } + + kobject_uevent(&line->kobj, KOBJ_ADD); + + return 0; +} + +static void gpio_reset_destroy_sysfs(struct gpio_reset_line *line) +{ + kobject_put(&line->kobj); +} + + +#else + +static int gpio_reset_create_sysfs(struct gpio_reset_line *line) +{ + return 0; +} + +static void gpio_reset_destroy_sysfs(struct gpio_reset_line *line) +{ +} + +#endif /* CONFIG_SYSFS */ + + +#ifdef CONFIG_RESET_CONTROLLER +#define rcdev_to_gpio_reset_line(x) \ + container_of(x, struct gpio_reset_line, rcdev) + +static int gpio_reset_controller_assert(struct reset_controller_dev *rcdev, + unsigned long sw_reset_idx) +{ + struct gpio_reset_line *line = rcdev_to_gpio_reset_line(rcdev); + + gpio_reset_assert(line); + + return 0; +} + +static int gpio_reset_controller_deassert(struct reset_controller_dev *rcdev, + unsigned long sw_reset_idx) +{ + struct gpio_reset_line *line = rcdev_to_gpio_reset_line(rcdev); + + gpio_reset_deassert(line); + + return 0; +} + +static int gpio_reset_controller_reset(struct reset_controller_dev *rcdev, + unsigned long sw_reset_idx) +{ + struct gpio_reset_line *line = rcdev_to_gpio_reset_line(rcdev); + + gpio_reset_reset(line); + + return 0; +} + +static struct reset_control_ops gpio_reset_ops = { + .assert = gpio_reset_controller_assert, + .deassert = gpio_reset_controller_deassert, + .reset = gpio_reset_controller_reset, +}; + + +int gpio_reset_nooarg_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + if (WARN_ON(reset_spec->args_count != 0)) + return -EINVAL; + + return 0; +} + +/* We register one controller per line rather than a single + * global controller so that drivers my directly reference the + * phandle of the gpio_reset subnode rather than having to know + * the index. + */ +static int gpio_reset_register_controller( + struct device_node *np, + struct gpio_reset_line *line) +{ + line->rcdev.of_node = np; + line->rcdev.nr_resets = 1; + line->rcdev.ops = &gpio_reset_ops; + line->rcdev.of_xlate = gpio_reset_nooarg_xlate; + + return reset_controller_register(&line->rcdev); +} + +static void gpio_reset_unregister_controller(struct gpio_reset_line *line) +{ + if (line->rcdev.nr_resets) + reset_controller_unregister(&line->rcdev); +} + +#else + +static int gpio_reset_register_controller( + struct device_node *np, + struct gpio_reset_line *line) +{ + return 0; +} + +static void gpio_reset_unregister_controller(struct gpio_reset_line *line) +{ +} +#endif /* CONFIG_RESET_CONTROLLER */ + + +static int gpio_reset_init_line( + struct device_node *np, + struct gpio_reset_line *line) +{ + int ret; + struct device *dev = line->priv->dev; + + line->name = np->name; + + line->gpio = of_get_gpio(np, 0); + if (!gpio_is_valid(line->gpio)) { + dev_warn(dev, "Invalid reset gpio for '%s'", np->name); + return 0; + } + + line->duration_ms = 1; + of_property_read_u32(np, "asserted-state", &line->asserted_value); + of_property_read_u32(np, "duration-ms", &line->duration_ms); + + ret = devm_gpio_request_one(dev, line->gpio, + line->asserted_value ? GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH, + line->name); + if (ret) + return ret; + + ret = gpio_reset_create_sysfs(line); + if (ret) + return ret; + + ret = gpio_reset_register_controller(np, line); + if (ret) + return ret; + + + if (of_property_read_bool(np, "auto")) + gpio_reset_reset(line); + + return 0; +} + +static void gpio_reset_free_line(struct gpio_reset_line *line) +{ + gpio_reset_destroy_sysfs(line); + gpio_reset_unregister_controller(line); +} + +static int gpio_reset_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node, *child; + struct gpio_reset_priv *priv; + struct gpio_reset_line *line; + int num_lines; + int ret; + + num_lines = of_get_available_child_count(np); + if (!num_lines) + return -ENODEV; + + for_each_available_child_of_node(np, child) { + ret = of_get_gpio(child, 0); + if (ret == -EPROBE_DEFER) + return ret; + } + + priv = kzalloc(sizeof(*priv) + sizeof(*line) * num_lines, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + kref_init(&priv->refcount); + priv->dev = &pdev->dev; + priv->num_lines = num_lines; + + line = priv->lines; + for_each_available_child_of_node(np, child) { + line->priv = priv; + ret = gpio_reset_init_line(child, line); + if (ret) + goto rollback; + line++; + } + + platform_set_drvdata(pdev, priv); + + return 0; + +rollback: + while (line >= priv->lines) + gpio_reset_free_line(line--); + + kref_put(&priv->refcount, gpio_reset_free_priv); + + return ret; +} + +static int gpio_reset_remove(struct platform_device *pdev) +{ + struct gpio_reset_priv *priv = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < priv->num_lines; i++) + gpio_reset_free_line(&priv->lines[i]); + + kref_put(&priv->refcount, gpio_reset_free_priv); + + return 0; +} + +static const struct of_device_id gpio_reset_dt_ids[] = { + { .compatible = "linux,gpio-reset" }, + {} +}; + +static struct platform_driver gpio_reset_driver = { + .probe = gpio_reset_probe, + .remove = gpio_reset_remove, + .driver = { + .name = "gpio_reset", + .owner = THIS_MODULE, + .of_match_table = gpio_reset_dt_ids, + }, +}; + +static int __init gpio_reset_init(void) +{ + return platform_driver_register(&gpio_reset_driver); +} +subsys_initcall(gpio_reset_init); + +static void __exit gpio_reset_exit(void) +{ + platform_driver_unregister(&gpio_reset_driver); +} +module_exit(gpio_reset_exit); + +MODULE_AUTHOR("Martin Fuzzey "); +MODULE_DESCRIPTION("GPIO reset controller"); +MODULE_LICENSE("GPL");