diff options
Diffstat (limited to 'drivers/gpio/gpio-ixp4xx.c')
-rw-r--r-- | drivers/gpio/gpio-ixp4xx.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-ixp4xx.c b/drivers/gpio/gpio-ixp4xx.c new file mode 100644 index 000000000..56656fb51 --- /dev/null +++ b/drivers/gpio/gpio-ixp4xx.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// IXP4 GPIO driver +// Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org> +// +// based on previous work and know-how from: +// Deepak Saxena <dsaxena@plexity.net> + +#include <linux/gpio/driver.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/irqchip.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/bitops.h> + +#define IXP4XX_REG_GPOUT 0x00 +#define IXP4XX_REG_GPOE 0x04 +#define IXP4XX_REG_GPIN 0x08 +#define IXP4XX_REG_GPIS 0x0C +#define IXP4XX_REG_GPIT1 0x10 +#define IXP4XX_REG_GPIT2 0x14 +#define IXP4XX_REG_GPCLK 0x18 +#define IXP4XX_REG_GPDBSEL 0x1C + +/* + * The hardware uses 3 bits to indicate interrupt "style". + * we clear and set these three bits accordingly. The lower 24 + * bits in two registers (GPIT1 and GPIT2) are used to set up + * the style for 8 lines each for a total of 16 GPIO lines. + */ +#define IXP4XX_GPIO_STYLE_ACTIVE_HIGH 0x0 +#define IXP4XX_GPIO_STYLE_ACTIVE_LOW 0x1 +#define IXP4XX_GPIO_STYLE_RISING_EDGE 0x2 +#define IXP4XX_GPIO_STYLE_FALLING_EDGE 0x3 +#define IXP4XX_GPIO_STYLE_TRANSITIONAL 0x4 +#define IXP4XX_GPIO_STYLE_MASK GENMASK(2, 0) +#define IXP4XX_GPIO_STYLE_SIZE 3 + +/** + * struct ixp4xx_gpio - IXP4 GPIO state container + * @dev: containing device for this instance + * @fwnode: the fwnode for this GPIO chip + * @gc: gpiochip for this instance + * @base: remapped I/O-memory base + * @irq_edge: Each bit represents an IRQ: 1: edge-triggered, + * 0: level triggered + */ +struct ixp4xx_gpio { + struct device *dev; + struct fwnode_handle *fwnode; + struct gpio_chip gc; + void __iomem *base; + unsigned long long irq_edge; +}; + +static void ixp4xx_gpio_irq_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct ixp4xx_gpio *g = gpiochip_get_data(gc); + + __raw_writel(BIT(d->hwirq), g->base + IXP4XX_REG_GPIS); +} + +static void ixp4xx_gpio_mask_irq(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + irq_chip_mask_parent(d); + gpiochip_disable_irq(gc, d->hwirq); +} + +static void ixp4xx_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct ixp4xx_gpio *g = gpiochip_get_data(gc); + + /* ACK when unmasking if not edge-triggered */ + if (!(g->irq_edge & BIT(d->hwirq))) + ixp4xx_gpio_irq_ack(d); + + gpiochip_enable_irq(gc, d->hwirq); + irq_chip_unmask_parent(d); +} + +static int ixp4xx_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct ixp4xx_gpio *g = gpiochip_get_data(gc); + int line = d->hwirq; + unsigned long flags; + u32 int_style; + u32 int_reg; + u32 val; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + irq_set_handler_locked(d, handle_edge_irq); + int_style = IXP4XX_GPIO_STYLE_TRANSITIONAL; + g->irq_edge |= BIT(d->hwirq); + break; + case IRQ_TYPE_EDGE_RISING: + irq_set_handler_locked(d, handle_edge_irq); + int_style = IXP4XX_GPIO_STYLE_RISING_EDGE; + g->irq_edge |= BIT(d->hwirq); + break; + case IRQ_TYPE_EDGE_FALLING: + irq_set_handler_locked(d, handle_edge_irq); + int_style = IXP4XX_GPIO_STYLE_FALLING_EDGE; + g->irq_edge |= BIT(d->hwirq); + break; + case IRQ_TYPE_LEVEL_HIGH: + irq_set_handler_locked(d, handle_level_irq); + int_style = IXP4XX_GPIO_STYLE_ACTIVE_HIGH; + g->irq_edge &= ~BIT(d->hwirq); + break; + case IRQ_TYPE_LEVEL_LOW: + irq_set_handler_locked(d, handle_level_irq); + int_style = IXP4XX_GPIO_STYLE_ACTIVE_LOW; + g->irq_edge &= ~BIT(d->hwirq); + break; + default: + return -EINVAL; + } + + if (line >= 8) { + /* pins 8-15 */ + line -= 8; + int_reg = IXP4XX_REG_GPIT2; + } else { + /* pins 0-7 */ + int_reg = IXP4XX_REG_GPIT1; + } + + raw_spin_lock_irqsave(&g->gc.bgpio_lock, flags); + + /* Clear the style for the appropriate pin */ + val = __raw_readl(g->base + int_reg); + val &= ~(IXP4XX_GPIO_STYLE_MASK << (line * IXP4XX_GPIO_STYLE_SIZE)); + __raw_writel(val, g->base + int_reg); + + __raw_writel(BIT(line), g->base + IXP4XX_REG_GPIS); + + /* Set the new style */ + val = __raw_readl(g->base + int_reg); + val |= (int_style << (line * IXP4XX_GPIO_STYLE_SIZE)); + __raw_writel(val, g->base + int_reg); + + /* Force-configure this line as an input */ + val = __raw_readl(g->base + IXP4XX_REG_GPOE); + val |= BIT(d->hwirq); + __raw_writel(val, g->base + IXP4XX_REG_GPOE); + + raw_spin_unlock_irqrestore(&g->gc.bgpio_lock, flags); + + /* This parent only accept level high (asserted) */ + return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); +} + +static const struct irq_chip ixp4xx_gpio_irqchip = { + .name = "IXP4GPIO", + .irq_ack = ixp4xx_gpio_irq_ack, + .irq_mask = ixp4xx_gpio_mask_irq, + .irq_unmask = ixp4xx_gpio_irq_unmask, + .irq_set_type = ixp4xx_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int ixp4xx_gpio_child_to_parent_hwirq(struct gpio_chip *gc, + unsigned int child, + unsigned int child_type, + unsigned int *parent, + unsigned int *parent_type) +{ + /* All these interrupts are level high in the CPU */ + *parent_type = IRQ_TYPE_LEVEL_HIGH; + + /* GPIO lines 0..12 have dedicated IRQs */ + if (child == 0) { + *parent = 6; + return 0; + } + if (child == 1) { + *parent = 7; + return 0; + } + if (child >= 2 && child <= 12) { + *parent = child + 17; + return 0; + } + return -EINVAL; +} + +static int ixp4xx_gpio_probe(struct platform_device *pdev) +{ + unsigned long flags; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct irq_domain *parent; + struct resource *res; + struct ixp4xx_gpio *g; + struct gpio_irq_chip *girq; + struct device_node *irq_parent; + int ret; + + g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL); + if (!g) + return -ENOMEM; + g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + g->base = devm_ioremap_resource(dev, res); + if (IS_ERR(g->base)) + return PTR_ERR(g->base); + + irq_parent = of_irq_find_parent(np); + if (!irq_parent) { + dev_err(dev, "no IRQ parent node\n"); + return -ENODEV; + } + parent = irq_find_host(irq_parent); + if (!parent) { + dev_err(dev, "no IRQ parent domain\n"); + return -ENODEV; + } + g->fwnode = of_node_to_fwnode(np); + + /* + * Make sure GPIO 14 and 15 are NOT used as clocks but GPIO on + * specific machines. + */ + if (of_machine_is_compatible("dlink,dsm-g600-a") || + of_machine_is_compatible("iom,nas-100d")) + __raw_writel(0x0, g->base + IXP4XX_REG_GPCLK); + + /* + * This is a very special big-endian ARM issue: when the IXP4xx is + * run in big endian mode, all registers in the machine are switched + * around to the CPU-native endianness. As you see mostly in the + * driver we use __raw_readl()/__raw_writel() to access the registers + * in the appropriate order. With the GPIO library we need to specify + * byte order explicitly, so this flag needs to be set when compiling + * for big endian. + */ +#if defined(CONFIG_CPU_BIG_ENDIAN) + flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER; +#else + flags = 0; +#endif + + /* Populate and register gpio chip */ + ret = bgpio_init(&g->gc, dev, 4, + g->base + IXP4XX_REG_GPIN, + g->base + IXP4XX_REG_GPOUT, + NULL, + NULL, + g->base + IXP4XX_REG_GPOE, + flags); + if (ret) { + dev_err(dev, "unable to init generic GPIO\n"); + return ret; + } + g->gc.ngpio = 16; + g->gc.label = "IXP4XX_GPIO_CHIP"; + /* + * TODO: when we have migrated to device tree and all GPIOs + * are fetched using phandles, set this to -1 to get rid of + * the fixed gpiochip base. + */ + g->gc.base = 0; + g->gc.parent = &pdev->dev; + g->gc.owner = THIS_MODULE; + + girq = &g->gc.irq; + gpio_irq_chip_set_chip(girq, &ixp4xx_gpio_irqchip); + girq->fwnode = g->fwnode; + girq->parent_domain = parent; + girq->child_to_parent_hwirq = ixp4xx_gpio_child_to_parent_hwirq; + girq->handler = handle_bad_irq; + girq->default_type = IRQ_TYPE_NONE; + + ret = devm_gpiochip_add_data(dev, &g->gc, g); + if (ret) { + dev_err(dev, "failed to add SoC gpiochip\n"); + return ret; + } + + platform_set_drvdata(pdev, g); + dev_info(dev, "IXP4 GPIO registered\n"); + + return 0; +} + +static const struct of_device_id ixp4xx_gpio_of_match[] = { + { + .compatible = "intel,ixp4xx-gpio", + }, + {}, +}; + + +static struct platform_driver ixp4xx_gpio_driver = { + .driver = { + .name = "ixp4xx-gpio", + .of_match_table = of_match_ptr(ixp4xx_gpio_of_match), + }, + .probe = ixp4xx_gpio_probe, +}; +builtin_platform_driver(ixp4xx_gpio_driver); |