aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio')
-rw-r--r--drivers/gpio/Kconfig23
-rw-r--r--drivers/gpio/Makefile3
-rw-r--r--drivers/gpio/gpio-bcm-virt.c212
-rw-r--r--drivers/gpio/gpio-brcmstb.c156
-rw-r--r--drivers/gpio/gpio-fsm.c1210
-rw-r--r--drivers/gpio/gpio-mmio.c189
-rw-r--r--drivers/gpio/gpio-pca953x.c1
-rw-r--r--drivers/gpio/gpio-pwm.c143
-rw-r--r--drivers/gpio/gpiolib.c15
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;