Message ID | 20220705134912.2740421-2-lewis.hanly@microchip.com |
---|---|
State | New |
Headers | show |
Series | Add Polarfire SoC GPIO support | expand |
On 05/07/2022 14:49, lewis.hanly@microchip.com wrote: > From: Lewis Hanly <lewis.hanly@microchip.com> > > Add a driver to support the Polarfire SoC gpio controller. > > Signed-off-by: Lewis Hanly <lewis.hanly@microchip.com> Hey Lewis, Got an error building this against v5.19-rc4 w/ the default riscv defconfig + CONFIG_GPIO_POLARFIRE_SOC=y /stuff/linux/drivers/gpio/gpio-mpfs.c: In function 'mpfs_gpio_probe': /stuff/linux/drivers/gpio/gpio-mpfs.c:290:20: error: 'struct irq_chip' has no member named 'parent_device' 290 | irq_c->chip->parent_device = dev; | ^~ make[5]: *** [/stuff/linux/scripts/Makefile.build:249: drivers/gpio/gpio-mpfs.o] Error 1 make[4]: *** [/stuff/linux/scripts/Makefile.build:466: drivers/gpio] Error 2 make[4]: *** Waiting for unfinished jobs.... Also, please add the GPIO maintainers to V2: Linus Walleij <linus.walleij@linaro.org> (maintainer:GPIO SUBSYSTEM) Bartosz Golaszewski <brgl@bgdev.pl> (maintainer:GPIO SUBSYSTEM) Thanks, Conor. > --- > drivers/gpio/Kconfig | 7 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-mpfs.c | 358 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 366 insertions(+) > create mode 100644 drivers/gpio/gpio-mpfs.c > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > index b01961999ced..e279eac198da 100644 > --- a/drivers/gpio/Kconfig > +++ b/drivers/gpio/Kconfig > @@ -490,6 +490,13 @@ config GPIO_PMIC_EIC_SPRD > help > Say yes here to support Spreadtrum PMIC EIC device. > > +config GPIO_POLARFIRE_SOC > + bool "Microchip FPGA GPIO support" > + depends on OF_GPIO > + select GPIOLIB_IRQCHIP > + help > + Say yes here to support the GPIO device on Microchip FPGAs > + > config GPIO_PXA > bool "PXA GPIO support" > depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile > index 14352f6dfe8e..3b8b6703e593 100644 > --- a/drivers/gpio/Makefile > +++ b/drivers/gpio/Makefile > @@ -119,6 +119,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o > 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_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-mpfs.c b/drivers/gpio/gpio-mpfs.c > new file mode 100644 > index 000000000000..df48f2836e97 > --- /dev/null > +++ b/drivers/gpio/gpio-mpfs.c > @@ -0,0 +1,358 @@ > +// SPDX-License-Identifier: (GPL-2.0) > +/* > + * Microchip PolarFire SoC (MPFS) GPIO controller driver > + * > + * Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries > + * > + * Author: Lewis Hanly <lewis.hanly@microchip.com> > + * > + */ > + > +#include <linux/bitops.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/errno.h> > +#include <linux/gpio/driver.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/irqchip/chained_irq.h> > +#include <linux/of.h> > +#include <linux/of_irq.h> > +#include <linux/platform_device.h> > +#include <linux/spinlock.h> > + > +#define NUM_GPIO 32 > +#define BYTE_BOUNDARY 0x04 > +#define MPFS_GPIO_EN_INT 3 > +#define MPFS_GPIO_EN_OUT_BUF BIT(2) > +#define MPFS_GPIO_EN_IN BIT(1) > +#define MPFS_GPIO_EN_OUT BIT(0) > + > +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 > +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 > +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 > +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 > +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 > +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) > +#define MPFS_IRQ_REG 0x80 > +#define MPFS_INP_REG 0x84 > +#define MPFS_OUTP_REG 0x88 > + > +struct mpfs_gpio_chip { > + void __iomem *base; > + struct clk *clk; > + spinlock_t lock; /* lock */ > + struct gpio_chip gc; > +}; > + > +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int bit_offset, int value) > +{ > + u32 output = readl(addr); > + > + if (value) > + output |= BIT(bit_offset); > + else > + output &= ~BIT(bit_offset); > + > + writel(output, addr); > +} > + > +static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_IN; > + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; > + gpio_cfg &= ~MPFS_GPIO_EN_IN; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_get_direction(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + if (gpio_cfg & MPFS_GPIO_EN_IN) > + return 1; > + > + return 0; > +} > + > +static int mpfs_gpio_get(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & BIT(gpio_index)); > +} > + > +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return; > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > + gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > +} > + > +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + int gpio_index = irqd_to_hwirq(data); > + u32 interrupt_type; > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + switch (type) { > + case IRQ_TYPE_EDGE_BOTH: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; > + break; > + > + case IRQ_TYPE_EDGE_FALLING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; > + break; > + > + case IRQ_TYPE_EDGE_RISING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; > + break; > + > + case IRQ_TYPE_LEVEL_HIGH: > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; > + break; > + > + case IRQ_TYPE_LEVEL_LOW: > + default: > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; > + break; > + } > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; > + gpio_cfg |= interrupt_type; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static void mpfs_gpio_irq_enable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_direction_input(gc, gpio_index); > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 1); > +} > + > +static void mpfs_gpio_irq_disable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 0); > +} > + > +static struct irq_chip mpfs_gpio_irqchip = { > + .name = "mpfs_gpio_irqchip", > + .irq_set_type = mpfs_gpio_irq_set_type, > + .irq_enable = mpfs_gpio_irq_enable, > + .irq_disable = mpfs_gpio_irq_disable, > + .flags = IRQCHIP_MASK_ON_SUSPEND, > +}; > + > +static irqreturn_t mpfs_gpio_irq_handler(int irq, void *mpfs_gpio_data) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; > + unsigned long status; > + int offset; > + > + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); > + > + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, offset, 1); > + generic_handle_irq(irq_find_mapping(mpfs_gpio->gc.irq.domain, offset)); > + } > + return IRQ_HANDLED; > +} > + > +static int mpfs_gpio_probe(struct platform_device *pdev) > +{ > + struct clk *clk; > + struct device *dev = &pdev->dev; > + struct device_node *node = pdev->dev.of_node; > + struct mpfs_gpio_chip *mpfs_gpio; > + int i, ret, ngpio; > + struct gpio_irq_chip *irq_c; > + > + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); > + if (!mpfs_gpio) > + return -ENOMEM; > + > + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(mpfs_gpio->base)) { > + dev_err(dev, "failed to allocate device memory\n"); > + return PTR_ERR(mpfs_gpio->base); > + } > + clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(clk)) > + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed to get clock\n"); > + > + ret = clk_prepare_enable(clk); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "failed to enable clock\n"); > + > + mpfs_gpio->clk = clk; > + > + spin_lock_init(&mpfs_gpio->lock); > + > + ngpio = of_irq_count(node); > + if (ngpio > NUM_GPIO) { > + dev_err(dev, "too many interrupts\n"); > + goto cleanup_clock; > + } > + > + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; > + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; > + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; > + mpfs_gpio->gc.get = mpfs_gpio_get; > + mpfs_gpio->gc.set = mpfs_gpio_set; > + mpfs_gpio->gc.base = -1; > + mpfs_gpio->gc.ngpio = ngpio; > + mpfs_gpio->gc.label = dev_name(dev); > + mpfs_gpio->gc.parent = dev; > + mpfs_gpio->gc.owner = THIS_MODULE; > + > + irq_c = &mpfs_gpio->gc.irq; > + irq_c->chip = &mpfs_gpio_irqchip; > + irq_c->chip->parent_device = dev; > + irq_c->handler = handle_simple_irq; > + > + ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); > + if (ret < 0) { > + dev_err(dev, "failed to allocate descs\n"); > + goto cleanup_clock; > + } > + > + /* > + * Setup the interrupt handlers. Interrupts can be > + * direct and/or non-direct mode, based on register value: > + * GPIO_INTERRUPT_FAB_CR. > + */ > + for (i = 0; i < ngpio; i++) { > + int irq = platform_get_irq_optional(pdev, i); > + > + if (irq < 0) > + continue; > + > + ret = devm_request_irq(&pdev->dev, irq, > + mpfs_gpio_irq_handler, > + IRQF_SHARED, mpfs_gpio->gc.label, mpfs_gpio); > + if (ret) { > + dev_err(&pdev->dev, "failed to request irq %d: %d\n", > + irq, ret); > + goto cleanup_clock; > + } > + } > + > + ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); > + if (ret) > + goto cleanup_clock; > + > + platform_set_drvdata(pdev, mpfs_gpio); > + dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", ngpio); > + > + return 0; > + > +cleanup_clock: > + clk_disable_unprepare(mpfs_gpio->clk); > + return ret; > +} > + > +static int mpfs_gpio_remove(struct platform_device *pdev) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = platform_get_drvdata(pdev); > + > + gpiochip_remove(&mpfs_gpio->gc); > + clk_disable_unprepare(mpfs_gpio->clk); > + > + return 0; > +} > + > +static const struct of_device_id mpfs_gpio_match[] = { > + { .compatible = "microchip,mpfs-gpio", }, > + { /* end of list */ }, > +}; > + > +static struct platform_driver mpfs_gpio_driver = { > + .probe = mpfs_gpio_probe, > + .driver = { > + .name = "microchip,mpfs-gpio", > + .of_match_table = of_match_ptr(mpfs_gpio_match), > + }, > + .remove = mpfs_gpio_remove, > +}; > + > +builtin_platform_driver(mpfs_gpio_driver);
On Tue, 2022-07-05 at 15:17 +0000, Conor Dooley - M52691 wrote: > On 05/07/2022 14:49, lewis.hanly@microchip.com wrote: > > From: Lewis Hanly <lewis.hanly@microchip.com> > > > > Add a driver to support the Polarfire SoC gpio controller. > > > > Signed-off-by: Lewis Hanly <lewis.hanly@microchip.com> > > Hey Lewis, > Got an error building this against v5.19-rc4 w/ the default > riscv defconfig + CONFIG_GPIO_POLARFIRE_SOC=y > > /stuff/linux/drivers/gpio/gpio-mpfs.c: In function 'mpfs_gpio_probe': > /stuff/linux/drivers/gpio/gpio-mpfs.c:290:20: error: 'struct > irq_chip' has no member named 'parent_device' > 290 | irq_c->chip->parent_device = dev; > | ^~ > make[5]: *** [/stuff/linux/scripts/Makefile.build:249: > drivers/gpio/gpio-mpfs.o] Error 1 > make[4]: *** [/stuff/linux/scripts/Makefile.build:466: drivers/gpio] > Error 2 > make[4]: *** Waiting for unfinished jobs.... Error acknowledged, will followup with version 1. Regards, Lewis > > Also, please add the GPIO maintainers to V2: > Linus Walleij <linus.walleij@linaro.org> (maintainer:GPIO SUBSYSTEM) > Bartosz Golaszewski <brgl@bgdev.pl> (maintainer:GPIO SUBSYSTEM) > > Thanks, > Conor. > > > --- > > drivers/gpio/Kconfig | 7 + > > drivers/gpio/Makefile | 1 + > > drivers/gpio/gpio-mpfs.c | 358 > > +++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 366 insertions(+) > > create mode 100644 drivers/gpio/gpio-mpfs.c > > > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > > index b01961999ced..e279eac198da 100644 > > --- a/drivers/gpio/Kconfig > > +++ b/drivers/gpio/Kconfig > > @@ -490,6 +490,13 @@ config GPIO_PMIC_EIC_SPRD > > help > > Say yes here to support Spreadtrum PMIC EIC device. > > > > +config GPIO_POLARFIRE_SOC > > + bool "Microchip FPGA GPIO support" > > + depends on OF_GPIO > > + select GPIOLIB_IRQCHIP > > + help > > + Say yes here to support the GPIO device on Microchip FPGAs > > + > > config GPIO_PXA > > bool "PXA GPIO support" > > depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST > > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile > > index 14352f6dfe8e..3b8b6703e593 100644 > > --- a/drivers/gpio/Makefile > > +++ b/drivers/gpio/Makefile > > @@ -119,6 +119,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += > > gpio-pci-idio-16.o > > 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_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-mpfs.c b/drivers/gpio/gpio-mpfs.c > > new file mode 100644 > > index 000000000000..df48f2836e97 > > --- /dev/null > > +++ b/drivers/gpio/gpio-mpfs.c > > @@ -0,0 +1,358 @@ > > +// SPDX-License-Identifier: (GPL-2.0) > > +/* > > + * Microchip PolarFire SoC (MPFS) GPIO controller driver > > + * > > + * Copyright (c) 2018-2022 Microchip Technology Inc. and its > > subsidiaries > > + * > > + * Author: Lewis Hanly <lewis.hanly@microchip.com> > > + * > > + */ > > + > > +#include <linux/bitops.h> > > +#include <linux/clk.h> > > +#include <linux/device.h> > > +#include <linux/errno.h> > > +#include <linux/gpio/driver.h> > > +#include <linux/init.h> > > +#include <linux/irq.h> > > +#include <linux/irqchip/chained_irq.h> > > +#include <linux/of.h> > > +#include <linux/of_irq.h> > > +#include <linux/platform_device.h> > > +#include <linux/spinlock.h> > > + > > +#define NUM_GPIO 32 > > +#define BYTE_BOUNDARY 0x04 > > +#define MPFS_GPIO_EN_INT 3 > > +#define MPFS_GPIO_EN_OUT_BUF BIT(2) > > +#define MPFS_GPIO_EN_IN BIT(1) > > +#define MPFS_GPIO_EN_OUT BIT(0) > > + > > +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 > > +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 > > +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 > > +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 > > +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 > > +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) > > +#define MPFS_IRQ_REG 0x80 > > +#define MPFS_INP_REG 0x84 > > +#define MPFS_OUTP_REG 0x88 > > + > > +struct mpfs_gpio_chip { > > + void __iomem *base; > > + struct clk *clk; > > + spinlock_t lock; /* lock */ > > + struct gpio_chip gc; > > +}; > > + > > +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int > > bit_offset, int value) > > +{ > > + u32 output = readl(addr); > > + > > + if (value) > > + output |= BIT(bit_offset); > > + else > > + output &= ~BIT(bit_offset); > > + > > + writel(output, addr); > > +} > > + > > +static int mpfs_gpio_direction_input(struct gpio_chip *gc, > > unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > + > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg |= MPFS_GPIO_EN_IN; > > + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_direction_output(struct gpio_chip *gc, > > unsigned int gpio_index, int value) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > + > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; > > + gpio_cfg &= ~MPFS_GPIO_EN_IN; > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > > gpio_index, value); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_get_direction(struct gpio_chip *gc, > > + unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + if (gpio_cfg & MPFS_GPIO_EN_IN) > > + return 1; > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_get(struct gpio_chip *gc, > > + unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > + > > + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & > > BIT(gpio_index)); > > +} > > + > > +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int > > gpio_index, int value) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return; > > + > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > > + gpio_index, value); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > +} > > + > > +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned > > int type) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + int gpio_index = irqd_to_hwirq(data); > > + u32 interrupt_type; > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > + > > + switch (type) { > > + case IRQ_TYPE_EDGE_BOTH: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; > > + break; > > + > > + case IRQ_TYPE_EDGE_FALLING: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; > > + break; > > + > > + case IRQ_TYPE_EDGE_RISING: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; > > + break; > > + > > + case IRQ_TYPE_LEVEL_HIGH: > > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; > > + break; > > + > > + case IRQ_TYPE_LEVEL_LOW: > > + default: > > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; > > + break; > > + } > > + > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; > > + gpio_cfg |= interrupt_type; > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static void mpfs_gpio_irq_enable(struct irq_data *data) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > > + > > + mpfs_gpio_direction_input(gc, gpio_index); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > gpio_index, 1); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY), > > + MPFS_GPIO_EN_INT, 1); > > +} > > + > > +static void mpfs_gpio_irq_disable(struct irq_data *data) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > gpio_index, 1); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY), > > + MPFS_GPIO_EN_INT, 0); > > +} > > + > > +static struct irq_chip mpfs_gpio_irqchip = { > > + .name = "mpfs_gpio_irqchip", > > + .irq_set_type = mpfs_gpio_irq_set_type, > > + .irq_enable = mpfs_gpio_irq_enable, > > + .irq_disable = mpfs_gpio_irq_disable, > > + .flags = IRQCHIP_MASK_ON_SUSPEND, > > +}; > > + > > +static irqreturn_t mpfs_gpio_irq_handler(int irq, void > > *mpfs_gpio_data) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; > > + unsigned long status; > > + int offset; > > + > > + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); > > + > > + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > offset, 1); > > + generic_handle_irq(irq_find_mapping(mpfs_gpio- > > >gc.irq.domain, offset)); > > + } > > + return IRQ_HANDLED; > > +} > > + > > +static int mpfs_gpio_probe(struct platform_device *pdev) > > +{ > > + struct clk *clk; > > + struct device *dev = &pdev->dev; > > + struct device_node *node = pdev->dev.of_node; > > + struct mpfs_gpio_chip *mpfs_gpio; > > + int i, ret, ngpio; > > + struct gpio_irq_chip *irq_c; > > + > > + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); > > + if (!mpfs_gpio) > > + return -ENOMEM; > > + > > + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(mpfs_gpio->base)) { > > + dev_err(dev, "failed to allocate device memory\n"); > > + return PTR_ERR(mpfs_gpio->base); > > + } > > + clk = devm_clk_get(&pdev->dev, NULL); > > + if (IS_ERR(clk)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed > > to get clock\n"); > > + > > + ret = clk_prepare_enable(clk); > > + if (ret) > > + return dev_err_probe(&pdev->dev, ret, "failed to enable > > clock\n"); > > + > > + mpfs_gpio->clk = clk; > > + > > + spin_lock_init(&mpfs_gpio->lock); > > + > > + ngpio = of_irq_count(node); > > + if (ngpio > NUM_GPIO) { > > + dev_err(dev, "too many interrupts\n"); > > + goto cleanup_clock; > > + } > > + > > + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; > > + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; > > + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; > > + mpfs_gpio->gc.get = mpfs_gpio_get; > > + mpfs_gpio->gc.set = mpfs_gpio_set; > > + mpfs_gpio->gc.base = -1; > > + mpfs_gpio->gc.ngpio = ngpio; > > + mpfs_gpio->gc.label = dev_name(dev); > > + mpfs_gpio->gc.parent = dev; > > + mpfs_gpio->gc.owner = THIS_MODULE; > > + > > + irq_c = &mpfs_gpio->gc.irq; > > + irq_c->chip = &mpfs_gpio_irqchip; > > + irq_c->chip->parent_device = dev; > > + irq_c->handler = handle_simple_irq; > > + > > + ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); > > + if (ret < 0) { > > + dev_err(dev, "failed to allocate descs\n"); > > + goto cleanup_clock; > > + } > > + > > + /* > > + * Setup the interrupt handlers. Interrupts can be > > + * direct and/or non-direct mode, based on register value: > > + * GPIO_INTERRUPT_FAB_CR. > > + */ > > + for (i = 0; i < ngpio; i++) { > > + int irq = platform_get_irq_optional(pdev, i); > > + > > + if (irq < 0) > > + continue; > > + > > + ret = devm_request_irq(&pdev->dev, irq, > > + mpfs_gpio_irq_handler, > > + IRQF_SHARED, mpfs_gpio- > > >gc.label, mpfs_gpio); > > + if (ret) { > > + dev_err(&pdev->dev, "failed to request irq %d: > > %d\n", > > + irq, ret); > > + goto cleanup_clock; > > + } > > + } > > + > > + ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); > > + if (ret) > > + goto cleanup_clock; > > + > > + platform_set_drvdata(pdev, mpfs_gpio); > > + dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", > > ngpio); > > + > > + return 0; > > + > > +cleanup_clock: > > + clk_disable_unprepare(mpfs_gpio->clk); > > + return ret; > > +} > > + > > +static int mpfs_gpio_remove(struct platform_device *pdev) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = platform_get_drvdata(pdev); > > + > > + gpiochip_remove(&mpfs_gpio->gc); > > + clk_disable_unprepare(mpfs_gpio->clk); > > + > > + return 0; > > +} > > + > > +static const struct of_device_id mpfs_gpio_match[] = { > > + { .compatible = "microchip,mpfs-gpio", }, > > + { /* end of list */ }, > > +}; > > + > > +static struct platform_driver mpfs_gpio_driver = { > > + .probe = mpfs_gpio_probe, > > + .driver = { > > + .name = "microchip,mpfs-gpio", > > + .of_match_table = of_match_ptr(mpfs_gpio_match), > > + }, > > + .remove = mpfs_gpio_remove, > > +}; > > + > > +builtin_platform_driver(mpfs_gpio_driver);
On Tue, 05 Jul 2022 14:49:12 +0100, <lewis.hanly@microchip.com> wrote: > > From: Lewis Hanly <lewis.hanly@microchip.com> > > Add a driver to support the Polarfire SoC gpio controller. > > Signed-off-by: Lewis Hanly <lewis.hanly@microchip.com> > --- > drivers/gpio/Kconfig | 7 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-mpfs.c | 358 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 366 insertions(+) > create mode 100644 drivers/gpio/gpio-mpfs.c > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > index b01961999ced..e279eac198da 100644 > --- a/drivers/gpio/Kconfig > +++ b/drivers/gpio/Kconfig > @@ -490,6 +490,13 @@ config GPIO_PMIC_EIC_SPRD > help > Say yes here to support Spreadtrum PMIC EIC device. > > +config GPIO_POLARFIRE_SOC > + bool "Microchip FPGA GPIO support" > + depends on OF_GPIO > + select GPIOLIB_IRQCHIP > + help > + Say yes here to support the GPIO device on Microchip FPGAs > + > config GPIO_PXA > bool "PXA GPIO support" > depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile > index 14352f6dfe8e..3b8b6703e593 100644 > --- a/drivers/gpio/Makefile > +++ b/drivers/gpio/Makefile > @@ -119,6 +119,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o > 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_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-mpfs.c b/drivers/gpio/gpio-mpfs.c > new file mode 100644 > index 000000000000..df48f2836e97 > --- /dev/null > +++ b/drivers/gpio/gpio-mpfs.c > @@ -0,0 +1,358 @@ > +// SPDX-License-Identifier: (GPL-2.0) > +/* > + * Microchip PolarFire SoC (MPFS) GPIO controller driver > + * > + * Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries > + * > + * Author: Lewis Hanly <lewis.hanly@microchip.com> > + * > + */ > + > +#include <linux/bitops.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/errno.h> > +#include <linux/gpio/driver.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/irqchip/chained_irq.h> > +#include <linux/of.h> > +#include <linux/of_irq.h> > +#include <linux/platform_device.h> > +#include <linux/spinlock.h> > + > +#define NUM_GPIO 32 > +#define BYTE_BOUNDARY 0x04 > +#define MPFS_GPIO_EN_INT 3 > +#define MPFS_GPIO_EN_OUT_BUF BIT(2) > +#define MPFS_GPIO_EN_IN BIT(1) > +#define MPFS_GPIO_EN_OUT BIT(0) > + > +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 > +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 > +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 > +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 > +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 > +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) > +#define MPFS_IRQ_REG 0x80 > +#define MPFS_INP_REG 0x84 > +#define MPFS_OUTP_REG 0x88 > + > +struct mpfs_gpio_chip { > + void __iomem *base; > + struct clk *clk; > + spinlock_t lock; /* lock */ Interrupt controllers must use a raw spinlock. Please drop the pointless comment. > + struct gpio_chip gc; > +}; > + > +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int bit_offset, int value) > +{ > + u32 output = readl(addr); > + > + if (value) > + output |= BIT(bit_offset); > + else > + output &= ~BIT(bit_offset); Use __assign_bit() instead. and make value a bool to reflect the fact that this is a single bit. > + > + writel(output, addr); > +} > + > +static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; How can this happen? > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_IN; > + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; > + gpio_cfg &= ~MPFS_GPIO_EN_IN; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_get_direction(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + if (gpio_cfg & MPFS_GPIO_EN_IN) > + return 1; > + > + return 0; > +} > + > +static int mpfs_gpio_get(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & BIT(gpio_index)); > +} > + > +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return; > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > + gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > +} > + > +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + int gpio_index = irqd_to_hwirq(data); > + u32 interrupt_type; > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + > + switch (type) { > + case IRQ_TYPE_EDGE_BOTH: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; > + break; > + > + case IRQ_TYPE_EDGE_FALLING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; > + break; > + > + case IRQ_TYPE_EDGE_RISING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; > + break; > + > + case IRQ_TYPE_LEVEL_HIGH: > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; > + break; > + > + case IRQ_TYPE_LEVEL_LOW: > + default: What's this default for? > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; > + break; > + } > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; > + gpio_cfg |= interrupt_type; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static void mpfs_gpio_irq_enable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_direction_input(gc, gpio_index); > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 1); > +} > + > +static void mpfs_gpio_irq_disable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 0); > +} > + > +static struct irq_chip mpfs_gpio_irqchip = { Must be const. > + .name = "mpfs_gpio_irqchip", Drop the pointless "gpio_irqchip". > + .irq_set_type = mpfs_gpio_irq_set_type, > + .irq_enable = mpfs_gpio_irq_enable, > + .irq_disable = mpfs_gpio_irq_disable, These should be unmask/mask. enable/disable *supplement* unmask/mask on startup. > + .flags = IRQCHIP_MASK_ON_SUSPEND, > +}; No. Please read the documentation and use the current API. The kernel should already be telling you that your driver needs fixing. > + > +static irqreturn_t mpfs_gpio_irq_handler(int irq, void *mpfs_gpio_data) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; > + unsigned long status; > + int offset; > + > + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); > + > + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, offset, 1); > + generic_handle_irq(irq_find_mapping(mpfs_gpio->gc.irq.domain, offset)); > + } > + return IRQ_HANDLED; > +} > + > +static int mpfs_gpio_probe(struct platform_device *pdev) > +{ > + struct clk *clk; > + struct device *dev = &pdev->dev; > + struct device_node *node = pdev->dev.of_node; > + struct mpfs_gpio_chip *mpfs_gpio; > + int i, ret, ngpio; > + struct gpio_irq_chip *irq_c; > + > + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); > + if (!mpfs_gpio) > + return -ENOMEM; > + > + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(mpfs_gpio->base)) { > + dev_err(dev, "failed to allocate device memory\n"); > + return PTR_ERR(mpfs_gpio->base); > + } > + clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(clk)) > + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed to get clock\n"); > + > + ret = clk_prepare_enable(clk); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "failed to enable clock\n"); > + > + mpfs_gpio->clk = clk; > + > + spin_lock_init(&mpfs_gpio->lock); > + > + ngpio = of_irq_count(node); > + if (ngpio > NUM_GPIO) { > + dev_err(dev, "too many interrupts\n"); > + goto cleanup_clock; > + } > + > + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; > + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; > + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; > + mpfs_gpio->gc.get = mpfs_gpio_get; > + mpfs_gpio->gc.set = mpfs_gpio_set; > + mpfs_gpio->gc.base = -1; > + mpfs_gpio->gc.ngpio = ngpio; > + mpfs_gpio->gc.label = dev_name(dev); > + mpfs_gpio->gc.parent = dev; > + mpfs_gpio->gc.owner = THIS_MODULE; > + > + irq_c = &mpfs_gpio->gc.irq; > + irq_c->chip = &mpfs_gpio_irqchip; Same thing about the use of the current API. > + irq_c->chip->parent_device = dev; OK, you clearly are developing against an ancient kernel. Don't bother posting another version if you haven't tested your code on top of a recent -rc. > + irq_c->handler = handle_simple_irq; Why? This looks broken. You should use the flow that actually matches the triggering, and not this. Thanks, M.
On 05/07/2022 14:49, lewis.hanly@microchip.com wrote: > From: Lewis Hanly <lewis.hanly@microchip.com> > > Add a driver to support the Polarfire SoC gpio controller. > > Signed-off-by: Lewis Hanly <lewis.hanly@microchip.com> > --- > drivers/gpio/Kconfig | 7 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-mpfs.c | 358 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 366 insertions(+) > create mode 100644 drivers/gpio/gpio-mpfs.c > snip > diff --git a/drivers/gpio/gpio-mpfs.c b/drivers/gpio/gpio-mpfs.c > new file mode 100644 > index 000000000000..df48f2836e97 > --- /dev/null > +++ b/drivers/gpio/gpio-mpfs.c > @@ -0,0 +1,358 @@ > +// SPDX-License-Identifier: (GPL-2.0) > +/* > + * Microchip PolarFire SoC (MPFS) GPIO controller driver > + * > + * Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries > + * > + * Author: Lewis Hanly <lewis.hanly@microchip.com> > + * > + */ > + > +#include <linux/bitops.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/errno.h> > +#include <linux/gpio/driver.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/irqchip/chained_irq.h> > +#include <linux/of.h> > +#include <linux/of_irq.h> > +#include <linux/platform_device.h> > +#include <linux/spinlock.h> > + > +#define NUM_GPIO 32 > +#define BYTE_BOUNDARY 0x04 > +#define MPFS_GPIO_EN_INT 3 > +#define MPFS_GPIO_EN_OUT_BUF BIT(2) > +#define MPFS_GPIO_EN_IN BIT(1) > +#define MPFS_GPIO_EN_OUT BIT(0) > + > +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 > +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 > +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 > +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 > +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 > +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) > +#define MPFS_IRQ_REG 0x80 > +#define MPFS_INP_REG 0x84 > +#define MPFS_OUTP_REG 0x88 > + > +struct mpfs_gpio_chip { > + void __iomem *base; > + struct clk *clk; > + spinlock_t lock; /* lock */ > + struct gpio_chip gc; > +}; > + > +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int bit_offset, int value) > +{ > + u32 output = readl(addr); > + > + if (value) > + output |= BIT(bit_offset); > + else > + output &= ~BIT(bit_offset); > + > + writel(output, addr); > +} > + > +static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_IN; > + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; Is this necessary, shouldn't gpio layer have checked this already? > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; > + gpio_cfg &= ~MPFS_GPIO_EN_IN; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static int mpfs_gpio_get_direction(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; Is this necessary, shouldn't gpio layer have checked this already? > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + if (gpio_cfg & MPFS_GPIO_EN_IN) > + return 1; > + > + return 0; > +} > + > +static int mpfs_gpio_get(struct gpio_chip *gc, > + unsigned int gpio_index) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; Is this necessary, shouldn't gpio layer have checked this already? > + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & BIT(gpio_index)); > +} > + > +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return; Is this necessary, shouldn't gpio layer have checked this already? > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > + gpio_index, value); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > +} > + > +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + int gpio_index = irqd_to_hwirq(data); > + u32 interrupt_type; > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + u32 gpio_cfg; > + unsigned long flags; > + > + if (gpio_index >= NUM_GPIO) > + return -EINVAL; > Is this necessary, shouldn't gpio layer have checked this already? > + switch (type) { > + case IRQ_TYPE_EDGE_BOTH: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; > + break; > + > + case IRQ_TYPE_EDGE_FALLING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; > + break; > + > + case IRQ_TYPE_EDGE_RISING: > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; > + break; > + > + case IRQ_TYPE_LEVEL_HIGH: > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; > + break; > + > + case IRQ_TYPE_LEVEL_LOW: > + default: > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; > + break; > + } > + > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > + > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; > + gpio_cfg |= interrupt_type; > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); > + > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > + > + return 0; > +} > + > +static void mpfs_gpio_irq_enable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_direction_input(gc, gpio_index); > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 1); > +} > + > +static void mpfs_gpio_irq_disable(struct irq_data *data) > +{ > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > + > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), > + MPFS_GPIO_EN_INT, 0); > +} > + > +static struct irq_chip mpfs_gpio_irqchip = { > + .name = "mpfs_gpio_irqchip", > + .irq_set_type = mpfs_gpio_irq_set_type, > + .irq_enable = mpfs_gpio_irq_enable, > + .irq_disable = mpfs_gpio_irq_disable, > + .flags = IRQCHIP_MASK_ON_SUSPEND, > +}; > + > +static irqreturn_t mpfs_gpio_irq_handler(int irq, void *mpfs_gpio_data) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; > + unsigned long status; > + int offset; > + > + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); > + > + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, offset, 1); > + generic_handle_irq(irq_find_mapping(mpfs_gpio->gc.irq.domain, offset)); > + } > + return IRQ_HANDLED; > +} > + > +static int mpfs_gpio_probe(struct platform_device *pdev) > +{ > + struct clk *clk; > + struct device *dev = &pdev->dev; > + struct device_node *node = pdev->dev.of_node; > + struct mpfs_gpio_chip *mpfs_gpio; > + int i, ret, ngpio; > + struct gpio_irq_chip *irq_c; > + > + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); > + if (!mpfs_gpio) > + return -ENOMEM; > + > + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(mpfs_gpio->base)) { > + dev_err(dev, "failed to allocate device memory\n"); > + return PTR_ERR(mpfs_gpio->base); > + } seems to be a mix of dev_err and dev_err_probe() in here? > + clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(clk)) > + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed to get clock\n"); > + > + ret = clk_prepare_enable(clk); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "failed to enable clock\n"); > + > + mpfs_gpio->clk = clk; > + > + spin_lock_init(&mpfs_gpio->lock); > + > + ngpio = of_irq_count(node); > + if (ngpio > NUM_GPIO) { > + dev_err(dev, "too many interrupts\n"); > + goto cleanup_clock; > + } > + > + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; > + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; > + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; > + mpfs_gpio->gc.get = mpfs_gpio_get; > + mpfs_gpio->gc.set = mpfs_gpio_set; > + mpfs_gpio->gc.base = -1; > + mpfs_gpio->gc.ngpio = ngpio; > + mpfs_gpio->gc.label = dev_name(dev); > + mpfs_gpio->gc.parent = dev; > + mpfs_gpio->gc.owner = THIS_MODULE; > + > + irq_c = &mpfs_gpio->gc.irq; > + irq_c->chip = &mpfs_gpio_irqchip; > + irq_c->chip->parent_device = dev; > + irq_c->handler = handle_simple_irq; > + > + ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); > + if (ret < 0) { > + dev_err(dev, "failed to allocate descs\n"); > + goto cleanup_clock; > + } > + > + /* > + * Setup the interrupt handlers. Interrupts can be > + * direct and/or non-direct mode, based on register value: > + * GPIO_INTERRUPT_FAB_CR. > + */ > + for (i = 0; i < ngpio; i++) { > + int irq = platform_get_irq_optional(pdev, i); > + > + if (irq < 0) > + continue; > + > + ret = devm_request_irq(&pdev->dev, irq, > + mpfs_gpio_irq_handler, > + IRQF_SHARED, mpfs_gpio->gc.label, mpfs_gpio); > + if (ret) { > + dev_err(&pdev->dev, "failed to request irq %d: %d\n", > + irq, ret); > + goto cleanup_clock; > + } > + } > + > + ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); > + if (ret) > + goto cleanup_clock; > + > + platform_set_drvdata(pdev, mpfs_gpio); > + dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", ngpio); > + > + return 0; > + > +cleanup_clock: > + clk_disable_unprepare(mpfs_gpio->clk); > + return ret; > +} do you need to deal with EPROBEDEFER in the above probe? > + > +static int mpfs_gpio_remove(struct platform_device *pdev) > +{ > + struct mpfs_gpio_chip *mpfs_gpio = platform_get_drvdata(pdev); > + > + gpiochip_remove(&mpfs_gpio->gc); > + clk_disable_unprepare(mpfs_gpio->clk); > + > + return 0; > +} > + > +static const struct of_device_id mpfs_gpio_match[] = { > + { .compatible = "microchip,mpfs-gpio", }, > + { /* end of list */ }, > +}; > + > +static struct platform_driver mpfs_gpio_driver = { > + .probe = mpfs_gpio_probe, > + .driver = { > + .name = "microchip,mpfs-gpio", > + .of_match_table = of_match_ptr(mpfs_gpio_match), > + }, > + .remove = mpfs_gpio_remove, > +}; > + > +builtin_platform_driver(mpfs_gpio_driver); MODULE_AUTHOR and associated info could do with being at the bottom.
Hi, I love your patch! Yet something to improve: [auto build test ERROR on brgl/gpio/for-next] [also build test ERROR on linus/master v5.19-rc5 next-20220706] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/lewis-hanly-microchip-com/Add-Polarfire-SoC-GPIO-support/20220705-220421 base: https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio/for-next config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20220707/202207071219.0Jw0owWG-lkp@intel.com/config) compiler: gcc-11 (Debian 11.3.0-3) 11.3.0 reproduce (this is a W=1 build): # https://github.com/intel-lab-lkp/linux/commit/f1c1cf42734c00cf02babb6220f8b68a62e86d0e git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review lewis-hanly-microchip-com/Add-Polarfire-SoC-GPIO-support/20220705-220421 git checkout f1c1cf42734c00cf02babb6220f8b68a62e86d0e # save the config file mkdir build_dir && cp config build_dir/.config make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/ If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> All errors (new ones prefixed by >>): drivers/gpio/gpio-mpfs.c: In function 'mpfs_gpio_probe': >> drivers/gpio/gpio-mpfs.c:290:20: error: 'struct irq_chip' has no member named 'parent_device' 290 | irq_c->chip->parent_device = dev; | ^~ >> drivers/gpio/gpio-mpfs.c:310:23: error: implicit declaration of function 'devm_request_irq'; did you mean 'can_request_irq'? [-Werror=implicit-function-declaration] 310 | ret = devm_request_irq(&pdev->dev, irq, | ^~~~~~~~~~~~~~~~ | can_request_irq >> drivers/gpio/gpio-mpfs.c:312:40: error: 'IRQF_SHARED' undeclared (first use in this function) 312 | IRQF_SHARED, mpfs_gpio->gc.label, mpfs_gpio); | ^~~~~~~~~~~ drivers/gpio/gpio-mpfs.c:312:40: note: each undeclared identifier is reported only once for each function it appears in cc1: some warnings being treated as errors vim +290 drivers/gpio/gpio-mpfs.c 240 241 static int mpfs_gpio_probe(struct platform_device *pdev) 242 { 243 struct clk *clk; 244 struct device *dev = &pdev->dev; 245 struct device_node *node = pdev->dev.of_node; 246 struct mpfs_gpio_chip *mpfs_gpio; 247 int i, ret, ngpio; 248 struct gpio_irq_chip *irq_c; 249 250 mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); 251 if (!mpfs_gpio) 252 return -ENOMEM; 253 254 mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); 255 if (IS_ERR(mpfs_gpio->base)) { 256 dev_err(dev, "failed to allocate device memory\n"); 257 return PTR_ERR(mpfs_gpio->base); 258 } 259 clk = devm_clk_get(&pdev->dev, NULL); 260 if (IS_ERR(clk)) 261 return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed to get clock\n"); 262 263 ret = clk_prepare_enable(clk); 264 if (ret) 265 return dev_err_probe(&pdev->dev, ret, "failed to enable clock\n"); 266 267 mpfs_gpio->clk = clk; 268 269 spin_lock_init(&mpfs_gpio->lock); 270 271 ngpio = of_irq_count(node); 272 if (ngpio > NUM_GPIO) { 273 dev_err(dev, "too many interrupts\n"); 274 goto cleanup_clock; 275 } 276 277 mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; 278 mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; 279 mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; 280 mpfs_gpio->gc.get = mpfs_gpio_get; 281 mpfs_gpio->gc.set = mpfs_gpio_set; 282 mpfs_gpio->gc.base = -1; 283 mpfs_gpio->gc.ngpio = ngpio; 284 mpfs_gpio->gc.label = dev_name(dev); 285 mpfs_gpio->gc.parent = dev; 286 mpfs_gpio->gc.owner = THIS_MODULE; 287 288 irq_c = &mpfs_gpio->gc.irq; 289 irq_c->chip = &mpfs_gpio_irqchip; > 290 irq_c->chip->parent_device = dev; 291 irq_c->handler = handle_simple_irq; 292 293 ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); 294 if (ret < 0) { 295 dev_err(dev, "failed to allocate descs\n"); 296 goto cleanup_clock; 297 } 298 299 /* 300 * Setup the interrupt handlers. Interrupts can be 301 * direct and/or non-direct mode, based on register value: 302 * GPIO_INTERRUPT_FAB_CR. 303 */ 304 for (i = 0; i < ngpio; i++) { 305 int irq = platform_get_irq_optional(pdev, i); 306 307 if (irq < 0) 308 continue; 309 > 310 ret = devm_request_irq(&pdev->dev, irq, 311 mpfs_gpio_irq_handler, > 312 IRQF_SHARED, mpfs_gpio->gc.label, mpfs_gpio); 313 if (ret) { 314 dev_err(&pdev->dev, "failed to request irq %d: %d\n", 315 irq, ret); 316 goto cleanup_clock; 317 } 318 } 319 320 ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); 321 if (ret) 322 goto cleanup_clock; 323 324 platform_set_drvdata(pdev, mpfs_gpio); 325 dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", ngpio); 326 327 return 0; 328 329 cleanup_clock: 330 clk_disable_unprepare(mpfs_gpio->clk); 331 return ret; 332 } 333
On Thu, 07 Jul 2022 05:46:44 +0100, kernel test robot <lkp@intel.com> wrote: > > Hi, > > I love your patch! Yet something to improve: > > [auto build test ERROR on brgl/gpio/for-next] > [also build test ERROR on linus/master v5.19-rc5 next-20220706] > [If your patch is applied to the wrong git tree, kindly drop us a note. > And when submitting patch, we suggest to use '--base' as documented in > https://git-scm.com/docs/git-format-patch#_base_tree_information] > > url: https://github.com/intel-lab-lkp/linux/commits/lewis-hanly-microchip-com/Add-Polarfire-SoC-GPIO-support/20220705-220421 > base: https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio/for-next > config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20220707/202207071219.0Jw0owWG-lkp@intel.com/config) > compiler: gcc-11 (Debian 11.3.0-3) 11.3.0 > reproduce (this is a W=1 build): > # https://github.com/intel-lab-lkp/linux/commit/f1c1cf42734c00cf02babb6220f8b68a62e86d0e > git remote add linux-review https://github.com/intel-lab-lkp/linux > git fetch --no-tags linux-review lewis-hanly-microchip-com/Add-Polarfire-SoC-GPIO-support/20220705-220421 > git checkout f1c1cf42734c00cf02babb6220f8b68a62e86d0e > # save the config file > mkdir build_dir && cp config build_dir/.config > make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/ > > If you fix the issue, kindly add following tag where applicable > Reported-by: kernel test robot <lkp@intel.com> > > All errors (new ones prefixed by >>): > > drivers/gpio/gpio-mpfs.c: In function 'mpfs_gpio_probe': > >> drivers/gpio/gpio-mpfs.c:290:20: error: 'struct irq_chip' has no member named 'parent_device' > 290 | irq_c->chip->parent_device = dev; > | ^~ > >> drivers/gpio/gpio-mpfs.c:310:23: error: implicit declaration of function 'devm_request_irq'; did you mean 'can_request_irq'? [-Werror=implicit-function-declaration] > 310 | ret = devm_request_irq(&pdev->dev, irq, I missed this in my initial eyeballing of this patch. This should implement a chained interrupt flow, and not request the mux interrupt directly. That's yet another level of brokenness in this driver. M.
Thanks Ben for feedback,working on next version with some of your updates and also from Marc. Regards, Lewis On Wed, 2022-07-06 at 11:03 +0100, Ben Dooks wrote: > EXTERNAL EMAIL: Do not click links or open attachments unless you > know the content is safe > > On 05/07/2022 14:49, lewis.hanly@microchip.com wrote: > > From: Lewis Hanly <lewis.hanly@microchip.com> > > > > Add a driver to support the Polarfire SoC gpio controller. > > > > Signed-off-by: Lewis Hanly <lewis.hanly@microchip.com> > > --- > > drivers/gpio/Kconfig | 7 + > > drivers/gpio/Makefile | 1 + > > drivers/gpio/gpio-mpfs.c | 358 > > +++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 366 insertions(+) > > create mode 100644 drivers/gpio/gpio-mpfs.c > > > > snip > > > diff --git a/drivers/gpio/gpio-mpfs.c b/drivers/gpio/gpio-mpfs.c > > new file mode 100644 > > index 000000000000..df48f2836e97 > > --- /dev/null > > +++ b/drivers/gpio/gpio-mpfs.c > > @@ -0,0 +1,358 @@ > > +// SPDX-License-Identifier: (GPL-2.0) > > +/* > > + * Microchip PolarFire SoC (MPFS) GPIO controller driver > > + * > > + * Copyright (c) 2018-2022 Microchip Technology Inc. and its > > subsidiaries > > + * > > + * Author: Lewis Hanly <lewis.hanly@microchip.com> > > + * > > + */ > > + > > +#include <linux/bitops.h> > > +#include <linux/clk.h> > > +#include <linux/device.h> > > +#include <linux/errno.h> > > +#include <linux/gpio/driver.h> > > +#include <linux/init.h> > > +#include <linux/irq.h> > > +#include <linux/irqchip/chained_irq.h> > > +#include <linux/of.h> > > +#include <linux/of_irq.h> > > +#include <linux/platform_device.h> > > +#include <linux/spinlock.h> > > + > > +#define NUM_GPIO 32 > > +#define BYTE_BOUNDARY 0x04 > > +#define MPFS_GPIO_EN_INT 3 > > +#define MPFS_GPIO_EN_OUT_BUF BIT(2) > > +#define MPFS_GPIO_EN_IN BIT(1) > > +#define MPFS_GPIO_EN_OUT BIT(0) > > + > > +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 > > +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 > > +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 > > +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 > > +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 > > +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) > > +#define MPFS_IRQ_REG 0x80 > > +#define MPFS_INP_REG 0x84 > > +#define MPFS_OUTP_REG 0x88 > > + > > +struct mpfs_gpio_chip { > > + void __iomem *base; > > + struct clk *clk; > > + spinlock_t lock; /* lock */ > > + struct gpio_chip gc; > > +}; > > + > > +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int > > bit_offset, int value) > > +{ > > + u32 output = readl(addr); > > + > > + if (value) > > + output |= BIT(bit_offset); > > + else > > + output &= ~BIT(bit_offset); > > + > > + writel(output, addr); > > +} > > + > > +static int mpfs_gpio_direction_input(struct gpio_chip *gc, > > unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg |= MPFS_GPIO_EN_IN; > > + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_direction_output(struct gpio_chip *gc, > > unsigned int gpio_index, int value) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > Is this necessary, shouldn't gpio layer have checked this already? No, removing all instances. > > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; > > + gpio_cfg &= ~MPFS_GPIO_EN_IN; > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > > gpio_index, value); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_get_direction(struct gpio_chip *gc, > > + unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > Is this necessary, shouldn't gpio layer have checked this already? > > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + if (gpio_cfg & MPFS_GPIO_EN_IN) > > + return 1; > > + > > + return 0; > > +} > > + > > +static int mpfs_gpio_get(struct gpio_chip *gc, > > + unsigned int gpio_index) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > Is this necessary, shouldn't gpio layer have checked this already? > > > + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & > > BIT(gpio_index)); > > +} > > + > > +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int > > gpio_index, int value) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return; > > Is this necessary, shouldn't gpio layer have checked this already? > > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, > > + gpio_index, value); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > +} > > + > > +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned > > int type) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + int gpio_index = irqd_to_hwirq(data); > > + u32 interrupt_type; > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + u32 gpio_cfg; > > + unsigned long flags; > > + > > + if (gpio_index >= NUM_GPIO) > > + return -EINVAL; > > > > Is this necessary, shouldn't gpio layer have checked this already? > > > + switch (type) { > > + case IRQ_TYPE_EDGE_BOTH: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; > > + break; > > + > > + case IRQ_TYPE_EDGE_FALLING: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; > > + break; > > + > > + case IRQ_TYPE_EDGE_RISING: > > + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; > > + break; > > + > > + case IRQ_TYPE_LEVEL_HIGH: > > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; > > + break; > > + > > + case IRQ_TYPE_LEVEL_LOW: > > + default: > > + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; > > + break; > > + } > > + > > + spin_lock_irqsave(&mpfs_gpio->lock, flags); > > + > > + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; > > + gpio_cfg |= interrupt_type; > > + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY)); > > + > > + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); > > + > > + return 0; > > +} > > + > > +static void mpfs_gpio_irq_enable(struct irq_data *data) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > > + > > + mpfs_gpio_direction_input(gc, gpio_index); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > gpio_index, 1); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY), > > + MPFS_GPIO_EN_INT, 1); > > +} > > + > > +static void mpfs_gpio_irq_disable(struct irq_data *data) > > +{ > > + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); > > + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); > > + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; > > + > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > gpio_index, 1); > > + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * > > BYTE_BOUNDARY), > > + MPFS_GPIO_EN_INT, 0); > > +} > > + > > +static struct irq_chip mpfs_gpio_irqchip = { > > + .name = "mpfs_gpio_irqchip", > > + .irq_set_type = mpfs_gpio_irq_set_type, > > + .irq_enable = mpfs_gpio_irq_enable, > > + .irq_disable = mpfs_gpio_irq_disable, > > + .flags = IRQCHIP_MASK_ON_SUSPEND, > > +}; > > + > > +static irqreturn_t mpfs_gpio_irq_handler(int irq, void > > *mpfs_gpio_data) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; > > + unsigned long status; > > + int offset; > > + > > + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); > > + > > + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { > > + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, > > offset, 1); > > + generic_handle_irq(irq_find_mapping(mpfs_gpio- > > >gc.irq.domain, offset)); > > + } > > + return IRQ_HANDLED; > > +} > > + > > +static int mpfs_gpio_probe(struct platform_device *pdev) > > +{ > > + struct clk *clk; > > + struct device *dev = &pdev->dev; > > + struct device_node *node = pdev->dev.of_node; > > + struct mpfs_gpio_chip *mpfs_gpio; > > + int i, ret, ngpio; > > + struct gpio_irq_chip *irq_c; > > + > > + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), > > GFP_KERNEL); > > + if (!mpfs_gpio) > > + return -ENOMEM; > > + > > + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(mpfs_gpio->base)) { > > + dev_err(dev, "failed to allocate device memory\n"); > > + return PTR_ERR(mpfs_gpio->base); > > + } > > seems to be a mix of dev_err and dev_err_probe() in here? will update to to use only dev_err_probe > > > + clk = devm_clk_get(&pdev->dev, NULL); > > + if (IS_ERR(clk)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(clk), > > "failed to get clock\n"); > > + > > + ret = clk_prepare_enable(clk); > > + if (ret) > > + return dev_err_probe(&pdev->dev, ret, "failed to > > enable clock\n"); > > + > > + mpfs_gpio->clk = clk; > > + > > + spin_lock_init(&mpfs_gpio->lock); > > + > > + ngpio = of_irq_count(node); > > + if (ngpio > NUM_GPIO) { > > + dev_err(dev, "too many interrupts\n"); > > + goto cleanup_clock; > > + } > > + > > + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; > > + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; > > + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; > > + mpfs_gpio->gc.get = mpfs_gpio_get; > > + mpfs_gpio->gc.set = mpfs_gpio_set; > > + mpfs_gpio->gc.base = -1; > > + mpfs_gpio->gc.ngpio = ngpio; > > + mpfs_gpio->gc.label = dev_name(dev); > > + mpfs_gpio->gc.parent = dev; > > + mpfs_gpio->gc.owner = THIS_MODULE; > > + > > + irq_c = &mpfs_gpio->gc.irq; > > + irq_c->chip = &mpfs_gpio_irqchip; > > + irq_c->chip->parent_device = dev; > > + irq_c->handler = handle_simple_irq; > > + > > + ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); > > + if (ret < 0) { > > + dev_err(dev, "failed to allocate descs\n"); > > + goto cleanup_clock; > > + } > > + > > + /* > > + * Setup the interrupt handlers. Interrupts can be > > + * direct and/or non-direct mode, based on register value: > > + * GPIO_INTERRUPT_FAB_CR. > > + */ > > + for (i = 0; i < ngpio; i++) { > > + int irq = platform_get_irq_optional(pdev, i); > > + > > + if (irq < 0) > > + continue; > > + > > + ret = devm_request_irq(&pdev->dev, irq, > > + mpfs_gpio_irq_handler, > > + IRQF_SHARED, mpfs_gpio- > > >gc.label, mpfs_gpio); > > + if (ret) { > > + dev_err(&pdev->dev, "failed to request irq > > %d: %d\n", > > + irq, ret); > > + goto cleanup_clock; > > + } > > + } > > + > > + ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); > > + if (ret) > > + goto cleanup_clock; > > + > > + platform_set_drvdata(pdev, mpfs_gpio); > > + dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", > > ngpio); > > + > > + return 0; > > + > > +cleanup_clock: > > + clk_disable_unprepare(mpfs_gpio->clk); > > + return ret; > > +} > > do you need to deal with EPROBEDEFER in the above probe? > > > + > > +static int mpfs_gpio_remove(struct platform_device *pdev) > > +{ > > + struct mpfs_gpio_chip *mpfs_gpio = > > platform_get_drvdata(pdev); > > + > > + gpiochip_remove(&mpfs_gpio->gc); > > + clk_disable_unprepare(mpfs_gpio->clk); > > + > > + return 0; > > +} > > + > > +static const struct of_device_id mpfs_gpio_match[] = { > > + { .compatible = "microchip,mpfs-gpio", }, > > + { /* end of list */ }, > > +}; > > + > > +static struct platform_driver mpfs_gpio_driver = { > > + .probe = mpfs_gpio_probe, > > + .driver = { > > + .name = "microchip,mpfs-gpio", > > + .of_match_table = of_match_ptr(mpfs_gpio_match), > > + }, > > + .remove = mpfs_gpio_remove, > > +}; > > + > > +builtin_platform_driver(mpfs_gpio_driver); > > MODULE_AUTHOR and associated info could do with being at the bottom. It's not a module, author info at top of file. > > -- > Ben Dooks http://www.codethink.co.uk/ > Senior Engineer Codethink - Providing Genius > > https://www.codethink.co.uk/privacy.html
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b01961999ced..e279eac198da 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -490,6 +490,13 @@ config GPIO_PMIC_EIC_SPRD help Say yes here to support Spreadtrum PMIC EIC device. +config GPIO_POLARFIRE_SOC + bool "Microchip FPGA GPIO support" + depends on OF_GPIO + select GPIOLIB_IRQCHIP + help + Say yes here to support the GPIO device on Microchip FPGAs + config GPIO_PXA bool "PXA GPIO support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 14352f6dfe8e..3b8b6703e593 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o 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_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-mpfs.c b/drivers/gpio/gpio-mpfs.c new file mode 100644 index 000000000000..df48f2836e97 --- /dev/null +++ b/drivers/gpio/gpio-mpfs.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: (GPL-2.0) +/* + * Microchip PolarFire SoC (MPFS) GPIO controller driver + * + * Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries + * + * Author: Lewis Hanly <lewis.hanly@microchip.com> + * + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/gpio/driver.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define NUM_GPIO 32 +#define BYTE_BOUNDARY 0x04 +#define MPFS_GPIO_EN_INT 3 +#define MPFS_GPIO_EN_OUT_BUF BIT(2) +#define MPFS_GPIO_EN_IN BIT(1) +#define MPFS_GPIO_EN_OUT BIT(0) + +#define MPFS_GPIO_TYPE_INT_EDGE_BOTH 0x80 +#define MPFS_GPIO_TYPE_INT_EDGE_NEG 0x60 +#define MPFS_GPIO_TYPE_INT_EDGE_POS 0x40 +#define MPFS_GPIO_TYPE_INT_LEVEL_LOW 0x20 +#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH 0x00 +#define MPFS_GPIO_TYPE_INT_MASK GENMASK(7, 5) +#define MPFS_IRQ_REG 0x80 +#define MPFS_INP_REG 0x84 +#define MPFS_OUTP_REG 0x88 + +struct mpfs_gpio_chip { + void __iomem *base; + struct clk *clk; + spinlock_t lock; /* lock */ + struct gpio_chip gc; +}; + +static void mpfs_gpio_assign_bit(void __iomem *addr, unsigned int bit_offset, int value) +{ + u32 output = readl(addr); + + if (value) + output |= BIT(bit_offset); + else + output &= ~BIT(bit_offset); + + writel(output, addr); +} + +static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index) +{ + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + u32 gpio_cfg; + unsigned long flags; + + if (gpio_index >= NUM_GPIO) + return -EINVAL; + + spin_lock_irqsave(&mpfs_gpio->lock, flags); + + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + gpio_cfg |= MPFS_GPIO_EN_IN; + gpio_cfg &= ~(MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF); + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); + + return 0; +} + +static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value) +{ + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + u32 gpio_cfg; + unsigned long flags; + + if (gpio_index >= NUM_GPIO) + return -EINVAL; + + spin_lock_irqsave(&mpfs_gpio->lock, flags); + + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + gpio_cfg |= MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF; + gpio_cfg &= ~MPFS_GPIO_EN_IN; + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, gpio_index, value); + + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); + + return 0; +} + +static int mpfs_gpio_get_direction(struct gpio_chip *gc, + unsigned int gpio_index) +{ + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + u32 gpio_cfg; + + if (gpio_index >= NUM_GPIO) + return -EINVAL; + + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + + if (gpio_cfg & MPFS_GPIO_EN_IN) + return 1; + + return 0; +} + +static int mpfs_gpio_get(struct gpio_chip *gc, + unsigned int gpio_index) +{ + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + + if (gpio_index >= NUM_GPIO) + return -EINVAL; + + return !!(readl(mpfs_gpio->base + MPFS_INP_REG) & BIT(gpio_index)); +} + +static void mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value) +{ + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + unsigned long flags; + + if (gpio_index >= NUM_GPIO) + return; + + spin_lock_irqsave(&mpfs_gpio->lock, flags); + + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_OUTP_REG, + gpio_index, value); + + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); +} + +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + int gpio_index = irqd_to_hwirq(data); + u32 interrupt_type; + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + u32 gpio_cfg; + unsigned long flags; + + if (gpio_index >= NUM_GPIO) + return -EINVAL; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; + break; + + case IRQ_TYPE_EDGE_FALLING: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; + break; + + case IRQ_TYPE_EDGE_RISING: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; + break; + + case IRQ_TYPE_LEVEL_HIGH: + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; + break; + + case IRQ_TYPE_LEVEL_LOW: + default: + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; + break; + } + + spin_lock_irqsave(&mpfs_gpio->lock, flags); + + gpio_cfg = readl(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + gpio_cfg &= ~MPFS_GPIO_TYPE_INT_MASK; + gpio_cfg |= interrupt_type; + writel(gpio_cfg, mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY)); + + spin_unlock_irqrestore(&mpfs_gpio->lock, flags); + + return 0; +} + +static void mpfs_gpio_irq_enable(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; + + mpfs_gpio_direction_input(gc, gpio_index); + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), + MPFS_GPIO_EN_INT, 1); +} + +static void mpfs_gpio_irq_disable(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + int gpio_index = irqd_to_hwirq(data) % NUM_GPIO; + + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, gpio_index, 1); + mpfs_gpio_assign_bit(mpfs_gpio->base + (gpio_index * BYTE_BOUNDARY), + MPFS_GPIO_EN_INT, 0); +} + +static struct irq_chip mpfs_gpio_irqchip = { + .name = "mpfs_gpio_irqchip", + .irq_set_type = mpfs_gpio_irq_set_type, + .irq_enable = mpfs_gpio_irq_enable, + .irq_disable = mpfs_gpio_irq_disable, + .flags = IRQCHIP_MASK_ON_SUSPEND, +}; + +static irqreturn_t mpfs_gpio_irq_handler(int irq, void *mpfs_gpio_data) +{ + struct mpfs_gpio_chip *mpfs_gpio = mpfs_gpio_data; + unsigned long status; + int offset; + + status = readl(mpfs_gpio->base + MPFS_IRQ_REG); + + for_each_set_bit(offset, &status, mpfs_gpio->gc.ngpio) { + mpfs_gpio_assign_bit(mpfs_gpio->base + MPFS_IRQ_REG, offset, 1); + generic_handle_irq(irq_find_mapping(mpfs_gpio->gc.irq.domain, offset)); + } + return IRQ_HANDLED; +} + +static int mpfs_gpio_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct mpfs_gpio_chip *mpfs_gpio; + int i, ret, ngpio; + struct gpio_irq_chip *irq_c; + + mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); + if (!mpfs_gpio) + return -ENOMEM; + + mpfs_gpio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mpfs_gpio->base)) { + dev_err(dev, "failed to allocate device memory\n"); + return PTR_ERR(mpfs_gpio->base); + } + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "failed to get clock\n"); + + ret = clk_prepare_enable(clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to enable clock\n"); + + mpfs_gpio->clk = clk; + + spin_lock_init(&mpfs_gpio->lock); + + ngpio = of_irq_count(node); + if (ngpio > NUM_GPIO) { + dev_err(dev, "too many interrupts\n"); + goto cleanup_clock; + } + + mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input; + mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output; + mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction; + mpfs_gpio->gc.get = mpfs_gpio_get; + mpfs_gpio->gc.set = mpfs_gpio_set; + mpfs_gpio->gc.base = -1; + mpfs_gpio->gc.ngpio = ngpio; + mpfs_gpio->gc.label = dev_name(dev); + mpfs_gpio->gc.parent = dev; + mpfs_gpio->gc.owner = THIS_MODULE; + + irq_c = &mpfs_gpio->gc.irq; + irq_c->chip = &mpfs_gpio_irqchip; + irq_c->chip->parent_device = dev; + irq_c->handler = handle_simple_irq; + + ret = devm_irq_alloc_descs(&pdev->dev, -1, 0, ngpio, 0); + if (ret < 0) { + dev_err(dev, "failed to allocate descs\n"); + goto cleanup_clock; + } + + /* + * Setup the interrupt handlers. Interrupts can be + * direct and/or non-direct mode, based on register value: + * GPIO_INTERRUPT_FAB_CR. + */ + for (i = 0; i < ngpio; i++) { + int irq = platform_get_irq_optional(pdev, i); + + if (irq < 0) + continue; + + ret = devm_request_irq(&pdev->dev, irq, + mpfs_gpio_irq_handler, + IRQF_SHARED, mpfs_gpio->gc.label, mpfs_gpio); + if (ret) { + dev_err(&pdev->dev, "failed to request irq %d: %d\n", + irq, ret); + goto cleanup_clock; + } + } + + ret = gpiochip_add_data(&mpfs_gpio->gc, mpfs_gpio); + if (ret) + goto cleanup_clock; + + platform_set_drvdata(pdev, mpfs_gpio); + dev_info(dev, "Microchip MPFS GPIO registered %d GPIOs\n", ngpio); + + return 0; + +cleanup_clock: + clk_disable_unprepare(mpfs_gpio->clk); + return ret; +} + +static int mpfs_gpio_remove(struct platform_device *pdev) +{ + struct mpfs_gpio_chip *mpfs_gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&mpfs_gpio->gc); + clk_disable_unprepare(mpfs_gpio->clk); + + return 0; +} + +static const struct of_device_id mpfs_gpio_match[] = { + { .compatible = "microchip,mpfs-gpio", }, + { /* end of list */ }, +}; + +static struct platform_driver mpfs_gpio_driver = { + .probe = mpfs_gpio_probe, + .driver = { + .name = "microchip,mpfs-gpio", + .of_match_table = of_match_ptr(mpfs_gpio_match), + }, + .remove = mpfs_gpio_remove, +}; + +builtin_platform_driver(mpfs_gpio_driver);