diff options
Diffstat (limited to 'drivers/gpio')
| -rw-r--r-- | drivers/gpio/Kconfig | 23 | ||||
| -rw-r--r-- | drivers/gpio/Makefile | 3 | ||||
| -rw-r--r-- | drivers/gpio/gpio-bcm-virt.c | 212 | ||||
| -rw-r--r-- | drivers/gpio/gpio-brcmstb.c | 156 | ||||
| -rw-r--r-- | drivers/gpio/gpio-fsm.c | 1210 | ||||
| -rw-r--r-- | drivers/gpio/gpio-mmio.c | 189 | ||||
| -rw-r--r-- | drivers/gpio/gpio-pca953x.c | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-pwm.c | 143 | ||||
| -rw-r--r-- | drivers/gpio/gpiolib.c | 15 |
9 files changed, 1848 insertions, 104 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7ee3afbc2b05..65e3a016a0f3 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -225,6 +225,12 @@ config GPIO_BCM_XGS_IPROC help Say yes here to enable GPIO support for Broadcom XGS iProc SoCs. +config GPIO_BCM_VIRT + bool "Broadcom Virt GPIO" + depends on OF_GPIO && RASPBERRYPI_FIRMWARE && (ARCH_BCM2835 || COMPILE_TEST) + help + Turn on virtual GPIO support for Broadcom BCM283X chips. + config GPIO_BLZP1600 tristate "Blaize BLZP1600 GPIO support" default y if ARCH_BLAIZE @@ -576,6 +582,14 @@ config GPIO_POLARFIRE_SOC help Say yes here to support the GPIO controllers on Microchip FPGAs. +config GPIO_PWM + tristate "PWM chip GPIO" + depends on OF_GPIO + depends on PWM + help + Turn on support for exposing a PWM chip as a GPIO + driver. + config GPIO_PXA bool "PXA GPIO support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST @@ -1422,6 +1436,15 @@ config GPIO_ELKHARTLAKE To compile this driver as a module, choose M here: the module will be called gpio-elkhartlake. +config GPIO_FSM + tristate "GPIO FSM support" + help + The GPIO FSM driver allows the creation of state machines for + manipulating GPIOs (both real and virtual), with state transitions + triggered by GPIO edges or delays. + + If unsure, say N. + config GPIO_JANZ_TTL tristate "Janz VMOD-TTL Digital IO Module" depends on MFD_JANZ_CMODIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index ec296fa14bfd..17442badd2d5 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o +obj-$(CONFIG_GPIO_BCM_VIRT) += gpio-bcm-virt.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o @@ -69,6 +70,7 @@ obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o +obj-$(CONFIG_GPIO_FSM) += gpio-fsm.o obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o @@ -145,6 +147,7 @@ obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o obj-$(CONFIG_GPIO_POLARFIRE_SOC) += gpio-mpfs.o +obj-$(CONFIG_GPIO_PWM) += gpio-pwm.o obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o diff --git a/drivers/gpio/gpio-bcm-virt.c b/drivers/gpio/gpio-bcm-virt.c new file mode 100644 index 000000000000..45069c7f0b5a --- /dev/null +++ b/drivers/gpio/gpio-bcm-virt.c @@ -0,0 +1,212 @@ +/* + * brcmvirt GPIO driver + * + * Copyright (C) 2012,2013 Dom Cobley <[email protected]> + * Based on gpio-clps711x.c by Alexander Shiyan <[email protected]> + * + * 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. + */ + +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define MODULE_NAME "brcmvirt-gpio" +#define NUM_GPIO 2 + +struct brcmvirt_gpio { + struct gpio_chip gc; + u32 __iomem *ts_base; + /* two packed 16-bit counts of enabled and disables + Allows host to detect a brief enable that was missed */ + u32 enables_disables[NUM_GPIO]; + dma_addr_t bus_addr; +}; + +static int brcmvirt_gpio_dir_in(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return -EINVAL; +} + +static int brcmvirt_gpio_dir_out(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return 0; +} + +static int brcmvirt_gpio_get(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + unsigned v; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + v = readl(gpio->ts_base + off); + return (s16)((v >> 16) - v) > 0; +} + +static void brcmvirt_gpio_set(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + u16 enables, disables; + s16 diff; + bool lit; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + enables = gpio->enables_disables[off] >> 16; + disables = gpio->enables_disables[off] >> 0; + diff = (s16)(enables - disables); + lit = diff > 0; + if ((val && lit) || (!val && !lit)) + return; + if (val) + enables++; + else + disables++; + diff = (s16)(enables - disables); + BUG_ON(diff != 0 && diff != 1); + gpio->enables_disables[off] = (enables << 16) | (disables << 0); + writel(gpio->enables_disables[off], gpio->ts_base + off); +} + +static int brcmvirt_gpio_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev); + struct device_node *fw_node; + struct rpi_firmware *fw; + struct brcmvirt_gpio *ucb; + u32 gpiovirtbuf; + + fw_node = of_get_parent(np); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = devm_rpi_firmware_get(&pdev->dev, fw_node); + of_node_put(fw_node); + if (!fw) + return -EPROBE_DEFER; + + ucb = devm_kzalloc(dev, sizeof *ucb, GFP_KERNEL); + if (!ucb) { + err = -EINVAL; + goto out; + } + + ucb->ts_base = dma_alloc_coherent(dev, PAGE_SIZE, &ucb->bus_addr, GFP_KERNEL); + if (!ucb->ts_base) { + pr_err("[%s]: failed to dma_alloc_coherent(%ld)\n", + __func__, PAGE_SIZE); + err = -ENOMEM; + goto out; + } + + gpiovirtbuf = (u32)ucb->bus_addr; + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err || gpiovirtbuf != 0) { + dev_warn(dev, "Failed to set gpiovirtbuf, trying to get err:%x\n", err); + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->ts_base = 0; + ucb->bus_addr = 0; + } + + if (!ucb->ts_base) { + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err) { + dev_err(dev, "Failed to get gpiovirtbuf\n"); + goto out; + } + + if (!gpiovirtbuf) { + dev_err(dev, "No virtgpio buffer\n"); + err = -ENOENT; + goto out; + } + + // mmap the physical memory + gpiovirtbuf &= ~0xc0000000; + ucb->ts_base = ioremap(gpiovirtbuf, 4096); + if (ucb->ts_base == NULL) { + dev_err(dev, "Failed to map physical address\n"); + err = -ENOENT; + goto out; + } + ucb->bus_addr = 0; + } + ucb->gc.parent = dev; + ucb->gc.label = MODULE_NAME; + ucb->gc.owner = THIS_MODULE; + ucb->gc.base = -1; + ucb->gc.ngpio = NUM_GPIO; + + ucb->gc.direction_input = brcmvirt_gpio_dir_in; + ucb->gc.direction_output = brcmvirt_gpio_dir_out; + ucb->gc.get = brcmvirt_gpio_get; + ucb->gc.set = brcmvirt_gpio_set; + ucb->gc.can_sleep = true; + + err = gpiochip_add_data(&ucb->gc, NULL); + if (err) + goto out; + + platform_set_drvdata(pdev, ucb); + + return 0; +out: + if (ucb->bus_addr) { + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->bus_addr = 0; + ucb->ts_base = NULL; + } else if (ucb->ts_base) { + iounmap(ucb->ts_base); + ucb->ts_base = NULL; + } + return err; +} + +static void brcmvirt_gpio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct brcmvirt_gpio *ucb = platform_get_drvdata(pdev); + + gpiochip_remove(&ucb->gc); + if (ucb->bus_addr) + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + else if (ucb->ts_base) + iounmap(ucb->ts_base); +} + +static const struct of_device_id __maybe_unused brcmvirt_gpio_ids[] = { + { .compatible = "brcm,bcm2835-virtgpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, brcmvirt_gpio_ids); + +static struct platform_driver brcmvirt_gpio_driver = { + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(brcmvirt_gpio_ids), + }, + .probe = brcmvirt_gpio_probe, + .remove = brcmvirt_gpio_remove, +}; +module_platform_driver(brcmvirt_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dom Cobley <[email protected]>"); +MODULE_DESCRIPTION("brcmvirt GPIO driver"); +MODULE_ALIAS("platform:brcmvirt-gpio"); diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index f40c9472588b..a078de2b807d 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -24,16 +24,16 @@ enum gio_reg_index { NUMBER_OF_GIO_REGISTERS }; -#define GIO_BANK_SIZE (NUMBER_OF_GIO_REGISTERS * sizeof(u32)) -#define GIO_BANK_OFF(bank, off) (((bank) * GIO_BANK_SIZE) + (off * sizeof(u32))) -#define GIO_ODEN(bank) GIO_BANK_OFF(bank, GIO_REG_ODEN) -#define GIO_DATA(bank) GIO_BANK_OFF(bank, GIO_REG_DATA) -#define GIO_IODIR(bank) GIO_BANK_OFF(bank, GIO_REG_IODIR) -#define GIO_EC(bank) GIO_BANK_OFF(bank, GIO_REG_EC) -#define GIO_EI(bank) GIO_BANK_OFF(bank, GIO_REG_EI) -#define GIO_MASK(bank) GIO_BANK_OFF(bank, GIO_REG_MASK) -#define GIO_LEVEL(bank) GIO_BANK_OFF(bank, GIO_REG_LEVEL) -#define GIO_STAT(bank) GIO_BANK_OFF(bank, GIO_REG_STAT) +#define GIO_BANK_SIZE (NUMBER_OF_GIO_REGISTERS * sizeof(u32)) +#define GIO_BANK_OFF(bank, off) (((bank) * GIO_BANK_SIZE) + (off * sizeof(u32))) +#define GIO_ODEN(bank) GIO_BANK_OFF(bank, GIO_REG_ODEN) +#define GIO_DATA(bank) GIO_BANK_OFF(bank, GIO_REG_DATA) +#define GIO_IODIR(bank) GIO_BANK_OFF(bank, GIO_REG_IODIR) +#define GIO_EC(bank) GIO_BANK_OFF(bank, GIO_REG_EC) +#define GIO_EI(bank) GIO_BANK_OFF(bank, GIO_REG_EI) +#define GIO_MASK(bank) GIO_BANK_OFF(bank, GIO_REG_MASK) +#define GIO_LEVEL(bank) GIO_BANK_OFF(bank, GIO_REG_LEVEL) +#define GIO_STAT(bank) GIO_BANK_OFF(bank, GIO_REG_STAT) struct brcmstb_gpio_bank { struct list_head node; @@ -56,10 +56,10 @@ struct brcmstb_gpio_priv { int parent_wake_irq; }; -#define MAX_GPIO_PER_BANK 32 -#define GPIO_BANK(gpio) ((gpio) >> 5) +#define MAX_GPIO_PER_BANK 32 +#define GPIO_BANK(gpio) ((gpio) >> 5) /* assumes MAX_GPIO_PER_BANK is a multiple of 2 */ -#define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1)) +#define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1)) static inline struct brcmstb_gpio_priv * brcmstb_gpio_gc_to_priv(struct gpio_chip *gc) @@ -73,8 +73,10 @@ __brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank) { void __iomem *reg_base = bank->parent_priv->reg_base; - return gpio_generic_read_reg(&bank->chip, reg_base + GIO_STAT(bank->id)) & - gpio_generic_read_reg(&bank->chip, reg_base + GIO_MASK(bank->id)); + return gpio_generic_read_reg(&bank->chip, + reg_base + GIO_STAT(bank->id)) & + gpio_generic_read_reg(&bank->chip, + reg_base + GIO_MASK(bank->id)); } static unsigned long @@ -96,7 +98,7 @@ static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq, } static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, - unsigned int hwirq, bool enable) + unsigned int hwirq, bool enable) { struct brcmstb_gpio_priv *priv = bank->parent_priv; u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); @@ -110,8 +112,8 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, imask |= mask; else imask &= ~mask; - gpio_generic_write_reg(&bank->chip, - priv->reg_base + GIO_MASK(bank->id), imask); + gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_MASK(bank->id), + imask); } static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset) @@ -150,8 +152,8 @@ static void brcmstb_gpio_irq_ack(struct irq_data *d) struct brcmstb_gpio_priv *priv = bank->parent_priv; u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); - gpio_generic_write_reg(&bank->chip, - priv->reg_base + GIO_STAT(bank->id), mask); + gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_STAT(bank->id), + mask); } static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) @@ -187,7 +189,7 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) break; case IRQ_TYPE_EDGE_BOTH: level = 0; - edge_config = 0; /* don't care, but want known value */ + edge_config = 0; /* don't care, but want known value */ edge_insensitive = mask; break; default: @@ -196,18 +198,20 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) guard(gpio_generic_lock_irqsave)(&bank->chip); - iedge_config = gpio_generic_read_reg(&bank->chip, - priv->reg_base + GIO_EC(bank->id)) & ~mask; - iedge_insensitive = gpio_generic_read_reg(&bank->chip, - priv->reg_base + GIO_EI(bank->id)) & ~mask; + iedge_config = gpio_generic_read_reg( + &bank->chip, priv->reg_base + GIO_EC(bank->id)) & + ~mask; + iedge_insensitive = + gpio_generic_read_reg(&bank->chip, + priv->reg_base + GIO_EI(bank->id)) & + ~mask; ilevel = gpio_generic_read_reg(&bank->chip, - priv->reg_base + GIO_LEVEL(bank->id)) & ~mask; + priv->reg_base + GIO_LEVEL(bank->id)) & + ~mask; - gpio_generic_write_reg(&bank->chip, - priv->reg_base + GIO_EC(bank->id), + gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_EC(bank->id), iedge_config | edge_config); - gpio_generic_write_reg(&bank->chip, - priv->reg_base + GIO_EI(bank->id), + gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_EI(bank->id), iedge_insensitive | edge_insensitive); gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_LEVEL(bank->id), @@ -217,7 +221,7 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) } static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv, - unsigned int enable) + unsigned int enable) { int ret = 0; @@ -273,9 +277,10 @@ static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank) for_each_set_bit(offset, &status, 32) { if (offset >= bank->width) - dev_warn(&priv->pdev->dev, - "IRQ for invalid GPIO (bank=%d, offset=%d)\n", - bank->id, offset); + dev_warn( + &priv->pdev->dev, + "IRQ for invalid GPIO (bank=%d, offset=%d)\n", + bank->id, offset); generic_handle_domain_irq(domain, hwbase + offset); } } @@ -297,8 +302,9 @@ static void brcmstb_gpio_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } -static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank( - struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq) +static struct brcmstb_gpio_bank * +brcmstb_gpio_hwirq_to_bank(struct brcmstb_gpio_priv *priv, + irq_hw_number_t hwirq) { struct brcmstb_gpio_bank *bank; int i = 0; @@ -319,9 +325,8 @@ static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank( static struct lock_class_key brcmstb_gpio_irq_lock_class; static struct lock_class_key brcmstb_gpio_irq_request_class; - static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hwirq) + irq_hw_number_t hwirq) { struct brcmstb_gpio_priv *priv = d->host_data; struct brcmstb_gpio_bank *bank = @@ -332,8 +337,8 @@ static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq, if (!bank) return -EINVAL; - dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n", - irq, (int)hwirq, bank->id); + dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n", irq, + (int)hwirq, bank->id); ret = irq_set_chip_data(irq, &bank->chip.gc); if (ret < 0) return ret; @@ -358,15 +363,17 @@ static const struct irq_domain_ops brcmstb_gpio_irq_domain_ops = { /* Make sure that the number of banks matches up between properties */ static int brcmstb_gpio_sanity_check_banks(struct device *dev, - struct device_node *np, struct resource *res) + struct device_node *np, + struct resource *res) { int res_num_banks = resource_size(res) / GIO_BANK_SIZE; int num_banks = of_property_count_u32_elems(np, "brcm,gpio-bank-widths"); if (res_num_banks != num_banks) { - dev_err(dev, "Mismatch in banks: res had %d, bank-widths had %d\n", - res_num_banks, num_banks); + dev_err(dev, + "Mismatch in banks: res had %d, bank-widths had %d\n", + res_num_banks, num_banks); return -EINVAL; } else { return 0; @@ -400,7 +407,8 @@ static void brcmstb_gpio_remove(struct platform_device *pdev) } static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, - const struct of_phandle_args *gpiospec, u32 *flags) + const struct of_phandle_args *gpiospec, + u32 *flags) { struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); @@ -419,7 +427,8 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, return -EINVAL; if (unlikely(offset >= bank->width)) { - dev_warn_ratelimited(&priv->pdev->dev, + dev_warn_ratelimited( + &priv->pdev->dev, "Received request for invalid GPIO offset %d\n", gpiospec->args[0]); } @@ -432,14 +441,15 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, /* priv->parent_irq and priv->num_gpios must be set before calling */ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, - struct brcmstb_gpio_priv *priv) + struct brcmstb_gpio_priv *priv) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; int err; - priv->irq_domain = irq_domain_create_linear(dev_fwnode(dev), priv->num_gpios, - &brcmstb_gpio_irq_domain_ops, priv); + priv->irq_domain = + irq_domain_create_linear(dev_fwnode(dev), priv->num_gpios, + &brcmstb_gpio_irq_domain_ops, priv); if (!priv->irq_domain) { dev_err(dev, "Couldn't allocate IRQ domain\n"); return -ENXIO; @@ -449,7 +459,8 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, priv->parent_wake_irq = platform_get_irq(pdev, 1); if (priv->parent_wake_irq < 0) { priv->parent_wake_irq = 0; - dev_warn(dev, + dev_warn( + dev, "Couldn't get wake IRQ - GPIOs will not be able to wake from sleep"); } else { /* @@ -460,8 +471,8 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, device_wakeup_enable(dev); err = devm_request_irq(dev, priv->parent_wake_irq, brcmstb_gpio_wake_irq_handler, - IRQF_SHARED, - "brcmstb-gpio-wake", priv); + IRQF_SHARED, "brcmstb-gpio-wake", + priv); if (err < 0) { dev_err(dev, "Couldn't request wake IRQ"); @@ -498,8 +509,9 @@ static void brcmstb_gpio_bank_save(struct brcmstb_gpio_priv *priv, unsigned int i; for (i = 0; i < GIO_REG_STAT; i++) - bank->saved_regs[i] = gpio_generic_read_reg(&bank->chip, - priv->reg_base + GIO_BANK_OFF(bank->id, i)); + bank->saved_regs[i] = gpio_generic_read_reg( + &bank->chip, + priv->reg_base + GIO_BANK_OFF(bank->id, i)); } static void brcmstb_gpio_quiesce(struct device *dev, bool save) @@ -540,9 +552,9 @@ static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv, unsigned int i; for (i = 0; i < GIO_REG_STAT; i++) - gpio_generic_write_reg(&bank->chip, - priv->reg_base + GIO_BANK_OFF(bank->id, i), - bank->saved_regs[i]); + gpio_generic_write_reg( + &bank->chip, priv->reg_base + GIO_BANK_OFF(bank->id, i), + bank->saved_regs[i]); } static int brcmstb_gpio_suspend(struct device *dev) @@ -573,12 +585,12 @@ static int brcmstb_gpio_resume(struct device *dev) } #else -#define brcmstb_gpio_suspend NULL -#define brcmstb_gpio_resume NULL +#define brcmstb_gpio_suspend NULL +#define brcmstb_gpio_resume NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops brcmstb_gpio_pm_ops = { - .suspend_noirq = brcmstb_gpio_suspend, + .suspend_noirq = brcmstb_gpio_suspend, .resume_noirq = brcmstb_gpio_resume, }; @@ -632,6 +644,8 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) #if defined(CONFIG_MIPS) && defined(__BIG_ENDIAN) flags = GPIO_GENERIC_BIG_ENDIAN_BYTE_ORDER; #endif + if (of_property_read_bool(np, "brcm,gpio-direct")) + flags |= GPIO_GENERIC_REG_DIRECT; of_property_for_each_u32(np, "brcm,gpio-bank-widths", bank_width) { struct brcmstb_gpio_bank *bank; @@ -672,7 +686,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) * and direction bits have 0 = output and 1 = input */ - config = (struct gpio_generic_chip_config) { + config = (struct gpio_generic_chip_config){ .dev = dev, .sz = 4, .dat = reg_base + GIO_DATA(bank->id), @@ -682,12 +696,15 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) err = gpio_generic_chip_init(&bank->chip, &config); if (err) { - dev_err(dev, "failed to initialize generic GPIO chip\n"); + dev_err(dev, + "failed to initialize generic GPIO chip\n"); goto fail; } gc->owner = THIS_MODULE; - gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np); + gc->label = devm_kasprintf(dev, GFP_KERNEL, "gpio-brcmstb@%zx", + (size_t)res->start + + GIO_BANK_OFF(bank->id, 0)); if (!gc->label) { err = -ENOMEM; goto fail; @@ -695,7 +712,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) gc->of_gpio_n_cells = 2; gc->of_xlate = brcmstb_gpio_of_xlate; /* not all ngpio lines are valid, will use bank width later */ - gc->ngpio = MAX_GPIO_PER_BANK; + gc->ngpio = bank_width; gc->offset = bank->id * MAX_GPIO_PER_BANK; gc->request = gpiochip_generic_request; gc->free = gpiochip_generic_free; @@ -706,14 +723,17 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) * Mask all interrupts by default, since wakeup interrupts may * be retained from S5 cold boot */ - need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); - gpio_generic_write_reg(&bank->chip, - reg_base + GIO_MASK(bank->id), 0); + if (priv->parent_irq > 0) { + need_wakeup_event |= + !!__brcmstb_gpio_get_active_irqs(bank); + gpio_generic_write_reg( + &bank->chip, reg_base + GIO_MASK(bank->id), 0); + } err = gpiochip_add_data(gc, bank); if (err) { dev_err(dev, "Could not add gpiochip for bank %d\n", - bank->id); + bank->id); goto fail; } num_gpios += gc->ngpio; @@ -740,7 +760,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) return 0; fail: - (void) brcmstb_gpio_remove(pdev); + (void)brcmstb_gpio_remove(pdev); return err; } diff --git a/drivers/gpio/gpio-fsm.c b/drivers/gpio/gpio-fsm.c new file mode 100644 index 000000000000..785827c70ed7 --- /dev/null +++ b/drivers/gpio/gpio-fsm.c @@ -0,0 +1,1210 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO FSM driver + * + * This driver implements simple state machines that allow real GPIOs to be + * controlled in response to inputs from other GPIOs - real and soft/virtual - + * and time delays. It can: + * + create dummy GPIOs for drivers that demand them + * + drive multiple GPIOs from a single input, with optional delays + * + add a debounce circuit to an input + * + drive pattern sequences onto LEDs + * etc. + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#include <dt-bindings/gpio/gpio-fsm.h> + +#define MODULE_NAME "gpio-fsm" + +#define GF_IO_TYPE(x) ((u32)(x) & 0xffff) +#define GF_IO_INDEX(x) ((u32)(x) >> 16) + +enum { + SIGNAL_GPIO, + SIGNAL_SOFT +}; + +enum { + INPUT_GPIO, + INPUT_SOFT +}; + +enum { + SYM_UNDEFINED, + SYM_NAME, + SYM_SET, + SYM_START, + SYM_SHUTDOWN, + + SYM_MAX +}; + +struct soft_gpio { + int dir; + int value; +}; + +struct input_gpio_state { + struct gpio_fsm *gf; + struct gpio_desc *desc; + struct fsm_state *target; + int index; + int value; + int irq; + bool enabled; + bool active_low; +}; + +struct gpio_event { + int index; + int value; + struct fsm_state *target; +}; + +struct symtab_entry { + const char *name; + void *value; + struct symtab_entry *next; +}; + +struct output_signal { + u8 type; + u8 value; + u16 index; +}; + +struct fsm_state { + const char *name; + struct output_signal *signals; + struct gpio_event *gpio_events; + struct gpio_event *soft_events; + struct fsm_state *delay_target; + struct fsm_state *shutdown_target; + unsigned int num_signals; + unsigned int num_gpio_events; + unsigned int num_soft_events; + unsigned int delay_ms; + unsigned int shutdown_ms; +}; + +struct gpio_fsm { + struct gpio_chip gc; + struct device *dev; + spinlock_t spinlock; + struct work_struct work; + struct timer_list timer; + wait_queue_head_t shutdown_event; + struct fsm_state *states; + struct input_gpio_state *input_gpio_states; + struct gpio_descs *input_gpios; + struct gpio_descs *output_gpios; + struct soft_gpio *soft_gpios; + struct fsm_state *start_state; + struct fsm_state *shutdown_state; + unsigned int num_states; + unsigned int num_output_gpios; + unsigned int num_input_gpios; + unsigned int num_soft_gpios; + unsigned int shutdown_timeout_ms; + unsigned int shutdown_jiffies; + + struct fsm_state *current_state; + struct fsm_state *next_state; + struct fsm_state *delay_target_state; + unsigned long delay_jiffies; + int delay_ms; + unsigned int debug; + bool shutting_down; + struct symtab_entry *symtab; +}; + +static struct symtab_entry *do_add_symbol(struct symtab_entry **symtab, + const char *name, void *value) +{ + struct symtab_entry **p = symtab; + + while (*p && strcmp((*p)->name, name)) + p = &(*p)->next; + + if (*p) { + /* This is an existing symbol */ + if ((*p)->value) { + /* Already defined */ + if (value) { + if ((uintptr_t)value < SYM_MAX) + return ERR_PTR(-EINVAL); + else + return ERR_PTR(-EEXIST); + } + } else { + /* Undefined */ + (*p)->value = value; + } + } else { + /* This is a new symbol */ + *p = kmalloc(sizeof(struct symtab_entry), GFP_KERNEL); + if (*p) { + (*p)->name = name; + (*p)->value = value; + (*p)->next = NULL; + } + } + return *p; +} + +static int add_symbol(struct symtab_entry **symtab, + const char *name, void *value) +{ + struct symtab_entry *sym = do_add_symbol(symtab, name, value); + + return PTR_ERR_OR_ZERO(sym); +} + +static struct symtab_entry *get_symbol(struct symtab_entry **symtab, + const char *name) +{ + struct symtab_entry *sym = do_add_symbol(symtab, name, NULL); + + if (IS_ERR(sym)) + return NULL; + return sym; +} + +static void free_symbols(struct symtab_entry **symtab) +{ + struct symtab_entry *sym = *symtab; + void *p; + + *symtab = NULL; + while (sym) { + p = sym; + sym = sym->next; + kfree(p); + } +} + +static void gpio_fsm_set_soft(struct gpio_fsm *gf, + unsigned int off, int val); + +static void gpio_fsm_enter_state(struct gpio_fsm *gf, + struct fsm_state *state) +{ + struct input_gpio_state *inp_state; + struct output_signal *signal; + struct gpio_event *event; + struct gpio_desc *gpiod; + struct soft_gpio *soft; + int value; + int i; + + dev_dbg(gf->dev, "enter_state(%s)\n", state->name); + + gf->current_state = state; + gf->delay_target_state = NULL; + + // 1. Apply any listed signals + for (i = 0; i < state->num_signals; i++) { + signal = &state->signals[i]; + + if (gf->debug) + dev_info(gf->dev, " set %s %d->%d\n", + (signal->type == SIGNAL_GPIO) ? "GF_OUT" : + "GF_SOFT", + signal->index, signal->value); + switch (signal->type) { + case SIGNAL_GPIO: + gpiod = gf->output_gpios->desc[signal->index]; + gpiod_set_value_cansleep(gpiod, signal->value); + break; + case SIGNAL_SOFT: + soft = &gf->soft_gpios[signal->index]; + gpio_fsm_set_soft(gf, signal->index, signal->value); + break; + } + } + + // 2. Exit if successfully reached shutdown state + if (gf->shutting_down && state == state->shutdown_target) { + wake_up(&gf->shutdown_event); + return; + } + + // 3. Schedule a timer callback if shutting down + if (state->shutdown_target) { + // Remember the absolute shutdown time in case remove is called + // at a later time. + gf->shutdown_jiffies = + jiffies + msecs_to_jiffies(state->shutdown_ms); + + if (gf->shutting_down) { + gf->delay_jiffies = gf->shutdown_jiffies; + gf->delay_target_state = state->shutdown_target; + gf->delay_ms = state->shutdown_ms; + mod_timer(&gf->timer, gf->delay_jiffies); + } + } + + // During shutdown, skip everything else + if (gf->shutting_down) + return; + + // Otherwise record what the shutdown time would be + gf->shutdown_jiffies = jiffies + msecs_to_jiffies(state->shutdown_ms); + + // 4. Check soft inputs for transitions to take + for (i = 0; i < state->num_soft_events; i++) { + event = &state->soft_events[i]; + if (gf->soft_gpios[event->index].value == event->value) { + if (gf->debug) + dev_info(gf->dev, + "GF_SOFT %d=%d -> %s\n", event->index, + event->value, event->target->name); + gpio_fsm_enter_state(gf, event->target); + return; + } + } + + // 5. Check GPIOs for transitions to take, enabling the IRQs + for (i = 0; i < state->num_gpio_events; i++) { + event = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[event->index]; + inp_state->target = event->target; + inp_state->value = event->value; + inp_state->enabled = true; + + value = gpiod_get_value_cansleep(gf->input_gpios->desc[event->index]); + + // Clear stale event state + disable_irq(inp_state->irq); + + irq_set_irq_type(inp_state->irq, + (inp_state->value ^ inp_state->active_low) ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING); + enable_irq(inp_state->irq); + + if (value == event->value && inp_state->target) { + if (gf->debug) + dev_info(gf->dev, + "GF_IN %d=%d -> %s\n", event->index, + event->value, event->target->name); + gpio_fsm_enter_state(gf, event->target); + return; + } + } + + // 6. Schedule a timer callback if delay_target + if (state->delay_target) { + gf->delay_target_state = state->delay_target; + gf->delay_jiffies = jiffies + + msecs_to_jiffies(state->delay_ms); + gf->delay_ms = state->delay_ms; + mod_timer(&gf->timer, gf->delay_jiffies); + } +} + +static void gpio_fsm_go_to_state(struct gpio_fsm *gf, + struct fsm_state *new_state) +{ + struct input_gpio_state *inp_state; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "go_to_state(%s)\n", + new_state ? new_state->name : "<unset>"); + + state = gf->current_state; + + /* Disable any enabled GPIO IRQs */ + for (i = 0; i < state->num_gpio_events; i++) { + gp_ev = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[gp_ev->index]; + if (inp_state->enabled) { + inp_state->enabled = false; + irq_set_irq_type(inp_state->irq, + IRQF_TRIGGER_NONE); + } + } + + gpio_fsm_enter_state(gf, new_state); +} + +static void gpio_fsm_go_to_state_deferred(struct gpio_fsm *gf, + struct fsm_state *new_state) +{ + struct input_gpio_state *inp_state; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "go_to_state_deferred(%s)\n", + new_state ? new_state->name : "<unset>"); + + spin_lock(&gf->spinlock); + + if (gf->next_state) { + /* Something else has already requested a transition */ + spin_unlock(&gf->spinlock); + return; + } + + gf->next_state = new_state; + state = gf->current_state; + + /* Disarm any GPIO IRQs */ + for (i = 0; i < state->num_gpio_events; i++) { + gp_ev = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[gp_ev->index]; + inp_state->target = NULL; + } + + spin_unlock(&gf->spinlock); + + schedule_work(&gf->work); +} + +static void gpio_fsm_work(struct work_struct *work) +{ + struct fsm_state *new_state; + struct gpio_fsm *gf; + + gf = container_of(work, struct gpio_fsm, work); + spin_lock(&gf->spinlock); + new_state = gf->next_state; + gf->next_state = NULL; + spin_unlock(&gf->spinlock); + + gpio_fsm_go_to_state(gf, new_state); +} + +static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id) +{ + struct input_gpio_state *inp_state = dev_id; + struct gpio_fsm *gf = inp_state->gf; + struct fsm_state *target; + + target = inp_state->target; + if (!target) + return IRQ_NONE; + + /* If the IRQ has fired then the desired state _must_ have occurred */ + inp_state->enabled = false; + irq_set_irq_type(inp_state->irq, IRQF_TRIGGER_NONE); + if (gf->debug) + dev_info(gf->dev, "GF_IN %d->%d -> %s\n", + inp_state->index, inp_state->value, target->name); + gpio_fsm_go_to_state_deferred(gf, target); + return IRQ_HANDLED; +} + +static void gpio_fsm_timer(struct timer_list *timer) +{ + struct gpio_fsm *gf = container_of(timer, struct gpio_fsm, timer); + struct fsm_state *target; + + target = gf->delay_target_state; + if (!target) + return; + if (gf->debug) + dev_info(gf->dev, "GF_DELAY %d -> %s\n", gf->delay_ms, + target->name); + + gpio_fsm_go_to_state_deferred(gf, target); +} + +static int gpio_fsm_parse_signals(struct gpio_fsm *gf, struct fsm_state *state, + struct property *prop) +{ + const __be32 *cells = prop->value; + struct output_signal *signal; + u32 io; + u32 type; + u32 index; + u32 value; + int ret = 0; + int i; + + if (prop->length % 8) { + dev_err(gf->dev, "malformed set in state %s\n", + state->name); + return -EINVAL; + } + + state->num_signals = prop->length/8; + state->signals = devm_kcalloc(gf->dev, state->num_signals, + sizeof(struct output_signal), + GFP_KERNEL); + for (i = 0; i < state->num_signals; i++) { + signal = &state->signals[i]; + io = be32_to_cpu(cells[0]); + type = GF_IO_TYPE(io); + index = GF_IO_INDEX(io); + value = be32_to_cpu(cells[1]); + + if (type != GF_OUT && type != GF_SOFT) { + dev_err(gf->dev, + "invalid set type %d in state %s\n", + type, state->name); + ret = -EINVAL; + break; + } + if (type == GF_OUT && index >= gf->num_output_gpios) { + dev_err(gf->dev, + "invalid GF_OUT number %d in state %s\n", + index, state->name); + ret = -EINVAL; + break; + } + if (type == GF_SOFT && index >= gf->num_soft_gpios) { + dev_err(gf->dev, + "invalid GF_SOFT number %d in state %s\n", + index, state->name); + ret = -EINVAL; + break; + } + if (value != 0 && value != 1) { + dev_err(gf->dev, + "invalid set value %d in state %s\n", + value, state->name); + ret = -EINVAL; + break; + } + signal->type = (type == GF_OUT) ? SIGNAL_GPIO : SIGNAL_SOFT; + signal->index = index; + signal->value = value; + cells += 2; + } + + return ret; +} + +static struct gpio_event *new_event(struct gpio_event **events, int *num_events) +{ + int num = ++(*num_events); + *events = krealloc(*events, num * sizeof(struct gpio_event), + GFP_KERNEL); + return *events ? *events + (num - 1) : NULL; +} + +static int gpio_fsm_parse_events(struct gpio_fsm *gf, struct fsm_state *state, + struct property *prop) +{ + const __be32 *cells = prop->value; + struct symtab_entry *sym; + int num_cells; + int ret = 0; + int i; + + if (prop->length % 8) { + dev_err(gf->dev, + "malformed transitions from state %s to state %s\n", + state->name, prop->name); + return -EINVAL; + } + + sym = get_symbol(&gf->symtab, prop->name); + num_cells = prop->length / 4; + i = 0; + while (i < num_cells) { + struct gpio_event *gp_ev; + u32 event, param; + u32 index; + + event = be32_to_cpu(cells[i++]); + param = be32_to_cpu(cells[i++]); + index = GF_IO_INDEX(event); + + switch (GF_IO_TYPE(event)) { + case GF_IN: + if (index >= gf->num_input_gpios) { + dev_err(gf->dev, + "invalid GF_IN %d in transitions from state %s to state %s\n", + index, state->name, prop->name); + return -EINVAL; + } + if (param > 1) { + dev_err(gf->dev, + "invalid GF_IN value %d in transitions from state %s to state %s\n", + param, state->name, prop->name); + return -EINVAL; + } + gp_ev = new_event(&state->gpio_events, + &state->num_gpio_events); + if (!gp_ev) + return -ENOMEM; + gp_ev->index = index; + gp_ev->value = param; + gp_ev->target = (struct fsm_state *)sym; + break; + + case GF_SOFT: + if (index >= gf->num_soft_gpios) { + dev_err(gf->dev, + "invalid GF_SOFT %d in transitions from state %s to state %s\n", + index, state->name, prop->name); + return -EINVAL; + } + if (param > 1) { + dev_err(gf->dev, + "invalid GF_SOFT value %d in transitions from state %s to state %s\n", + param, state->name, prop->name); + return -EINVAL; + } + gp_ev = new_event(&state->soft_events, + &state->num_soft_events); + if (!gp_ev) + return -ENOMEM; + gp_ev->index = index; + gp_ev->value = param; + gp_ev->target = (struct fsm_state *)sym; + break; + + case GF_DELAY: + if (state->delay_target) { + dev_err(gf->dev, + "state %s has multiple GF_DELAYs\n", + state->name); + return -EINVAL; + } + state->delay_target = (struct fsm_state *)sym; + state->delay_ms = param; + break; + + case GF_SHUTDOWN: + if (state->shutdown_target == state) { + dev_err(gf->dev, + "shutdown state %s has GF_SHUTDOWN\n", + state->name); + return -EINVAL; + } else if (state->shutdown_target) { + dev_err(gf->dev, + "state %s has multiple GF_SHUTDOWNs\n", + state->name); + return -EINVAL; + } + state->shutdown_target = + (struct fsm_state *)sym; + state->shutdown_ms = param; + break; + + default: + dev_err(gf->dev, + "invalid event %08x in transitions from state %s to state %s\n", + event, state->name, prop->name); + return -EINVAL; + } + } + if (i != num_cells) { + dev_err(gf->dev, + "malformed transitions from state %s to state %s\n", + state->name, prop->name); + return -EINVAL; + } + + return ret; +} + +static int gpio_fsm_parse_state(struct gpio_fsm *gf, + struct fsm_state *state, + struct device_node *np) +{ + struct symtab_entry *sym; + struct property *prop; + int ret; + + state->name = np->name; + ret = add_symbol(&gf->symtab, np->name, state); + if (ret) { + switch (ret) { + case -EINVAL: + dev_err(gf->dev, "'%s' is not a valid state name\n", + np->name); + break; + case -EEXIST: + dev_err(gf->dev, "state %s already defined\n", + np->name); + break; + default: + dev_err(gf->dev, "error %d adding state %s symbol\n", + ret, np->name); + break; + } + return ret; + } + + for_each_property_of_node(np, prop) { + sym = get_symbol(&gf->symtab, prop->name); + if (!sym) { + ret = -ENOMEM; + break; + } + + switch ((uintptr_t)sym->value) { + case SYM_SET: + ret = gpio_fsm_parse_signals(gf, state, prop); + break; + case SYM_START: + if (gf->start_state) { + dev_err(gf->dev, "multiple start states\n"); + ret = -EINVAL; + } else { + gf->start_state = state; + } + break; + case SYM_SHUTDOWN: + state->shutdown_target = state; + gf->shutdown_state = state; + break; + case SYM_NAME: + /* Ignore */ + break; + default: + /* A set of transition events to this state */ + ret = gpio_fsm_parse_events(gf, state, prop); + break; + } + } + + return ret; +} + +static void dump_all(struct gpio_fsm *gf) +{ + int i, j; + + dev_info(gf->dev, "Input GPIOs:\n"); + for (i = 0; i < gf->num_input_gpios; i++) + dev_info(gf->dev, " %d: %p\n", i, + gf->input_gpios->desc[i]); + + dev_info(gf->dev, "Output GPIOs:\n"); + for (i = 0; i < gf->num_output_gpios; i++) + dev_info(gf->dev, " %d: %p\n", i, + gf->output_gpios->desc[i]); + + dev_info(gf->dev, "Soft GPIOs:\n"); + for (i = 0; i < gf->num_soft_gpios; i++) + dev_info(gf->dev, " %d: %s %d\n", i, + (gf->soft_gpios[i].dir == GPIOF_IN) ? "IN" : "OUT", + gf->soft_gpios[i].value); + + dev_info(gf->dev, "Start state: %s\n", + gf->start_state ? gf->start_state->name : "-"); + + dev_info(gf->dev, "Shutdown timeout: %d ms\n", + gf->shutdown_timeout_ms); + + for (i = 0; i < gf->num_states; i++) { + struct fsm_state *state = &gf->states[i]; + + dev_info(gf->dev, "State %s:\n", state->name); + + if (state->shutdown_target == state) + dev_info(gf->dev, " Shutdown state\n"); + + dev_info(gf->dev, " Signals:\n"); + for (j = 0; j < state->num_signals; j++) { + struct output_signal *signal = &state->signals[j]; + + dev_info(gf->dev, " %d: %s %d=%d\n", j, + (signal->type == SIGNAL_GPIO) ? "GPIO" : + "SOFT", + signal->index, signal->value); + } + + dev_info(gf->dev, " GPIO events:\n"); + for (j = 0; j < state->num_gpio_events; j++) { + struct gpio_event *event = &state->gpio_events[j]; + + dev_info(gf->dev, " %d: %d=%d -> %s\n", j, + event->index, event->value, + event->target->name); + } + + dev_info(gf->dev, " Soft events:\n"); + for (j = 0; j < state->num_soft_events; j++) { + struct gpio_event *event = &state->soft_events[j]; + + dev_info(gf->dev, " %d: %d=%d -> %s\n", j, + event->index, event->value, + event->target->name); + } + + if (state->delay_target) + dev_info(gf->dev, " Delay: %d ms -> %s\n", + state->delay_ms, state->delay_target->name); + + if (state->shutdown_target && state->shutdown_target != state) + dev_info(gf->dev, " Shutdown: %d ms -> %s\n", + state->shutdown_ms, + state->shutdown_target->name); + } + dev_info(gf->dev, "\n"); +} + +static int resolve_sym_to_state(struct gpio_fsm *gf, struct fsm_state **pstate) +{ + struct symtab_entry *sym = (struct symtab_entry *)*pstate; + + if (!sym) + return -ENOMEM; + + *pstate = sym->value; + + if (!*pstate) { + dev_err(gf->dev, "state %s not defined\n", + sym->name); + return -EINVAL; + } + + return 0; +} + +static void gpio_fsm_set_soft(struct gpio_fsm *gf, + unsigned int off, int val) +{ + struct soft_gpio *sg = &gf->soft_gpios[off]; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "set(%d,%d)\n", off, val); + state = gf->current_state; + sg->value = val; + for (i = 0; i < state->num_soft_events; i++) { + gp_ev = &state->soft_events[i]; + if (gp_ev->index == off && gp_ev->value == val) { + if (gf->debug) + dev_info(gf->dev, + "GF_SOFT %d->%d -> %s\n", gp_ev->index, + gp_ev->value, gp_ev->target->name); + gpio_fsm_go_to_state(gf, gp_ev->target); + break; + } + } +} + +static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + + return sg->value; +} + +static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct gpio_fsm *gf; + + gf = gpiochip_get_data(gc); + if (off < gf->num_soft_gpios) + gpio_fsm_set_soft(gf, off, val); +} + +static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + + return sg->dir; +} + +static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + sg->dir = GPIOF_IN; + + return 0; +} + +static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off, + int value) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + sg->dir = GPIOF_OUT_INIT_LOW; + gpio_fsm_set_soft(gf, off, value); + + return 0; +} + +/* + * /sys/class/gpio-fsm/<fsm-name>/ + * /state ... the current state + */ + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", gf->current_state->name); +} +static DEVICE_ATTR_RO(state); + +static ssize_t delay_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", + gf->delay_target_state ? gf->delay_target_state->name : + "-"); +} + +static DEVICE_ATTR_RO(delay_state); + +static ssize_t delay_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + int jiffies_left; + + jiffies_left = max((int)(gf->delay_jiffies - jiffies), 0); + return sprintf(buf, + gf->delay_target_state ? "%u\n" : "-\n", + jiffies_to_msecs(jiffies_left)); +} +static DEVICE_ATTR_RO(delay_ms); + +static struct attribute *gpio_fsm_attrs[] = { + &dev_attr_state.attr, + &dev_attr_delay_state.attr, + &dev_attr_delay_ms.attr, + NULL, +}; + +static const struct attribute_group gpio_fsm_group = { + .attrs = gpio_fsm_attrs, + //.is_visible = gpio_is_visible, +}; + +static const struct attribute_group *gpio_fsm_groups[] = { + &gpio_fsm_group, + NULL +}; + +static struct attribute *gpio_fsm_class_attrs[] = { + // There are no top-level attributes + NULL, +}; +ATTRIBUTE_GROUPS(gpio_fsm_class); + +static struct class gpio_fsm_class = { + .name = MODULE_NAME, + + .class_groups = gpio_fsm_class_groups, +}; + +static int gpio_fsm_probe(struct platform_device *pdev) +{ + struct input_gpio_state *inp_state; + struct device *dev = &pdev->dev; + struct device *sysfs_dev; + struct device_node *np = dev_of_node(dev); + struct device_node *cp; + struct gpio_fsm *gf; + u32 debug = 0; + int num_states; + u32 num_soft_gpios; + int ret; + int i; + static const char *const reserved_symbols[] = { + [SYM_NAME] = "name", + [SYM_SET] = "set", + [SYM_START] = "start_state", + [SYM_SHUTDOWN] = "shutdown_state", + }; + + if (of_property_read_u32(np, "num-swgpios", &num_soft_gpios) && + of_property_read_u32(np, "num-soft-gpios", &num_soft_gpios)) { + dev_err(dev, "missing 'num-swgpios' property\n"); + return -EINVAL; + } + + of_property_read_u32(np, "debug", &debug); + + gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL); + if (!gf) + return -ENOMEM; + + gf->dev = dev; + gf->debug = debug; + + if (of_property_read_u32(np, "shutdown-timeout-ms", + &gf->shutdown_timeout_ms)) + gf->shutdown_timeout_ms = 5000; + + gf->num_soft_gpios = num_soft_gpios; + gf->soft_gpios = devm_kcalloc(dev, num_soft_gpios, + sizeof(struct soft_gpio), GFP_KERNEL); + if (!gf->soft_gpios) + return -ENOMEM; + for (i = 0; i < num_soft_gpios; i++) { + struct soft_gpio *sg = &gf->soft_gpios[i]; + + sg->dir = GPIOF_IN; + sg->value = 0; + } + + gf->input_gpios = devm_gpiod_get_array_optional(dev, "input", GPIOD_IN); + if (IS_ERR(gf->input_gpios)) { + ret = PTR_ERR(gf->input_gpios); + dev_err(dev, "failed to get input gpios from DT - %d\n", ret); + return ret; + } + gf->num_input_gpios = (gf->input_gpios ? gf->input_gpios->ndescs : 0); + + gf->input_gpio_states = devm_kcalloc(dev, gf->num_input_gpios, + sizeof(struct input_gpio_state), + GFP_KERNEL); + if (!gf->input_gpio_states) + return -ENOMEM; + for (i = 0; i < gf->num_input_gpios; i++) { + inp_state = &gf->input_gpio_states[i]; + inp_state->desc = gf->input_gpios->desc[i]; + inp_state->gf = gf; + inp_state->index = i; + inp_state->irq = gpiod_to_irq(inp_state->desc); + inp_state->active_low = gpiod_is_active_low(inp_state->desc); + if (inp_state->irq >= 0) + ret = devm_request_irq(gf->dev, inp_state->irq, + gpio_fsm_gpio_irq_handler, + IRQF_TRIGGER_NONE, + dev_name(dev), + inp_state); + else + ret = inp_state->irq; + + if (ret) { + dev_err(dev, + "failed to get IRQ for input gpio - %d\n", + ret); + return ret; + } + } + + gf->output_gpios = devm_gpiod_get_array_optional(dev, "output", + GPIOD_OUT_LOW); + if (IS_ERR(gf->output_gpios)) { + ret = PTR_ERR(gf->output_gpios); + dev_err(dev, "failed to get output gpios from DT - %d\n", ret); + return ret; + } + gf->num_output_gpios = (gf->output_gpios ? gf->output_gpios->ndescs : + 0); + + num_states = of_get_child_count(np); + if (!num_states) { + dev_err(dev, "no states declared\n"); + return -EINVAL; + } + gf->states = devm_kcalloc(dev, num_states, + sizeof(struct fsm_state), GFP_KERNEL); + if (!gf->states) + return -ENOMEM; + + // add reserved words to the symbol table + for (i = 0; i < ARRAY_SIZE(reserved_symbols); i++) { + if (reserved_symbols[i]) + add_symbol(&gf->symtab, reserved_symbols[i], + (void *)(uintptr_t)i); + } + + // parse the state + for_each_child_of_node(np, cp) { + struct fsm_state *state = &gf->states[gf->num_states]; + + ret = gpio_fsm_parse_state(gf, state, cp); + if (ret) + return ret; + gf->num_states++; + } + + if (!gf->start_state) { + dev_err(gf->dev, "no start state defined\n"); + return -EINVAL; + } + + // resolve symbol pointers into state pointers + for (i = 0; !ret && i < gf->num_states; i++) { + struct fsm_state *state = &gf->states[i]; + int j; + + for (j = 0; !ret && j < state->num_gpio_events; j++) { + struct gpio_event *ev = &state->gpio_events[j]; + + ret = resolve_sym_to_state(gf, &ev->target); + } + + for (j = 0; !ret && j < state->num_soft_events; j++) { + struct gpio_event *ev = &state->soft_events[j]; + + ret = resolve_sym_to_state(gf, &ev->target); + } + + if (!ret) { + resolve_sym_to_state(gf, &state->delay_target); + if (state->shutdown_target != state) + resolve_sym_to_state(gf, + &state->shutdown_target); + } + } + + if (!ret && gf->debug > 1) + dump_all(gf); + + free_symbols(&gf->symtab); + + if (ret) + return ret; + + gf->gc.parent = dev; + gf->gc.label = np->name; + gf->gc.owner = THIS_MODULE; + gf->gc.base = -1; + gf->gc.ngpio = num_soft_gpios; + + gf->gc.get_direction = gpio_fsm_get_direction; + gf->gc.direction_input = gpio_fsm_direction_input; + gf->gc.direction_output = gpio_fsm_direction_output; + gf->gc.get = gpio_fsm_get; + gf->gc.set = gpio_fsm_set; + gf->gc.can_sleep = true; + spin_lock_init(&gf->spinlock); + INIT_WORK(&gf->work, gpio_fsm_work); + timer_setup(&gf->timer, gpio_fsm_timer, 0); + init_waitqueue_head(&gf->shutdown_event); + + platform_set_drvdata(pdev, gf); + + sysfs_dev = device_create_with_groups(&gpio_fsm_class, dev, + MKDEV(0, 0), gf, + gpio_fsm_groups, + "%s", np->name); + if (IS_ERR(sysfs_dev)) + dev_err(gf->dev, "Error creating sysfs entry\n"); + + if (gf->debug) + dev_info(gf->dev, "Start -> %s\n", gf->start_state->name); + + gpio_fsm_enter_state(gf, gf->start_state); + + return devm_gpiochip_add_data(dev, &gf->gc, gf); +} + +static void gpio_fsm_remove(struct platform_device *pdev) +{ + struct gpio_fsm *gf = platform_get_drvdata(pdev); + int i; + + if (gf->shutdown_state) { + if (gf->debug) + dev_info(gf->dev, "Shutting down...\n"); + + spin_lock(&gf->spinlock); + gf->shutting_down = true; + if (gf->current_state->shutdown_target && + gf->current_state->shutdown_target != gf->current_state) { + gf->delay_target_state = + gf->current_state->shutdown_target; + mod_timer(&gf->timer, gf->shutdown_jiffies); + } + spin_unlock(&gf->spinlock); + + wait_event_timeout(gf->shutdown_event, + gf->current_state->shutdown_target == + gf->current_state, + msecs_to_jiffies(gf->shutdown_timeout_ms)); + /* On failure to reach a shutdown state, jump to one */ + if (gf->current_state->shutdown_target != gf->current_state) + gpio_fsm_enter_state(gf, gf->shutdown_state); + } + cancel_work_sync(&gf->work); + del_timer_sync(&gf->timer); + + /* Events aren't allocated from managed storage */ + for (i = 0; i < gf->num_states; i++) { + kfree(gf->states[i].gpio_events); + kfree(gf->states[i].soft_events); + } + if (gf->debug) + dev_info(gf->dev, "Exiting\n"); +} + +static void gpio_fsm_shutdown(struct platform_device *pdev) +{ + gpio_fsm_remove(pdev); +} + +static const struct of_device_id gpio_fsm_ids[] = { + { .compatible = "rpi,gpio-fsm" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_fsm_ids); + +static struct platform_driver gpio_fsm_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(gpio_fsm_ids), + }, + .probe = gpio_fsm_probe, + .remove = gpio_fsm_remove, + .shutdown = gpio_fsm_shutdown, +}; + +static int gpio_fsm_init(void) +{ + int ret; + + ret = class_register(&gpio_fsm_class); + if (ret) + return ret; + + ret = platform_driver_register(&gpio_fsm_driver); + if (ret) + class_unregister(&gpio_fsm_class); + + return ret; +} +module_init(gpio_fsm_init); + +static void gpio_fsm_exit(void) +{ + platform_driver_unregister(&gpio_fsm_driver); + class_unregister(&gpio_fsm_class); +} +module_exit(gpio_fsm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Phil Elwell <[email protected]>"); +MODULE_DESCRIPTION("GPIO FSM driver"); +MODULE_ALIAS("platform:gpio-fsm"); diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 7d6dd36cf1ae..9f6b7fcd4f3f 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -245,6 +245,27 @@ static int bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) return 0; } +static int bgpio_set_direct(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + unsigned long mask = bgpio_line2mask(gc, gpio); + unsigned long flags; + + raw_spin_lock_irqsave(&chip->lock, flags); + + chip->sdata = chip->read_reg(chip->reg_dat); + + if (val) + chip->sdata |= mask; + else + chip->sdata &= ~mask; + + chip->write_reg(chip->reg_dat, chip->sdata); + + raw_spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + static int bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, int val) { @@ -278,8 +299,8 @@ static int bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) return 0; } -static void bgpio_multiple_get_masks(struct gpio_chip *gc, - unsigned long *mask, unsigned long *bits, +static void bgpio_multiple_get_masks(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits, unsigned long *set_mask, unsigned long *clear_mask) { @@ -318,7 +339,7 @@ static void bgpio_set_multiple_single_reg(struct gpio_chip *gc, } static int bgpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, - unsigned long *bits) + unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -354,7 +375,30 @@ static int bgpio_set_multiple_with_clear(struct gpio_chip *gc, return 0; } -static int bgpio_dir_return(struct gpio_chip *gc, unsigned int gpio, bool dir_out) +static int bgpio_set_multiple_direct(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + unsigned long flags; + unsigned long set_mask, clear_mask; + + raw_spin_lock_irqsave(&chip->lock, flags); + + bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); + + chip->sdata = chip->read_reg(chip->reg_dat); + + chip->sdata |= set_mask; + chip->sdata &= ~clear_mask; + + chip->write_reg(chip->reg_dat, chip->sdata); + + raw_spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int bgpio_dir_return(struct gpio_chip *gc, unsigned int gpio, + bool dir_out) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -377,8 +421,7 @@ static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) return bgpio_dir_return(gc, gpio, false); } -static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, - int val) +static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, int val) { return -EINVAL; } @@ -410,6 +453,30 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) return bgpio_dir_return(gc, gpio, false); } +static int bgpio_dir_in_direct(struct gpio_chip *gc, unsigned int gpio) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + unsigned long flags; + + raw_spin_lock_irqsave(&chip->lock, flags); + + if (chip->reg_dir_in) + chip->sdir = ~chip->read_reg(chip->reg_dir_in); + if (chip->reg_dir_out) + chip->sdir = chip->read_reg(chip->reg_dir_out); + + chip->sdir &= ~bgpio_line2mask(gc, gpio); + + if (chip->reg_dir_in) + chip->write_reg(chip->reg_dir_in, ~chip->sdir); + if (chip->reg_dir_out) + chip->write_reg(chip->reg_dir_out, chip->sdir); + + raw_spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -422,13 +489,15 @@ static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) } if (chip->reg_dir_out) { - if (chip->read_reg(chip->reg_dir_out) & bgpio_line2mask(gc, gpio)) + if (chip->read_reg(chip->reg_dir_out) & + bgpio_line2mask(gc, gpio)) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } if (chip->reg_dir_in) - if (!(chip->read_reg(chip->reg_dir_in) & bgpio_line2mask(gc, gpio))) + if (!(chip->read_reg(chip->reg_dir_in) & + bgpio_line2mask(gc, gpio))) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; @@ -451,6 +520,29 @@ static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) raw_spin_unlock_irqrestore(&chip->lock, flags); } +static void bgpio_dir_out_direct(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + unsigned long flags; + + raw_spin_lock_irqsave(&chip->lock, flags); + + if (chip->reg_dir_in) + chip->sdir = ~chip->read_reg(chip->reg_dir_in); + if (chip->reg_dir_out) + chip->sdir = chip->read_reg(chip->reg_dir_out); + + chip->sdir |= bgpio_line2mask(gc, gpio); + + if (chip->reg_dir_in) + chip->write_reg(chip->reg_dir_in, ~chip->sdir); + if (chip->reg_dir_out) + chip->write_reg(chip->reg_dir_out, chip->sdir); + + raw_spin_unlock_irqrestore(&chip->lock, flags); +} + static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, int val) { @@ -467,31 +559,46 @@ static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio, return bgpio_dir_return(gc, gpio, true); } +static int bgpio_dir_out_dir_first_direct(struct gpio_chip *gc, + unsigned int gpio, int val) +{ + bgpio_dir_out_direct(gc, gpio, val); + gc->set(gc, gpio, val); + return 0; +} + +static int bgpio_dir_out_val_first_direct(struct gpio_chip *gc, + unsigned int gpio, int val) +{ + gc->set(gc, gpio, val); + bgpio_dir_out_direct(gc, gpio, val); + return 0; +} + static int bgpio_setup_accessors(struct device *dev, - struct gpio_generic_chip *chip, - bool byte_be) + struct gpio_generic_chip *chip, bool byte_be) { switch (chip->bits) { case 8: - chip->read_reg = bgpio_read8; - chip->write_reg = bgpio_write8; + chip->read_reg = bgpio_read8; + chip->write_reg = bgpio_write8; break; case 16: if (byte_be) { - chip->read_reg = bgpio_read16be; - chip->write_reg = bgpio_write16be; + chip->read_reg = bgpio_read16be; + chip->write_reg = bgpio_write16be; } else { - chip->read_reg = bgpio_read16; - chip->write_reg = bgpio_write16; + chip->read_reg = bgpio_read16; + chip->write_reg = bgpio_write16; } break; case 32: if (byte_be) { - chip->read_reg = bgpio_read32be; - chip->write_reg = bgpio_write32be; + chip->read_reg = bgpio_read32be; + chip->write_reg = bgpio_write32be; } else { - chip->read_reg = bgpio_read32; - chip->write_reg = bgpio_write32; + chip->read_reg = bgpio_read32; + chip->write_reg = bgpio_write32; } break; #if BITS_PER_LONG >= 64 @@ -501,8 +608,8 @@ static int bgpio_setup_accessors(struct device *dev, "64 bit big endian byte order unsupported\n"); return -EINVAL; } else { - chip->read_reg = bgpio_read64; - chip->write_reg = bgpio_write64; + chip->read_reg = bgpio_read64; + chip->write_reg = bgpio_write64; } break; #endif /* BITS_PER_LONG >= 64 */ @@ -557,6 +664,9 @@ static int bgpio_setup_io(struct gpio_generic_chip *chip, } else if (cfg->flags & GPIO_GENERIC_NO_OUTPUT) { gc->set = bgpio_set_none; gc->set_multiple = NULL; + } else if (cfg->flags & GPIO_GENERIC_REG_DIRECT) { + gc->set = bgpio_set_direct; + gc->set_multiple = bgpio_set_multiple_direct; } else { gc->set = bgpio_set; gc->set_multiple = bgpio_set_multiple; @@ -593,11 +703,21 @@ static int bgpio_setup_direction(struct gpio_generic_chip *chip, if (cfg->dirout || cfg->dirin) { chip->reg_dir_out = cfg->dirout; chip->reg_dir_in = cfg->dirin; - if (cfg->flags & GPIO_GENERIC_NO_SET_ON_INPUT) - gc->direction_output = bgpio_dir_out_dir_first; - else - gc->direction_output = bgpio_dir_out_val_first; - gc->direction_input = bgpio_dir_in; + if (cfg->flags & GPIO_GENERIC_REG_DIRECT) { + if (cfg->flags & GPIO_GENERIC_NO_SET_ON_INPUT) + gc->direction_output = + bgpio_dir_out_dir_first_direct; + else + gc->direction_output = + bgpio_dir_out_val_first_direct; + gc->direction_input = bgpio_dir_in_direct; + } else { + if (cfg->flags & GPIO_GENERIC_NO_SET_ON_INPUT) + gc->direction_output = bgpio_dir_out_dir_first; + else + gc->direction_output = bgpio_dir_out_val_first; + gc->direction_input = bgpio_dir_in; + } gc->get_direction = bgpio_get_dir; } else { if (cfg->flags & GPIO_GENERIC_NO_OUTPUT) @@ -681,7 +801,7 @@ int gpio_generic_chip_init(struct gpio_generic_chip *chip, chip->sdata = chip->read_reg(chip->reg_dat); if (gc->set == bgpio_set_set && - !(flags & GPIO_GENERIC_UNREADABLE_REG_SET)) + !(flags & GPIO_GENERIC_UNREADABLE_REG_SET)) chip->sdata = chip->read_reg(chip->reg_set); if (flags & GPIO_GENERIC_UNREADABLE_REG_DIR) @@ -712,8 +832,7 @@ EXPORT_SYMBOL_GPL(gpio_generic_chip_init); #if IS_ENABLED(CONFIG_GPIO_GENERIC_PLATFORM) -static void __iomem *bgpio_map(struct platform_device *pdev, - const char *name, +static void __iomem *bgpio_map(struct platform_device *pdev, const char *name, resource_size_t sane_sz) { struct resource *r; @@ -735,7 +854,7 @@ static const struct of_device_id bgpio_of_match[] = { { .compatible = "wd,mbl-gpio" }, { .compatible = "ni,169445-nand-gpio" }, { .compatible = "intel,ixp4xx-expansion-bus-mmio-gpio" }, - { } + {} }; MODULE_DEVICE_TABLE(of, bgpio_of_match); @@ -792,7 +911,7 @@ static int bgpio_pdev_probe(struct platform_device *pdev) if (device_property_read_bool(dev, "no-output")) flags |= GPIO_GENERIC_NO_OUTPUT; - config = (struct gpio_generic_chip_config) { + config = (struct gpio_generic_chip_config){ .dev = dev, .sz = sz, .dat = dat, @@ -826,10 +945,10 @@ static int bgpio_pdev_probe(struct platform_device *pdev) static const struct platform_device_id bgpio_id_table[] = { { - .name = "basic-mmio-gpio", - .driver_data = 0, + .name = "basic-mmio-gpio", + .driver_data = 0, }, - { } + {} }; MODULE_DEVICE_TABLE(platform, bgpio_id_table); diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index b46927f55038..a9bc5a64c43b 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -1440,6 +1440,7 @@ static const struct of_device_id pca953x_dt_ids[] = { { .compatible = "ti,tca9535", .data = OF_953X(16, PCA_INT), }, { .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), }, { .compatible = "ti,tca9539", .data = OF_953X(16, PCA_INT), }, + { .compatible = "ti,tca9554", .data = OF_953X( 8, PCA_INT), }, { .compatible = "onnn,cat9554", .data = OF_953X( 8, PCA_INT), }, { .compatible = "onnn,pca9654", .data = OF_953X( 8, PCA_INT), }, diff --git a/drivers/gpio/gpio-pwm.c b/drivers/gpio/gpio-pwm.c new file mode 100644 index 000000000000..01a2d404d4a3 --- /dev/null +++ b/drivers/gpio/gpio-pwm.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO driver wrapping PWM API + * + * PWM 0% and PWM 100% are equivalent to digital GPIO + * outputs, and there are times where it is useful to use + * PWM outputs as straight GPIOs (eg outputs of NXP PCA9685 + * I2C PWM chip). This driver wraps the PWM API as a GPIO + * controller. + * + * Copyright (C) 2021 Raspberry Pi (Trading) Ltd. + */ + +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct pwm_gpio { + struct gpio_chip gc; + struct pwm_device **pwm; +}; + +static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int off) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static void pwm_gpio_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct pwm_gpio *pwm_gpio = gpiochip_get_data(gc); + struct pwm_state state; + + pwm_get_state(pwm_gpio->pwm[off], &state); + state.duty_cycle = val ? state.period : 0; + pwm_apply_might_sleep(pwm_gpio->pwm[off], &state); +} + +static int pwm_gpio_parse_dt(struct pwm_gpio *pwm_gpio, + struct device *dev) +{ + struct device_node *node = dev->of_node; + struct pwm_state state; + int ret = 0, i, num_gpios; + const char *pwm_name; + + if (!node) + return -ENODEV; + + num_gpios = of_property_count_strings(node, "pwm-names"); + if (num_gpios <= 0) + return 0; + + pwm_gpio->pwm = devm_kzalloc(dev, + sizeof(*pwm_gpio->pwm) * num_gpios, + GFP_KERNEL); + if (!pwm_gpio->pwm) + return -ENOMEM; + + for (i = 0; i < num_gpios; i++) { + ret = of_property_read_string_index(node, "pwm-names", i, + &pwm_name); + if (ret) { + dev_err(dev, "unable to get pwm device index %d, name %s", + i, pwm_name); + goto error; + } + + pwm_gpio->pwm[i] = devm_pwm_get(dev, pwm_name); + if (IS_ERR(pwm_gpio->pwm[i])) { + ret = PTR_ERR(pwm_gpio->pwm[i]); + if (ret != -EPROBE_DEFER) + dev_err(dev, "unable to request PWM\n"); + goto error; + } + + /* Sync up PWM state. */ + pwm_init_state(pwm_gpio->pwm[i], &state); + + state.duty_cycle = 0; + pwm_apply_might_sleep(pwm_gpio->pwm[i], &state); + } + + pwm_gpio->gc.ngpio = num_gpios; + +error: + return ret; +} + +static int pwm_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_gpio *pwm_gpio; + int ret; + + pwm_gpio = devm_kzalloc(dev, sizeof(*pwm_gpio), GFP_KERNEL); + if (!pwm_gpio) + return -ENOMEM; + + pwm_gpio->gc.parent = dev; + pwm_gpio->gc.label = "pwm-gpio"; + pwm_gpio->gc.owner = THIS_MODULE; + pwm_gpio->gc.fwnode = dev->fwnode; + pwm_gpio->gc.base = -1; + + pwm_gpio->gc.get_direction = pwm_gpio_get_direction; + pwm_gpio->gc.set = pwm_gpio_set; + pwm_gpio->gc.can_sleep = true; + + ret = pwm_gpio_parse_dt(pwm_gpio, dev); + if (ret) + return ret; + + if (!pwm_gpio->gc.ngpio) + return 0; + + return devm_gpiochip_add_data(dev, &pwm_gpio->gc, pwm_gpio); +} + +static void pwm_gpio_remove(struct platform_device *pdev) +{ +} + +static const struct of_device_id pwm_gpio_of_match[] = { + { .compatible = "pwm-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, pwm_gpio_of_match); + +static struct platform_driver pwm_gpio_driver = { + .driver = { + .name = "pwm-gpio", + .of_match_table = of_match_ptr(pwm_gpio_of_match), + }, + .probe = pwm_gpio_probe, + .remove = pwm_gpio_remove, +}; +module_platform_driver(pwm_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dave Stevenson <[email protected]>"); +MODULE_DESCRIPTION("PWM GPIO driver"); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index cd8800ba5825..b48fa439ec14 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -51,6 +51,8 @@ * GPIOs can sometimes cost only an instruction or two per bit. */ +#define dont_test_bit(b,d) (0) + /* Device and char device-related information */ static DEFINE_IDA(gpio_ida); static dev_t gpio_devt; @@ -116,6 +118,7 @@ static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc); static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc); static bool gpiolib_initialized; +static int first_dynamic_gpiochip_num = -1; const char *gpiod_get_label(struct gpio_desc *desc) { @@ -1036,6 +1039,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, unsigned int desc_index; int base = 0; int ret; + int id; /* * First: allocate and populate the internal stat container, and @@ -1055,7 +1059,16 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc)); - ret = ida_alloc(&gpio_ida, GFP_KERNEL); + if (first_dynamic_gpiochip_num < 0) { + id = of_alias_get_highest_id("gpiochip"); + first_dynamic_gpiochip_num = (id >= 0) ? (id + 1) : 0; + } + + id = of_alias_get_id(gdev->dev.of_node, "gpiochip"); + if (id < 0) + id = first_dynamic_gpiochip_num; + + ret = ida_alloc_range(&gpio_ida, id, ~0, GFP_KERNEL); if (ret < 0) goto err_free_gdev; gdev->id = ret; |
