diff options
Diffstat (limited to 'drivers/gpio/gpio-vf610.c')
| -rw-r--r-- | drivers/gpio/gpio-vf610.c | 289 |
1 files changed, 143 insertions, 146 deletions
diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index 1b79ebcfce3e..aa8586d8a787 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -10,25 +10,26 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/gpio/driver.h> +#include <linux/gpio/generic.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/irq.h> +#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> -#include <linux/of.h> -#include <linux/of_device.h> -#include <linux/of_irq.h> +#include <linux/property.h> #define VF610_GPIO_PER_PORT 32 struct fsl_gpio_soc_data { /* SoCs has a Port Data Direction Register (PDDR) */ bool have_paddr; + bool have_dual_base; }; struct vf610_gpio_port { - struct gpio_chip gc; + struct gpio_generic_chip chip; void __iomem *base; void __iomem *gpio_base; const struct fsl_gpio_soc_data *sdata; @@ -60,15 +61,26 @@ struct vf610_gpio_port { #define PORT_INT_EITHER_EDGE 0xb #define PORT_INT_LOGIC_ONE 0xc -static struct irq_chip vf610_gpio_irq_chip; +#define IMX8ULP_GPIO_BASE_OFF 0x40 +#define IMX8ULP_BASE_OFF 0x80 + +static const struct fsl_gpio_soc_data vf610_data = { + .have_dual_base = true, +}; static const struct fsl_gpio_soc_data imx_data = { .have_paddr = true, + .have_dual_base = true, +}; + +static const struct fsl_gpio_soc_data imx8ulp_data = { + .have_paddr = true, }; static const struct of_device_id vf610_gpio_dt_ids[] = { - { .compatible = "fsl,vf610-gpio", .data = NULL, }, + { .compatible = "fsl,vf610-gpio", .data = &vf610_data }, { .compatible = "fsl,imx7ulp-gpio", .data = &imx_data, }, + { .compatible = "fsl,imx8ulp-gpio", .data = &imx8ulp_data, }, { /* sentinel */ } }; @@ -82,63 +94,6 @@ static inline u32 vf610_gpio_readl(void __iomem *reg) return readl_relaxed(reg); } -static int vf610_gpio_get(struct gpio_chip *gc, unsigned int gpio) -{ - struct vf610_gpio_port *port = gpiochip_get_data(gc); - unsigned long mask = BIT(gpio); - void __iomem *addr; - - if (port->sdata && port->sdata->have_paddr) { - mask &= vf610_gpio_readl(port->gpio_base + GPIO_PDDR); - addr = mask ? port->gpio_base + GPIO_PDOR : - port->gpio_base + GPIO_PDIR; - return !!(vf610_gpio_readl(addr) & BIT(gpio)); - } else { - return !!(vf610_gpio_readl(port->gpio_base + GPIO_PDIR) - & BIT(gpio)); - } -} - -static void vf610_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) -{ - struct vf610_gpio_port *port = gpiochip_get_data(gc); - unsigned long mask = BIT(gpio); - - if (val) - vf610_gpio_writel(mask, port->gpio_base + GPIO_PSOR); - else - vf610_gpio_writel(mask, port->gpio_base + GPIO_PCOR); -} - -static int vf610_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) -{ - struct vf610_gpio_port *port = gpiochip_get_data(chip); - unsigned long mask = BIT(gpio); - u32 val; - - if (port->sdata && port->sdata->have_paddr) { - val = vf610_gpio_readl(port->gpio_base + GPIO_PDDR); - val &= ~mask; - vf610_gpio_writel(val, port->gpio_base + GPIO_PDDR); - } - - return pinctrl_gpio_direction_input(chip->base + gpio); -} - -static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, - int value) -{ - struct vf610_gpio_port *port = gpiochip_get_data(chip); - unsigned long mask = BIT(gpio); - - if (port->sdata && port->sdata->have_paddr) - vf610_gpio_writel(mask, port->gpio_base + GPIO_PDDR); - - vf610_gpio_set(chip, gpio, value); - - return pinctrl_gpio_direction_output(chip->base + gpio); -} - static void vf610_gpio_irq_handler(struct irq_desc *desc) { struct vf610_gpio_port *port = @@ -154,7 +109,7 @@ static void vf610_gpio_irq_handler(struct irq_desc *desc) for_each_set_bit(pin, &irq_isfr, VF610_GPIO_PER_PORT) { vf610_gpio_writel(BIT(pin), port->base + PORT_ISFR); - generic_handle_irq(irq_find_mapping(port->gc.irq.domain, pin)); + generic_handle_domain_irq(port->chip.gc.irq.domain, pin); } chained_irq_exit(chip, desc); @@ -207,20 +162,24 @@ static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type) static void vf610_gpio_irq_mask(struct irq_data *d) { - struct vf610_gpio_port *port = - gpiochip_get_data(irq_data_get_irq_chip_data(d)); - void __iomem *pcr_base = port->base + PORT_PCR(d->hwirq); + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct vf610_gpio_port *port = gpiochip_get_data(gc); + irq_hw_number_t gpio_num = irqd_to_hwirq(d); + void __iomem *pcr_base = port->base + PORT_PCR(gpio_num); vf610_gpio_writel(0, pcr_base); + gpiochip_disable_irq(gc, gpio_num); } static void vf610_gpio_irq_unmask(struct irq_data *d) { - struct vf610_gpio_port *port = - gpiochip_get_data(irq_data_get_irq_chip_data(d)); - void __iomem *pcr_base = port->base + PORT_PCR(d->hwirq); + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct vf610_gpio_port *port = gpiochip_get_data(gc); + irq_hw_number_t gpio_num = irqd_to_hwirq(d); + void __iomem *pcr_base = port->base + PORT_PCR(gpio_num); - vf610_gpio_writel(port->irqc[d->hwirq] << PORT_PCR_IRQC_OFFSET, + gpiochip_enable_irq(gc, gpio_num); + vf610_gpio_writel(port->irqc[gpio_num] << PORT_PCR_IRQC_OFFSET, pcr_base); } @@ -237,115 +196,152 @@ static int vf610_gpio_irq_set_wake(struct irq_data *d, u32 enable) return 0; } -static struct irq_chip vf610_gpio_irq_chip = { - .name = "gpio-vf610", - .irq_ack = vf610_gpio_irq_ack, - .irq_mask = vf610_gpio_irq_mask, - .irq_unmask = vf610_gpio_irq_unmask, - .irq_set_type = vf610_gpio_irq_set_type, - .irq_set_wake = vf610_gpio_irq_set_wake, +static const struct irq_chip vf610_irqchip = { + .name = "gpio-vf610", + .irq_ack = vf610_gpio_irq_ack, + .irq_mask = vf610_gpio_irq_mask, + .irq_unmask = vf610_gpio_irq_unmask, + .irq_set_type = vf610_gpio_irq_set_type, + .irq_set_wake = vf610_gpio_irq_set_wake, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND + | IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; +static void vf610_gpio_disable_clk(void *data) +{ + clk_disable_unprepare(data); +} + static int vf610_gpio_probe(struct platform_device *pdev) { + struct gpio_generic_chip_config config; struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct vf610_gpio_port *port; - struct resource *iores; struct gpio_chip *gc; + struct gpio_irq_chip *girq; + unsigned long flags; + int i; int ret; + bool dual_base; - port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; - port->sdata = of_device_get_match_data(dev); - iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); - port->base = devm_ioremap_resource(dev, iores); - if (IS_ERR(port->base)) - return PTR_ERR(port->base); + port->sdata = device_get_match_data(dev); + + dual_base = port->sdata->have_dual_base; + + /* + * Handle legacy compatible combinations which used two reg values + * for the i.MX8ULP and i.MX93. + */ + if (device_is_compatible(dev, "fsl,imx7ulp-gpio") && + (device_is_compatible(dev, "fsl,imx93-gpio") || + (device_is_compatible(dev, "fsl,imx8ulp-gpio")))) + dual_base = true; + + if (dual_base) { + port->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(port->base)) + return PTR_ERR(port->base); - iores = platform_get_resource(pdev, IORESOURCE_MEM, 1); - port->gpio_base = devm_ioremap_resource(dev, iores); - if (IS_ERR(port->gpio_base)) - return PTR_ERR(port->gpio_base); + port->gpio_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(port->gpio_base)) + return PTR_ERR(port->gpio_base); + } else { + port->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(port->base)) + return PTR_ERR(port->base); + + port->gpio_base = port->base + IMX8ULP_GPIO_BASE_OFF; + port->base = port->base + IMX8ULP_BASE_OFF; + } port->irq = platform_get_irq(pdev, 0); if (port->irq < 0) return port->irq; - port->clk_port = devm_clk_get(&pdev->dev, "port"); - if (!IS_ERR(port->clk_port)) { + port->clk_port = devm_clk_get(dev, "port"); + ret = PTR_ERR_OR_ZERO(port->clk_port); + if (!ret) { ret = clk_prepare_enable(port->clk_port); if (ret) return ret; - } else if (port->clk_port == ERR_PTR(-EPROBE_DEFER)) { + ret = devm_add_action_or_reset(dev, vf610_gpio_disable_clk, + port->clk_port); + if (ret) + return ret; + } else if (ret == -EPROBE_DEFER) { /* * Percolate deferrals, for anything else, * just live without the clocking. */ - return PTR_ERR(port->clk_port); + return ret; } - port->clk_gpio = devm_clk_get(&pdev->dev, "gpio"); - if (!IS_ERR(port->clk_gpio)) { + port->clk_gpio = devm_clk_get(dev, "gpio"); + ret = PTR_ERR_OR_ZERO(port->clk_gpio); + if (!ret) { ret = clk_prepare_enable(port->clk_gpio); - if (ret) { - clk_disable_unprepare(port->clk_port); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, vf610_gpio_disable_clk, + port->clk_gpio); + if (ret) return ret; - } - } else if (port->clk_gpio == ERR_PTR(-EPROBE_DEFER)) { - clk_disable_unprepare(port->clk_port); - return PTR_ERR(port->clk_gpio); + } else if (ret == -EPROBE_DEFER) { + return ret; } - platform_set_drvdata(pdev, port); - - gc = &port->gc; - gc->of_node = np; - gc->parent = dev; - gc->label = "vf610-gpio"; - gc->ngpio = VF610_GPIO_PER_PORT; - gc->base = of_alias_get_id(np, "gpio") * VF610_GPIO_PER_PORT; - - gc->request = gpiochip_generic_request; - gc->free = gpiochip_generic_free; - gc->direction_input = vf610_gpio_direction_input; - gc->get = vf610_gpio_get; - gc->direction_output = vf610_gpio_direction_output; - gc->set = vf610_gpio_set; - - ret = gpiochip_add_data(gc, port); - if (ret < 0) - return ret; + gc = &port->chip.gc; + flags = GPIO_GENERIC_PINCTRL_BACKEND; + /* + * We only read the output register for current value on output + * lines if the direction register is available so we can switch + * direction. + */ + if (port->sdata->have_paddr) + flags |= GPIO_GENERIC_READ_OUTPUT_REG_SET; + + config = (struct gpio_generic_chip_config) { + .dev = dev, + .sz = 4, + .dat = port->gpio_base + GPIO_PDIR, + .set = port->gpio_base + GPIO_PDOR, + .dirout = port->sdata->have_paddr ? + port->gpio_base + GPIO_PDDR : NULL, + .flags = flags, + }; + + ret = gpio_generic_chip_init(&port->chip, &config); + if (ret) + return dev_err_probe(dev, ret, "unable to init generic GPIO\n"); + gc->label = dev_name(dev); + gc->base = -1; + + /* Mask all GPIO interrupts */ + for (i = 0; i < gc->ngpio; i++) + vf610_gpio_writel(0, port->base + PORT_PCR(i)); /* Clear the interrupt status register for all GPIO's */ vf610_gpio_writel(~0, port->base + PORT_ISFR); - ret = gpiochip_irqchip_add(gc, &vf610_gpio_irq_chip, 0, - handle_edge_irq, IRQ_TYPE_NONE); - if (ret) { - dev_err(dev, "failed to add irqchip\n"); - gpiochip_remove(gc); - return ret; - } - gpiochip_set_chained_irqchip(gc, &vf610_gpio_irq_chip, port->irq, - vf610_gpio_irq_handler); - - return 0; -} - -static int vf610_gpio_remove(struct platform_device *pdev) -{ - struct vf610_gpio_port *port = platform_get_drvdata(pdev); - - gpiochip_remove(&port->gc); - if (!IS_ERR(port->clk_port)) - clk_disable_unprepare(port->clk_port); - if (!IS_ERR(port->clk_gpio)) - clk_disable_unprepare(port->clk_gpio); + girq = &gc->irq; + gpio_irq_chip_set_chip(girq, &vf610_irqchip); + girq->parent_handler = vf610_gpio_irq_handler; + girq->num_parents = 1; + girq->parents = devm_kcalloc(&pdev->dev, 1, + sizeof(*girq->parents), + GFP_KERNEL); + if (!girq->parents) + return -ENOMEM; + girq->parents[0] = port->irq; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_edge_irq; - return 0; + return devm_gpiochip_add_data(dev, gc, port); } static struct platform_driver vf610_gpio_driver = { @@ -354,7 +350,8 @@ static struct platform_driver vf610_gpio_driver = { .of_match_table = vf610_gpio_dt_ids, }, .probe = vf610_gpio_probe, - .remove = vf610_gpio_remove, }; -builtin_platform_driver(vf610_gpio_driver); +module_platform_driver(vf610_gpio_driver); +MODULE_DESCRIPTION("VF610 GPIO driver"); +MODULE_LICENSE("GPL"); |
