From 36b5215436ad115551e12478ceb3be4a2279e415 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 28 Apr 2020 17:23:27 -0700 Subject: gpio: Document proper return value for gpio drivers The legacy defines GPIOF_DIR_XXX are only for consumers. Document the proper ones. Also: don't use "_XXX" since that's harder to find with "git grep". Just list both of the values. Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20200428172322.1.I396f351e364f3c09df7c7606e79abefb8682c092@changeid Signed-off-by: Linus Walleij --- include/linux/gpio/driver.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index b8fc92c177eb..7b5f5681b7e4 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -267,9 +267,9 @@ struct gpio_irq_chip { * @free: optional hook for chip-specific deactivation, such as * disabling module power and clock; may sleep * @get_direction: returns direction for signal "offset", 0=out, 1=in, - * (same as GPIOF_DIR_XXX), or negative error. - * It is recommended to always implement this function, even on - * input-only or output-only gpio chips. + * (same as GPIO_LINE_DIRECTION_OUT / GPIO_LINE_DIRECTION_IN), + * or negative error. It is recommended to always implement this + * function, even on input-only or output-only gpio chips. * @direction_input: configures signal "offset" as input, or returns error * This can be omitted on input-only or output-only gpio chips. * @direction_output: configures signal "offset" as output, or returns error -- cgit v1.2.3 From 8d0910121b070b4edf4239d91cf9a4523b33ca0c Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 28 Apr 2020 17:23:28 -0700 Subject: gpio: Make "offset" and "unsigned int", not just "unsigned" When I copied the function prototypes from the GPIO header file into my own driver, checkpatch yelled at me saying that I shouldn't use use "unsigned" but instead should say "unsigned int". Let's make the header file use "unsigned int" so others who copy like I did won't get yelled at. Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20200428172322.2.Iacb3c8152c3cf9015a91308678155a578b0cc050@changeid Signed-off-by: Linus Walleij --- include/linux/gpio/driver.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 7b5f5681b7e4..8c41ae41b6bb 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -349,30 +349,30 @@ struct gpio_chip { struct module *owner; int (*request)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); void (*free)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); int (*get_direction)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); int (*direction_input)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); int (*direction_output)(struct gpio_chip *gc, - unsigned offset, int value); + unsigned int offset, int value); int (*get)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); int (*get_multiple)(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits); void (*set)(struct gpio_chip *gc, - unsigned offset, int value); + unsigned int offset, int value); void (*set_multiple)(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits); int (*set_config)(struct gpio_chip *gc, - unsigned offset, + unsigned int offset, unsigned long config); int (*to_irq)(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); void (*dbg_show)(struct seq_file *s, struct gpio_chip *gc); @@ -459,7 +459,7 @@ struct gpio_chip { }; extern const char *gpiochip_is_requested(struct gpio_chip *gc, - unsigned offset); + unsigned int offset); /* add/remove chips */ extern int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, @@ -657,9 +657,9 @@ static inline int gpiochip_irqchip_add_nested(struct gpio_chip *gc, } #endif /* CONFIG_LOCKDEP */ -int gpiochip_generic_request(struct gpio_chip *gc, unsigned offset); -void gpiochip_generic_free(struct gpio_chip *gc, unsigned offset); -int gpiochip_generic_config(struct gpio_chip *gc, unsigned offset, +int gpiochip_generic_request(struct gpio_chip *gc, unsigned int offset); +void gpiochip_generic_free(struct gpio_chip *gc, unsigned int offset); +int gpiochip_generic_config(struct gpio_chip *gc, unsigned int offset, unsigned long config); /** -- cgit v1.2.3 From 4c033b549912bc301c5e2adfb7b6ca007c11bf31 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 11 May 2020 16:52:54 +0200 Subject: gpiolib: Add support for GPIO lookup by line name Currently a GPIO lookup table can only refer to a specific GPIO by a tuple, consisting of a GPIO controller label and a GPIO offset inside the controller. However, a GPIO may also carry a line name, defined by DT or ACPI. If present, the line name is the most use-centric way to refer to a GPIO. Hence add support for looking up GPIOs by line name. Note that there is no guarantee that GPIO line names are globally unique, so this will use the first match found. Implement this by reusing the existing gpiod_lookup infrastructure. Rename gpiod_lookup.chip_label to gpiod_lookup.key, to make it clear that this field can have two meanings, and update the kerneldoc and GPIO_LOOKUP*() macros. Signed-off-by: Geert Uytterhoeven Tested-by: Eugeniu Rosca Reviewed-by: Ulrich Hecht Reviewed-by: Eugeniu Rosca Link: https://lore.kernel.org/r/20200511145257.22970-4-geert+renesas@glider.be Signed-off-by: Linus Walleij --- Documentation/driver-api/gpio/board.rst | 15 ++++++++++----- drivers/gpio/gpiolib.c | 22 +++++++++++++++++----- include/linux/gpio/machine.h | 17 ++++++++++------- 3 files changed, 37 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst index ce91518bf9f4..191fa867826a 100644 --- a/Documentation/driver-api/gpio/board.rst +++ b/Documentation/driver-api/gpio/board.rst @@ -113,13 +113,15 @@ files that desire to do so need to include the following header:: GPIOs are mapped by the means of tables of lookups, containing instances of the gpiod_lookup structure. Two macros are defined to help declaring such mappings:: - GPIO_LOOKUP(chip_label, chip_hwnum, con_id, flags) - GPIO_LOOKUP_IDX(chip_label, chip_hwnum, con_id, idx, flags) + GPIO_LOOKUP(key, chip_hwnum, con_id, flags) + GPIO_LOOKUP_IDX(key, chip_hwnum, con_id, idx, flags) where - - chip_label is the label of the gpiod_chip instance providing the GPIO - - chip_hwnum is the hardware number of the GPIO within the chip + - key is either the label of the gpiod_chip instance providing the GPIO, or + the GPIO line name + - chip_hwnum is the hardware number of the GPIO within the chip, or U16_MAX + to indicate that key is a GPIO line name - con_id is the name of the GPIO function from the device point of view. It can be NULL, in which case it will match any function. - idx is the index of the GPIO within the function. @@ -135,7 +137,10 @@ where In the future, these flags might be extended to support more properties. -Note that GPIO_LOOKUP() is just a shortcut to GPIO_LOOKUP_IDX() where idx = 0. +Note that: + 1. GPIO line names are not guaranteed to be globally unique, so the first + match found will be used. + 2. GPIO_LOOKUP() is just a shortcut to GPIO_LOOKUP_IDX() where idx = 0. A lookup table can then be defined as follows, with an empty entry defining its end. The 'dev_id' field of the table is the identifier of the device that will diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 40f2d7f69be2..3c1dcdfe8572 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -4618,7 +4618,7 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id, if (!table) return desc; - for (p = &table->table[0]; p->chip_label; p++) { + for (p = &table->table[0]; p->key; p++) { struct gpio_chip *gc; /* idx must always match exactly */ @@ -4629,18 +4629,30 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id, if (p->con_id && (!con_id || strcmp(p->con_id, con_id))) continue; - gc = find_chip_by_name(p->chip_label); + if (p->chip_hwnum == U16_MAX) { + desc = gpio_name_to_desc(p->key); + if (desc) { + *flags = p->flags; + return desc; + } + + dev_warn(dev, "cannot find GPIO line %s, deferring\n", + p->key); + return ERR_PTR(-EPROBE_DEFER); + } + + gc = find_chip_by_name(p->key); if (!gc) { /* * As the lookup table indicates a chip with - * p->chip_label should exist, assume it may + * p->key should exist, assume it may * still appear later and let the interested * consumer be probed again or let the Deferred * Probe infrastructure handle the error. */ dev_warn(dev, "cannot find GPIO chip %s, deferring\n", - p->chip_label); + p->key); return ERR_PTR(-EPROBE_DEFER); } @@ -4671,7 +4683,7 @@ static int platform_gpio_count(struct device *dev, const char *con_id) if (!table) return -ENOENT; - for (p = &table->table[0]; p->chip_label; p++) { + for (p = &table->table[0]; p->key; p++) { if ((con_id && p->con_id && !strcmp(con_id, p->con_id)) || (!con_id && !p->con_id)) count++; diff --git a/include/linux/gpio/machine.h b/include/linux/gpio/machine.h index 1ebe5be05d5f..781a053abbb9 100644 --- a/include/linux/gpio/machine.h +++ b/include/linux/gpio/machine.h @@ -20,8 +20,11 @@ enum gpio_lookup_flags { /** * struct gpiod_lookup - lookup table - * @chip_label: name of the chip the GPIO belongs to - * @chip_hwnum: hardware number (i.e. relative to the chip) of the GPIO + * @key: either the name of the chip the GPIO belongs to, or the GPIO line name + * Note that GPIO line names are not guaranteed to be globally unique, + * so this will use the first match found! + * @chip_hwnum: hardware number (i.e. relative to the chip) of the GPIO, or + * U16_MAX to indicate that @key is a GPIO line name * @con_id: name of the GPIO from the device's point of view * @idx: index of the GPIO in case several GPIOs share the same name * @flags: bitmask of gpio_lookup_flags GPIO_* values @@ -30,7 +33,7 @@ enum gpio_lookup_flags { * functions using platform data. */ struct gpiod_lookup { - const char *chip_label; + const char *key; u16 chip_hwnum; const char *con_id; unsigned int idx; @@ -63,17 +66,17 @@ struct gpiod_hog { /* * Simple definition of a single GPIO under a con_id */ -#define GPIO_LOOKUP(_chip_label, _chip_hwnum, _con_id, _flags) \ - GPIO_LOOKUP_IDX(_chip_label, _chip_hwnum, _con_id, 0, _flags) +#define GPIO_LOOKUP(_key, _chip_hwnum, _con_id, _flags) \ + GPIO_LOOKUP_IDX(_key, _chip_hwnum, _con_id, 0, _flags) /* * Use this macro if you need to have several GPIOs under the same con_id. * Each GPIO needs to use a different index and can be accessed using * gpiod_get_index() */ -#define GPIO_LOOKUP_IDX(_chip_label, _chip_hwnum, _con_id, _idx, _flags) \ +#define GPIO_LOOKUP_IDX(_key, _chip_hwnum, _con_id, _idx, _flags) \ { \ - .chip_label = _chip_label, \ + .key = _key, \ .chip_hwnum = _chip_hwnum, \ .con_id = _con_id, \ .idx = _idx, \ -- cgit v1.2.3 From 551cb86cbb7d85a3d16d10f6fb4fb954f13a7556 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 19 May 2020 16:12:33 +0300 Subject: gpio: dwapb: Remove unneeded has_irq member in struct dwapb_port_property has_irq member of struct dwapb_port_property is used only in one place, so, make it local test instead and remove from the structure. This local test is using memchr_inv() which is quite efficient in comparison to the original loop and possible little overhead can be neglected. Signed-off-by: Andy Shevchenko Tested-by: Serge Semin Acked-by: Lee Jones Acked-by: Serge Semin Link: https://lore.kernel.org/r/20200519131233.59032-4-andriy.shevchenko@linux.intel.com Signed-off-by: Linus Walleij --- drivers/gpio/gpio-dwapb.c | 14 +++++++------- drivers/mfd/intel_quark_i2c_gpio.c | 1 - include/linux/platform_data/gpio-dwapb.h | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c index d3765672c42b..1d8d55bd63aa 100644 --- a/drivers/gpio/gpio-dwapb.c +++ b/drivers/gpio/gpio-dwapb.c @@ -366,6 +366,11 @@ static void dwapb_configure_irqs(struct dwapb_gpio *gpio, irq_hw_number_t hwirq; int err, i; + if (memchr_inv(pp->irq, 0, sizeof(pp->irq)) == NULL) { + dev_warn(gpio->dev, "no IRQ for port%d\n", pp->idx); + return; + } + gpio->domain = irq_domain_create_linear(fwnode, ngpio, &irq_generic_chip_ops, gpio); if (!gpio->domain) @@ -501,7 +506,8 @@ static int dwapb_gpio_add_port(struct dwapb_gpio *gpio, if (pp->idx == 0) port->gc.set_config = dwapb_gpio_set_config; - if (pp->has_irq) + /* Only port A can provide interrupts in all configurations of the IP */ + if (pp->idx == 0) dwapb_configure_irqs(gpio, port, pp); err = gpiochip_add_data(&port->gc, port); @@ -550,13 +556,7 @@ static void dwapb_get_irq(struct device *dev, struct fwnode_handle *fwnode, irq = platform_get_irq_optional(to_platform_device(dev), j); if (irq > 0) pp->irq[j] = irq; - - if (pp->irq[j]) - pp->has_irq = true; } - - if (!pp->has_irq) - dev_warn(dev, "no irq for port%d\n", pp->idx); } static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev) diff --git a/drivers/mfd/intel_quark_i2c_gpio.c b/drivers/mfd/intel_quark_i2c_gpio.c index 41326b48da55..84ca7902e1df 100644 --- a/drivers/mfd/intel_quark_i2c_gpio.c +++ b/drivers/mfd/intel_quark_i2c_gpio.c @@ -216,7 +216,6 @@ static int intel_quark_gpio_setup(struct pci_dev *pdev, struct mfd_cell *cell) pdata->properties->ngpio = INTEL_QUARK_MFD_NGPIO; pdata->properties->gpio_base = INTEL_QUARK_MFD_GPIO_BASE; pdata->properties->irq[0] = pdev->irq; - pdata->properties->has_irq = true; pdata->properties->irq_shared = true; cell->platform_data = pdata; diff --git a/include/linux/platform_data/gpio-dwapb.h b/include/linux/platform_data/gpio-dwapb.h index 3c606c450d05..ff1be737bad6 100644 --- a/include/linux/platform_data/gpio-dwapb.h +++ b/include/linux/platform_data/gpio-dwapb.h @@ -12,7 +12,6 @@ struct dwapb_port_property { unsigned int ngpio; unsigned int gpio_base; int irq[32]; - bool has_irq; bool irq_shared; }; -- cgit v1.2.3 From a8173820f441ab3e2a45c4bb66b70da9a57a349e Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Sat, 23 May 2020 22:41:10 +0530 Subject: gpio: gpiolib: Allow GPIO IRQs to lazy disable With 'commit 461c1a7d4733 ("gpiolib: override irq_enable/disable")' gpiolib overrides irqchip's irq_enable and irq_disable callbacks. If irq_disable callback is implemented then genirq takes unlazy path to disable irq. Underlying irqchip may not want to implement irq_disable callback to lazy disable irq when client drivers invokes disable_irq(). By overriding irq_disable callback, gpiolib ends up always unlazy disabling IRQ. Allow gpiolib to lazy disable IRQs by overriding irq_disable callback only if irqchip implemented irq_disable. In cases where irq_disable is not implemented irq_mask is overridden. Similarly override irq_enable callback only if irqchip implemented irq_enable otherwise irq_unmask is overridden. Fixes: 461c1a7d4733 ("gpiolib: override irq_enable/disable") Signed-off-by: Maulik Shah Tested-by: Hans Verkuil Link: https://lore.kernel.org/r/1590253873-11556-2-git-send-email-mkshah@codeaurora.org Signed-off-by: Linus Walleij --- drivers/gpio/gpiolib.c | 55 +++++++++++++++++++++++++++++---------------- include/linux/gpio/driver.h | 13 +++++++++++ 2 files changed, 49 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 34259c1e78ca..167860684be6 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2471,32 +2471,37 @@ static void gpiochip_irq_relres(struct irq_data *d) gpiochip_relres_irq(gc, d->hwirq); } +static void gpiochip_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + if (gc->irq.irq_mask) + gc->irq.irq_mask(d); + gpiochip_disable_irq(gc, d->hwirq); +} + +static void gpiochip_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + gpiochip_enable_irq(gc, d->hwirq); + if (gc->irq.irq_unmask) + gc->irq.irq_unmask(d); +} + static void gpiochip_irq_enable(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); gpiochip_enable_irq(gc, d->hwirq); - if (gc->irq.irq_enable) - gc->irq.irq_enable(d); - else - gc->irq.chip->irq_unmask(d); + gc->irq.irq_enable(d); } static void gpiochip_irq_disable(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - /* - * Since we override .irq_disable() we need to mimic the - * behaviour of __irq_disable() in irq/chip.c. - * First call .irq_disable() if it exists, else mimic the - * behaviour of mask_irq() which calls .irq_mask() if - * it exists. - */ - if (gc->irq.irq_disable) - gc->irq.irq_disable(d); - else if (gc->irq.chip->irq_mask) - gc->irq.chip->irq_mask(d); + gc->irq.irq_disable(d); gpiochip_disable_irq(gc, d->hwirq); } @@ -2521,10 +2526,22 @@ static void gpiochip_set_irq_hooks(struct gpio_chip *gc) "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n"); return; } - gc->irq.irq_enable = irqchip->irq_enable; - gc->irq.irq_disable = irqchip->irq_disable; - irqchip->irq_enable = gpiochip_irq_enable; - irqchip->irq_disable = gpiochip_irq_disable; + + if (irqchip->irq_disable) { + gc->irq.irq_disable = irqchip->irq_disable; + irqchip->irq_disable = gpiochip_irq_disable; + } else { + gc->irq.irq_mask = irqchip->irq_mask; + irqchip->irq_mask = gpiochip_irq_mask; + } + + if (irqchip->irq_enable) { + gc->irq.irq_enable = irqchip->irq_enable; + irqchip->irq_enable = gpiochip_irq_enable; + } else { + gc->irq.irq_unmask = irqchip->irq_unmask; + irqchip->irq_unmask = gpiochip_irq_unmask; + } } /** diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 8c41ae41b6bb..c8bcaf315d03 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -253,6 +253,19 @@ struct gpio_irq_chip { * Store old irq_chip irq_disable callback */ void (*irq_disable)(struct irq_data *data); + /** + * @irq_unmask: + * + * Store old irq_chip irq_unmask callback + */ + void (*irq_unmask)(struct irq_data *data); + + /** + * @irq_mask: + * + * Store old irq_chip irq_mask callback + */ + void (*irq_mask)(struct irq_data *data); }; /** -- cgit v1.2.3 From 6a45b0e2589fafd7fc501a5fbd0ab56689a08124 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 28 May 2020 16:58:43 +0200 Subject: gpiolib: Introduce gpiochip_irqchip_add_domain() The function connects an IRQ domain to a gpiochip and reuses gpiochip_to_irq() which is provided by gpiolib. gpiochip_irqchip_* and regmap_irq partially provide the same functionality. This function will help to connect just the minimal functionality of the gpiochip_irqchip which is needed to work together with regmap-irq. Signed-off-by: Michael Walle Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20200528145845.31436-2-michael@walle.cc Signed-off-by: Linus Walleij --- drivers/gpio/gpiolib.c | 20 ++++++++++++++++++++ include/linux/gpio/driver.h | 3 +++ 2 files changed, 23 insertions(+) (limited to 'include') diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 167860684be6..0a54a759dd40 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2779,6 +2779,26 @@ int gpiochip_irqchip_add_key(struct gpio_chip *gc, } EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_key); +/** + * gpiochip_irqchip_add_domain() - adds an irqdomain to a gpiochip + * @gc: the gpiochip to add the irqchip to + * @domain: the irqdomain to add to the gpiochip + * + * This function adds an IRQ domain to the gpiochip. + */ +int gpiochip_irqchip_add_domain(struct gpio_chip *gc, + struct irq_domain *domain) +{ + if (!domain) + return -EINVAL; + + gc->to_irq = gpiochip_to_irq; + gc->irq.domain = domain; + + return 0; +} +EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_domain); + #else /* CONFIG_GPIOLIB_IRQCHIP */ static inline int gpiochip_add_irqchip(struct gpio_chip *gc, diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index c8bcaf315d03..c4f272af7af5 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -612,6 +612,9 @@ int gpiochip_irqchip_add_key(struct gpio_chip *gc, bool gpiochip_irqchip_irq_valid(const struct gpio_chip *gc, unsigned int offset); +int gpiochip_irqchip_add_domain(struct gpio_chip *gc, + struct irq_domain *domain); + #ifdef CONFIG_LOCKDEP /* -- cgit v1.2.3 From ebe363197e525ffbd279c729421f6f6c24d8d681 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 28 May 2020 16:58:44 +0200 Subject: gpio: add a reusable generic gpio_chip using regmap There are quite a lot simple GPIO controller which are using regmap to access the hardware. This driver tries to be a base to unify existing code into one place. This won't cover everything but it should be a good starting point. It does not implement its own irq_chip because there is already a generic one for regmap based devices. Instead, the irq_chip will be instantiated in the parent driver and its irq domain will be associate to this driver. For now it consists of the usual registers, like set (and an optional clear) data register, an input register and direction registers. Out-of-the-box, it supports consecutive register mappings and mappings where the registers have gaps between them with a linear mapping between GPIO offset and bit position. For weirder mappings the user can register its own .xlate(). Signed-off-by: Michael Walle Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20200528145845.31436-3-michael@walle.cc Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 4 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-regmap.c | 349 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/gpio/regmap.h | 86 +++++++++++ 4 files changed, 440 insertions(+) create mode 100644 drivers/gpio/gpio-regmap.c create mode 100644 include/linux/gpio/regmap.h (limited to 'include') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7d077be10a0f..bcacd9c74aa8 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -73,6 +73,10 @@ config GPIO_GENERIC depends on HAS_IOMEM # Only for IOMEM drivers tristate +config GPIO_REGMAP + depends on REGMAP + tristate + # put drivers in the right section, in alphabetical order # This symbol is selected by both I2C and SPI expanders diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 65bf3940e33c..1e4894e0bf0f 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o # Device drivers. Generally keep list sorted alphabetically +obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o # directly supported by gpio-generic diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c new file mode 100644 index 000000000000..5412cb3b0b2a --- /dev/null +++ b/drivers/gpio/gpio-regmap.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * regmap based generic GPIO driver + * + * Copyright 2020 Michael Walle + */ + +#include +#include +#include +#include +#include + +struct gpio_regmap { + struct device *parent; + struct regmap *regmap; + struct gpio_chip gpio_chip; + + int reg_stride; + int ngpio_per_reg; + unsigned int reg_dat_base; + unsigned int reg_set_base; + unsigned int reg_clr_base; + unsigned int reg_dir_in_base; + unsigned int reg_dir_out_base; + + int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask); + + void *driver_data; +}; + +static unsigned int gpio_regmap_addr(unsigned int addr) +{ + if (addr == GPIO_REGMAP_ADDR_ZERO) + return 0; + + return addr; +} + +static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) +{ + unsigned int line = offset % gpio->ngpio_per_reg; + unsigned int stride = offset / gpio->ngpio_per_reg; + + *reg = base + stride * gpio->reg_stride; + *mask = BIT(line); + + return 0; +} + +static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int ret; + + /* we might not have an output register if we are input only */ + if (gpio->reg_dat_base) + base = gpio_regmap_addr(gpio->reg_dat_base); + else + base = gpio_regmap_addr(gpio->reg_set_base); + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + ret = regmap_read(gpio->regmap, reg, &val); + if (ret) + return ret; + + return !!(val & mask); +} + +static void gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base = gpio_regmap_addr(gpio->reg_set_base); + unsigned int reg, mask; + + gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (val) + regmap_update_bits(gpio->regmap, reg, mask, mask); + else + regmap_update_bits(gpio->regmap, reg, mask, 0); +} + +static void gpio_regmap_set_with_clear(struct gpio_chip *chip, + unsigned int offset, int val) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, reg, mask; + + if (val) + base = gpio_regmap_addr(gpio->reg_set_base); + else + base = gpio_regmap_addr(gpio->reg_clr_base); + + gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + regmap_write(gpio->regmap, reg, mask); +} + +static int gpio_regmap_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int invert, ret; + + if (gpio->reg_dir_out_base) { + base = gpio_regmap_addr(gpio->reg_dir_out_base); + invert = 0; + } else if (gpio->reg_dir_in_base) { + base = gpio_regmap_addr(gpio->reg_dir_in_base); + invert = 1; + } else { + return -EOPNOTSUPP; + } + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + ret = regmap_read(gpio->regmap, reg, &val); + if (ret) + return ret; + + if (!!(val & mask) ^ invert) + return GPIO_LINE_DIRECTION_OUT; + else + return GPIO_LINE_DIRECTION_IN; +} + +static int gpio_regmap_set_direction(struct gpio_chip *chip, + unsigned int offset, bool output) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + unsigned int base, val, reg, mask; + int invert, ret; + + if (gpio->reg_dir_out_base) { + base = gpio_regmap_addr(gpio->reg_dir_out_base); + invert = 0; + } else if (gpio->reg_dir_in_base) { + base = gpio_regmap_addr(gpio->reg_dir_in_base); + invert = 1; + } else { + return -EOPNOTSUPP; + } + + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + return ret; + + if (invert) + val = output ? 0 : mask; + else + val = output ? mask : 0; + + return regmap_update_bits(gpio->regmap, reg, mask, val); +} + +static int gpio_regmap_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + return gpio_regmap_set_direction(chip, offset, false); +} + +static int gpio_regmap_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + gpio_regmap_set(chip, offset, value); + + return gpio_regmap_set_direction(chip, offset, true); +} + +void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data) +{ + gpio->driver_data = data; +} +EXPORT_SYMBOL_GPL(gpio_regmap_set_drvdata); + +void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio) +{ + return gpio->driver_data; +} +EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata); + +/** + * gpio_regmap_register() - Register a generic regmap GPIO controller + * @config: configuration for gpio_regmap + * + * Return: A pointer to the registered gpio_regmap or ERR_PTR error value. + */ +struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config) +{ + struct gpio_regmap *gpio; + struct gpio_chip *chip; + int ret; + + if (!config->parent) + return ERR_PTR(-EINVAL); + + if (!config->ngpio) + return ERR_PTR(-EINVAL); + + /* we need at least one */ + if (!config->reg_dat_base && !config->reg_set_base) + return ERR_PTR(-EINVAL); + + /* if we have a direction register we need both input and output */ + if ((config->reg_dir_out_base || config->reg_dir_in_base) && + (!config->reg_dat_base || !config->reg_set_base)) + return ERR_PTR(-EINVAL); + + /* we don't support having both registers simultaneously for now */ + if (config->reg_dir_out_base && config->reg_dir_in_base) + return ERR_PTR(-EINVAL); + + gpio = kzalloc(sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return ERR_PTR(-ENOMEM); + + gpio->parent = config->parent; + gpio->regmap = config->regmap; + gpio->ngpio_per_reg = config->ngpio_per_reg; + gpio->reg_stride = config->reg_stride; + gpio->reg_mask_xlate = config->reg_mask_xlate; + gpio->reg_dat_base = config->reg_dat_base; + gpio->reg_set_base = config->reg_set_base; + gpio->reg_clr_base = config->reg_clr_base; + gpio->reg_dir_in_base = config->reg_dir_in_base; + gpio->reg_dir_out_base = config->reg_dir_out_base; + + /* if not set, assume there is only one register */ + if (!gpio->ngpio_per_reg) + gpio->ngpio_per_reg = config->ngpio; + + /* if not set, assume they are consecutive */ + if (!gpio->reg_stride) + gpio->reg_stride = 1; + + if (!gpio->reg_mask_xlate) + gpio->reg_mask_xlate = gpio_regmap_simple_xlate; + + chip = &gpio->gpio_chip; + chip->parent = config->parent; + chip->base = -1; + chip->ngpio = config->ngpio; + chip->names = config->names; + chip->label = config->label ?: dev_name(config->parent); + + /* + * If our regmap is fast_io we should probably set can_sleep to false. + * Right now, the regmap doesn't save this property, nor is there any + * access function for it. + * The only regmap type which uses fast_io is regmap-mmio. For now, + * assume a safe default of true here. + */ + chip->can_sleep = true; + + chip->get = gpio_regmap_get; + if (gpio->reg_set_base && gpio->reg_clr_base) + chip->set = gpio_regmap_set_with_clear; + else if (gpio->reg_set_base) + chip->set = gpio_regmap_set; + + if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) { + chip->get_direction = gpio_regmap_get_direction; + chip->direction_input = gpio_regmap_direction_input; + chip->direction_output = gpio_regmap_direction_output; + } + + ret = gpiochip_add_data(chip, gpio); + if (ret < 0) + goto err_free_gpio; + + if (config->irq_domain) { + ret = gpiochip_irqchip_add_domain(chip, config->irq_domain); + if (ret) + goto err_remove_gpiochip; + } + + return gpio; + +err_remove_gpiochip: + gpiochip_remove(chip); +err_free_gpio: + kfree(gpio); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(gpio_regmap_register); + +/** + * gpio_regmap_unregister() - Unregister a generic regmap GPIO controller + * @gpio: gpio_regmap device to unregister + */ +void gpio_regmap_unregister(struct gpio_regmap *gpio) +{ + gpiochip_remove(&gpio->gpio_chip); + kfree(gpio); +} +EXPORT_SYMBOL_GPL(gpio_regmap_unregister); + +static void devm_gpio_regmap_unregister(struct device *dev, void *res) +{ + gpio_regmap_unregister(*(struct gpio_regmap **)res); +} + +/** + * devm_gpio_regmap_register() - resource managed gpio_regmap_register() + * @dev: device that is registering this GPIO device + * @config: configuration for gpio_regmap + * + * Managed gpio_regmap_register(). For generic regmap GPIO device registered by + * this function, gpio_regmap_unregister() is automatically called on driver + * detach. See gpio_regmap_register() for more information. + * + * Return: A pointer to the registered gpio_regmap or ERR_PTR error value. + */ +struct gpio_regmap *devm_gpio_regmap_register(struct device *dev, + const struct gpio_regmap_config *config) +{ + struct gpio_regmap **ptr, *gpio; + + ptr = devres_alloc(devm_gpio_regmap_unregister, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + gpio = gpio_regmap_register(config); + if (!IS_ERR(gpio)) { + *ptr = gpio; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return gpio; +} +EXPORT_SYMBOL_GPL(devm_gpio_regmap_register); + +MODULE_AUTHOR("Michael Walle "); +MODULE_DESCRIPTION("GPIO generic regmap driver core"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h new file mode 100644 index 000000000000..4c1e6b34e824 --- /dev/null +++ b/include/linux/gpio/regmap.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_GPIO_REGMAP_H +#define _LINUX_GPIO_REGMAP_H + +struct device; +struct gpio_regmap; +struct irq_domain; +struct regmap; + +#define GPIO_REGMAP_ADDR_ZERO ((unsigned long)(-1)) +#define GPIO_REGMAP_ADDR(addr) ((addr) ? : GPIO_REGMAP_ADDR_ZERO) + +/** + * struct gpio_regmap_config - Description of a generic regmap gpio_chip. + * @parent: The parent device + * @regmap: The regmap used to access the registers + * given, the name of the device is used + * @label: (Optional) Descriptive name for GPIO controller. + * If not given, the name of the device is used. + * @ngpio: Number of GPIOs + * @names: (Optional) Array of names for gpios + * @reg_dat_base: (Optional) (in) register base address + * @reg_set_base: (Optional) set register base address + * @reg_clr_base: (Optional) clear register base address + * @reg_dir_in_base: (Optional) in setting register base address + * @reg_dir_out_base: (Optional) out setting register base address + * @reg_stride: (Optional) May be set if the registers (of the + * same type, dat, set, etc) are not consecutive. + * @ngpio_per_reg: Number of GPIOs per register + * @irq_domain: (Optional) IRQ domain if the controller is + * interrupt-capable + * @reg_mask_xlate: (Optional) Translates base address and GPIO + * offset to a register/bitmask pair. If not + * given the default gpio_regmap_simple_xlate() + * is used. + * + * The ->reg_mask_xlate translates a given base address and GPIO offset to + * register and mask pair. The base address is one of the given register + * base addresses in this structure. + * + * Although all register base addresses are marked as optional, there are + * several rules: + * 1. if you only have @reg_dat_base set, then it is input-only + * 2. if you only have @reg_set_base set, then it is output-only + * 3. if you have either @reg_dir_in_base or @reg_dir_out_base set, then + * you have to set both @reg_dat_base and @reg_set_base + * 4. if you have @reg_set_base set, you may also set @reg_clr_base to have + * two different registers for setting and clearing the output. This is + * also valid for the output-only case. + * 5. @reg_dir_in_base and @reg_dir_out_base are exclusive; is there really + * hardware which has redundant registers? + * + * Note: All base addresses may have the special value %GPIO_REGMAP_ADDR_ZERO + * which forces the address to the value 0. + */ +struct gpio_regmap_config { + struct device *parent; + struct regmap *regmap; + + const char *label; + int ngpio; + const char *const *names; + + unsigned int reg_dat_base; + unsigned int reg_set_base; + unsigned int reg_clr_base; + unsigned int reg_dir_in_base; + unsigned int reg_dir_out_base; + int reg_stride; + int ngpio_per_reg; + struct irq_domain *irq_domain; + + int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask); +}; + +struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config); +void gpio_regmap_unregister(struct gpio_regmap *gpio); +struct gpio_regmap *devm_gpio_regmap_register(struct device *dev, + const struct gpio_regmap_config *config); +void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data); +void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio); + +#endif /* _LINUX_GPIO_REGMAP_H */ -- cgit v1.2.3