Add driver model support for GPIOs. Since existing GPIO drivers do not use driver model, this feature must be enabled by CONFIG_DM_GPIO. After drivers are converted over
Tests are provided for the sandbox implementation, and are a sufficient sanity check for basic operation. The GPIO uclass understands the concept of named banks of GPIOs, which each GPIO device pricing a single bank. Within each bank the GPIOs are numbered using an offset from 0 to n-1. For example a bank named 'b' with 20 offsets will provide GPIOs named b0 to b19. Anonymous GPIO banks are also supported, and are just numbered without any prefix. Each time a GPIO driver is added to the uclass, the GPIOs are renumbered accordinging, so there is always a global GPIO numbering order. Signed-off-by: Simon Glass <[email protected]> Signed-off-by: Marek Vasut <[email protected]> Signed-off-by: Pavel Herrmann <[email protected]> Signed-off-by: Viktor Křivák <[email protected]> Signed-off-by: Tomas Hlavacek <[email protected]> --- Changes in v2: None drivers/gpio/Makefile | 2 + drivers/gpio/gpio-uclass.c | 281 +++++++++++++++++++++++++++++++++++++++++++++ include/asm-generic/gpio.h | 50 ++++++++ test/dm/gpio.c | 124 ++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 drivers/gpio/gpio-uclass.c create mode 100644 test/dm/gpio.c diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 9df1e26..0605fbf 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -25,6 +25,8 @@ include $(TOPDIR)/config.mk LIB := $(obj)libgpio.o +COBJS-$(CONFIG_DM_GPIO) += gpio-uclass.o + COBJS-$(CONFIG_AT91_GPIO) += at91_gpio.o COBJS-$(CONFIG_INTEL_ICH6_GPIO) += intel_ich6_gpio.o COBJS-$(CONFIG_KIRKWOOD_GPIO) += kw_gpio.o diff --git a/drivers/gpio/gpio-uclass.c b/drivers/gpio/gpio-uclass.c new file mode 100644 index 0000000..8289b62 --- /dev/null +++ b/drivers/gpio/gpio-uclass.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2013 Google, Inc + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <asm/gpio.h> + +/** + * gpio_to_device() - Convert global GPIO number to device, number + * gpio: The numeric representation of the GPIO + * + * Convert the GPIO number to an entry in the list of GPIOs + * or GPIO blocks registered with the GPIO controller. Returns + * entry on success, NULL on error. + */ +static int gpio_to_device(unsigned int gpio, struct device **devp, + unsigned int *offset) +{ + struct gpio_dev_priv *uc_priv; + struct device *dev; + int ret; + + for (ret = uclass_first_device(UCLASS_GPIO, &dev); + dev; + ret = uclass_next_device(&dev)) { + uc_priv = dev->uclass_priv; + if (gpio >= uc_priv->gpio_base && + gpio < uc_priv->gpio_base + uc_priv->gpio_count) { + *devp = dev; + *offset = gpio - uc_priv->gpio_base; + return 0; + } + } + + /* No such GPIO */ + return ret ? ret : -EINVAL; +} + +int gpio_lookup_name(const char *name, struct device **devp, + unsigned int *offsetp, unsigned int *gpiop) +{ + struct gpio_dev_priv *uc_priv; + struct device *dev; + int ret; + + if (devp) + *devp = NULL; + for (ret = uclass_first_device(UCLASS_GPIO, &dev); + dev; + ret = uclass_next_device(&dev)) { + ulong offset; + int len; + + uc_priv = dev->uclass_priv; + len = uc_priv->bank_name ? strlen(uc_priv->bank_name) : 0; + + if (!strncmp(name, uc_priv->bank_name, len)) { + if (strict_strtoul(name + len, 10, &offset)) + continue; + if (devp) + *devp = dev; + if (offsetp) + *offsetp = offset; + if (gpiop) + *gpiop = uc_priv->gpio_base + offset; + return 0; + } + } + + return ret ? ret : -EINVAL; +} + +/** + * gpio_request() - [COMPAT] Request GPIO + * gpio: GPIO number + * label: Name for the requested GPIO + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns 0 on success, negative value on error. + */ +int gpio_request(unsigned gpio, const char *label) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + if (!gpio_get_ops(dev)->request) + return 0; + + return gpio_get_ops(dev)->request(dev, offset, label); +} + +/** + * gpio_free() - [COMPAT] Relinquish GPIO + * gpio: GPIO number + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns 0 on success, negative value on error. + */ +int gpio_free(unsigned gpio) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + if (!gpio_get_ops(dev)->free) + return 0; + return gpio_get_ops(dev)->free(dev, offset); +} + +/** + * gpio_direction_input() - [COMPAT] Set GPIO direction to input + * gpio: GPIO number + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns 0 on success, negative value on error. + */ +int gpio_direction_input(unsigned gpio) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + return gpio_get_ops(dev)->direction_input(dev, offset); +} + +/** + * gpio_direction_output() - [COMPAT] Set GPIO direction to output and set value + * gpio: GPIO number + * value: Logical value to be set on the GPIO pin + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns 0 on success, negative value on error. + */ +int gpio_direction_output(unsigned gpio, int value) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + return gpio_get_ops(dev)->direction_output(dev, offset, value); +} + +/** + * gpio_get_value() - [COMPAT] Sample GPIO pin and return it's value + * gpio: GPIO number + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns the value of the GPIO pin, or negative value + * on error. + */ +int gpio_get_value(unsigned gpio) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + return gpio_get_ops(dev)->get_value(dev, offset); +} + +/** + * gpio_set_value() - [COMPAT] Configure logical value on GPIO pin + * gpio: GPIO number + * value: Logical value to be set on the GPIO pin. + * + * This function implements the API that's compatible with current + * GPIO API used in U-Boot. The request is forwarded to particular + * GPIO driver. Returns 0 on success, negative value on error. + */ +int gpio_set_value(unsigned gpio, int value) +{ + unsigned int offset; + struct device *dev; + int ret; + + ret = gpio_to_device(gpio, &dev, &offset); + if (ret) + return ret; + + return gpio_get_ops(dev)->set_value(dev, offset, value); +} + +const char *gpio_get_bank_info(struct device *dev, int *bit_count) +{ + struct gpio_dev_priv *priv; + + /* Must be called on an active device */ + priv = dev->uclass_priv; + assert(priv); + + *bit_count = priv->gpio_count; + return priv->bank_name; +} + +/* We need to renumber the GPIOs when any driver is probed/removed */ +static int gpio_renumber(void) +{ + struct gpio_dev_priv *uc_priv; + struct device *dev; + struct uclass *uc; + unsigned base; + int ret; + + ret = uclass_get(UCLASS_GPIO, &uc); + if (ret) + return ret; + + /* Ensure that we have a base for each bank */ + base = 0; + uclass_foreach_dev(dev, uc) { + if (device_active(dev)) { + uc_priv = dev->uclass_priv; + uc_priv->gpio_base = base; + base += uc_priv->gpio_count; + } + } + + return 0; +} + +static int gpio_post_probe(struct device *dev) +{ + return gpio_renumber(); +} + +static int gpio_pre_remove(struct device *dev) +{ + return gpio_renumber(); +} + +UCLASS_DRIVER(gpio) = { + .id = UCLASS_GPIO, + .name = "gpio", + .post_probe = gpio_post_probe, + .pre_remove = gpio_pre_remove, + .per_device_priv_size = sizeof(struct gpio_dev_priv), +}; diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h index bfedbe4..bc237dd 100644 --- a/include/asm-generic/gpio.h +++ b/include/asm-generic/gpio.h @@ -94,4 +94,54 @@ int gpio_get_value(unsigned gpio); * @return 0 if ok, -1 on error */ int gpio_set_value(unsigned gpio, int value); + +/* State of a GPIO, as reported by get_state() */ +enum { + GPIOF_INPUT = 0, + GPIOF_OUTPUT, + GPIOF_UNKNOWN, +}; + +struct device; + +/* + * Driver model GPIO operations, refer to functions above for description. + * These function copy the old API. + * + * This is trying to be close to Linux GPIO API. Once the U-Boot uses the + * new DM GPIO API, this should be really easy to flip over to the Linux + * GPIO API-alike interface. + * + * Akso it would be useful to standardise additional functions like + * pullup, slew rate and drive strength. + * + * gpio_request)( and gpio_free() are optional - if NULL then they will + * not be called. + */ +struct dm_gpio_ops { + int (*request)(struct device *dev, unsigned offset, const char *label); + int (*free)(struct device *dev, unsigned offset); + int (*direction_input)(struct device *dev, unsigned offset); + int (*direction_output)(struct device *dev, unsigned offset, + int value); + int (*get_value)(struct device *dev, unsigned offset); + int (*set_value)(struct device *dev, unsigned offset, int value); + int (*get_function)(struct device *dev, unsigned offset); + int (*get_state)(struct device *dev, unsigned offset, char *state, + int maxlen); +}; + +struct gpio_dev_priv { + const char *bank_name; + unsigned gpio_count; + unsigned gpio_base; +}; + +#define gpio_get_ops(dev) ((struct dm_gpio_ops *)(dev)->driver->ops) + +const char *gpio_get_bank_info(struct device *dev, int *offset_count); + +int gpio_lookup_name(const char *name, struct device **devp, + unsigned int *offsetp, unsigned int *gpiop); + #endif /* _ASM_GENERIC_GPIO_H_ */ diff --git a/test/dm/gpio.c b/test/dm/gpio.c new file mode 100644 index 0000000..703970f --- /dev/null +++ b/test/dm/gpio.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 Google, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <fdtdec.h> +#include <dm.h> +#include <dm/ut.h> +#include <dm/test.h> +#include <dm/util.h> +#include <asm/gpio.h> + +/* Test that sandbox GPIOs work correctly */ +static int dm_test_gpio(struct dm_test_state *dms) +{ + unsigned int offset, gpio; + struct dm_gpio_ops *ops; + struct device *dev; + const char *name; + int offset_count; + char buf[80]; + + /* + * We expect to get 3 banks. One is anonymous (just numbered) and + * comes from platform_data. The other two are named a (20 gpios) + * and b (10 gpios) and come from the device tree. See + * test/dm/test.dts. + */ + ut_assertok(gpio_lookup_name("b4", &dev, &offset, &gpio)); + ut_asserteq_str(dev->name, "extra-gpios"); + ut_asserteq(4, offset); + ut_asserteq(CONFIG_SANDBOX_GPIO_COUNT + 20 + 4, gpio); + + name = gpio_get_bank_info(dev, &offset_count); + ut_asserteq_str("b", name); + ut_asserteq(10, offset_count); + + /* Get the operations for this device */ + ops = gpio_get_ops(dev); + ut_assert(ops->get_state); + + /* Cannot get a value until it is reserved */ + ut_asserteq(-1, ops->get_value(dev, offset)); + + /* + * Now some tests that use the 'sandbox' back door. All GPIOs + * should default to input, include b4 that we are using here. + */ + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: in: 0 [ ]", buf); + + /* Change it to an output */ + sandbox_gpio_set_direction(dev, offset, 1); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: out: 0 [ ]", buf); + + sandbox_gpio_set_value(dev, offset, 1); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: out: 1 [ ]", buf); + + ut_assertok(ops->request(dev, offset, "testing")); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: out: 1 [x] testing", buf); + + /* Change the value a bit */ + ut_asserteq(1, ops->get_value(dev, offset)); + ut_assertok(ops->set_value(dev, offset, 0)); + ut_asserteq(0, ops->get_value(dev, offset)); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: out: 0 [x] testing", buf); + ut_assertok(ops->set_value(dev, offset, 1)); + ut_asserteq(1, ops->get_value(dev, offset)); + + /* Make it an input */ + ut_assertok(ops->direction_input(dev, offset)); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: in: 1 [x] testing", buf); + sandbox_gpio_set_value(dev, offset, 0); + ut_asserteq(0, sandbox_gpio_get_value(dev, offset)); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: in: 0 [x] testing", buf); + + ut_assertok(ops->free(dev, offset)); + ut_assertok(ops->get_state(dev, offset, buf, sizeof(buf))); + ut_asserteq_str("b4: in: 0 [ ]", buf); + + /* Check the 'a' bank also */ + ut_assertok(gpio_lookup_name("a15", &dev, &offset, &gpio)); + ut_asserteq_str(dev->name, "base-gpios"); + ut_asserteq(15, offset); + ut_asserteq(CONFIG_SANDBOX_GPIO_COUNT + 15, gpio); + + name = gpio_get_bank_info(dev, &offset_count); + ut_asserteq_str("a", name); + ut_asserteq(20, offset_count); + + /* And the anonymous bank */ + ut_assertok(gpio_lookup_name("14", &dev, &offset, &gpio)); + ut_asserteq_str(dev->name, "gpio_sandbox"); + ut_asserteq(14, offset); + ut_asserteq(14, gpio); + + name = gpio_get_bank_info(dev, &offset_count); + ut_asserteq_ptr(NULL, name); + ut_asserteq(CONFIG_SANDBOX_GPIO_COUNT, offset_count); + + return 0; +} +DM_TEST(dm_test_gpio, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); -- 1.8.2.1 _______________________________________________ U-Boot mailing list [email protected] http://lists.denx.de/mailman/listinfo/u-boot

