diff options
Diffstat (limited to 'drivers/gpio/gpio-rcar.c')
| -rw-r--r-- | drivers/gpio/gpio-rcar.c | 186 |
1 files changed, 145 insertions, 41 deletions
diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c index b4ca450947b..b6ae89ea881 100644 --- a/drivers/gpio/gpio-rcar.c +++ b/drivers/gpio/gpio-rcar.c @@ -22,9 +22,11 @@ #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_data/gpio-rcar.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/spinlock.h> #include <linux/slab.h> @@ -49,6 +51,9 @@ struct gpio_rcar_priv { #define POSNEG 0x20 #define EDGLEVEL 0x24 #define FILONOFF 0x28 +#define BOTHEDGE 0x4c + +#define RCAR_MAX_GPIO_PER_BANK 32 static inline u32 gpio_rcar_read(struct gpio_rcar_priv *p, int offs) { @@ -91,7 +96,8 @@ static void gpio_rcar_irq_enable(struct irq_data *d) static void gpio_rcar_config_interrupt_input_mode(struct gpio_rcar_priv *p, unsigned int hwirq, bool active_high_rising_edge, - bool level_trigger) + bool level_trigger, + bool both) { unsigned long flags; @@ -108,6 +114,10 @@ static void gpio_rcar_config_interrupt_input_mode(struct gpio_rcar_priv *p, /* Configure edge or level trigger in EDGLEVEL */ gpio_rcar_modify_bit(p, EDGLEVEL, hwirq, !level_trigger); + /* Select one edge or both edges in BOTHEDGE */ + if (p->config.has_both_edge_trigger) + gpio_rcar_modify_bit(p, BOTHEDGE, hwirq, both); + /* Select "Interrupt Input Mode" in IOINTSEL */ gpio_rcar_modify_bit(p, IOINTSEL, hwirq, true); @@ -127,16 +137,26 @@ static int gpio_rcar_irq_set_type(struct irq_data *d, unsigned int type) switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_LEVEL_HIGH: - gpio_rcar_config_interrupt_input_mode(p, hwirq, true, true); + gpio_rcar_config_interrupt_input_mode(p, hwirq, true, true, + false); break; case IRQ_TYPE_LEVEL_LOW: - gpio_rcar_config_interrupt_input_mode(p, hwirq, false, true); + gpio_rcar_config_interrupt_input_mode(p, hwirq, false, true, + false); break; case IRQ_TYPE_EDGE_RISING: - gpio_rcar_config_interrupt_input_mode(p, hwirq, true, false); + gpio_rcar_config_interrupt_input_mode(p, hwirq, true, false, + false); break; case IRQ_TYPE_EDGE_FALLING: - gpio_rcar_config_interrupt_input_mode(p, hwirq, false, false); + gpio_rcar_config_interrupt_input_mode(p, hwirq, false, false, + false); + break; + case IRQ_TYPE_EDGE_BOTH: + if (!p->config.has_both_edge_trigger) + return -EINVAL; + gpio_rcar_config_interrupt_input_mode(p, hwirq, true, false, + true); break; default: return -EINVAL; @@ -150,7 +170,8 @@ static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id) u32 pending; unsigned int offset, irqs_handled = 0; - while ((pending = gpio_rcar_read(p, INTDT))) { + while ((pending = gpio_rcar_read(p, INTDT) & + gpio_rcar_read(p, INTMSK))) { offset = __ffs(pending); gpio_rcar_write(p, INTCLR, BIT(offset)); generic_handle_irq(irq_find_mapping(p->irq_domain, offset)); @@ -214,7 +235,14 @@ static int gpio_rcar_direction_input(struct gpio_chip *chip, unsigned offset) static int gpio_rcar_get(struct gpio_chip *chip, unsigned offset) { - return (int)(gpio_rcar_read(gpio_to_priv(chip), INDT) & BIT(offset)); + u32 bit = BIT(offset); + + /* testing on r8a7790 shows that INDT does not show correct pin state + * when configured as output, so use OUTDT in case of output pins */ + if (gpio_rcar_read(gpio_to_priv(chip), INOUTSEL) & bit) + return (int)(gpio_rcar_read(gpio_to_priv(chip), OUTDT) & bit); + else + return (int)(gpio_rcar_read(gpio_to_priv(chip), INDT) & bit); } static void gpio_rcar_set(struct gpio_chip *chip, unsigned offset, int value) @@ -241,61 +269,130 @@ static int gpio_rcar_to_irq(struct gpio_chip *chip, unsigned offset) return irq_create_mapping(gpio_to_priv(chip)->irq_domain, offset); } -static int gpio_rcar_irq_domain_map(struct irq_domain *h, unsigned int virq, - irq_hw_number_t hw) +static int gpio_rcar_irq_domain_map(struct irq_domain *h, unsigned int irq, + irq_hw_number_t hwirq) { struct gpio_rcar_priv *p = h->host_data; - dev_dbg(&p->pdev->dev, "map hw irq = %d, virq = %d\n", (int)hw, virq); + dev_dbg(&p->pdev->dev, "map hw irq = %d, irq = %d\n", (int)hwirq, irq); - irq_set_chip_data(virq, h->host_data); - irq_set_chip_and_handler(virq, &p->irq_chip, handle_level_irq); - set_irq_flags(virq, IRQF_VALID); /* kill me now */ + irq_set_chip_data(irq, h->host_data); + irq_set_chip_and_handler(irq, &p->irq_chip, handle_level_irq); + set_irq_flags(irq, IRQF_VALID); /* kill me now */ return 0; } static struct irq_domain_ops gpio_rcar_irq_domain_ops = { .map = gpio_rcar_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +struct gpio_rcar_info { + bool has_both_edge_trigger; +}; + +static const struct of_device_id gpio_rcar_of_table[] = { + { + .compatible = "renesas,gpio-r8a7790", + .data = (void *)&(const struct gpio_rcar_info) { + .has_both_edge_trigger = true, + }, + }, { + .compatible = "renesas,gpio-r8a7791", + .data = (void *)&(const struct gpio_rcar_info) { + .has_both_edge_trigger = true, + }, + }, { + .compatible = "renesas,gpio-rcar", + .data = (void *)&(const struct gpio_rcar_info) { + .has_both_edge_trigger = false, + }, + }, { + /* Terminator */ + }, }; +MODULE_DEVICE_TABLE(of, gpio_rcar_of_table); + +static int gpio_rcar_parse_pdata(struct gpio_rcar_priv *p) +{ + struct gpio_rcar_config *pdata = dev_get_platdata(&p->pdev->dev); + struct device_node *np = p->pdev->dev.of_node; + struct of_phandle_args args; + int ret; + + if (pdata) { + p->config = *pdata; + } else if (IS_ENABLED(CONFIG_OF) && np) { + const struct of_device_id *match; + const struct gpio_rcar_info *info; + + match = of_match_node(gpio_rcar_of_table, np); + if (!match) + return -EINVAL; + + info = match->data; + + ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0, + &args); + p->config.number_of_pins = ret == 0 ? args.args[2] + : RCAR_MAX_GPIO_PER_BANK; + p->config.gpio_base = -1; + p->config.has_both_edge_trigger = info->has_both_edge_trigger; + } + + if (p->config.number_of_pins == 0 || + p->config.number_of_pins > RCAR_MAX_GPIO_PER_BANK) { + dev_warn(&p->pdev->dev, + "Invalid number of gpio lines %u, using %u\n", + p->config.number_of_pins, RCAR_MAX_GPIO_PER_BANK); + p->config.number_of_pins = RCAR_MAX_GPIO_PER_BANK; + } + + return 0; +} + static int gpio_rcar_probe(struct platform_device *pdev) { - struct gpio_rcar_config *pdata = pdev->dev.platform_data; struct gpio_rcar_priv *p; struct resource *io, *irq; struct gpio_chip *gpio_chip; struct irq_chip *irq_chip; - const char *name = dev_name(&pdev->dev); + struct device *dev = &pdev->dev; + const char *name = dev_name(dev); int ret; - p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); if (!p) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); ret = -ENOMEM; goto err0; } - /* deal with driver instance configuration */ - if (pdata) - p->config = *pdata; - p->pdev = pdev; - platform_set_drvdata(pdev, p); spin_lock_init(&p->lock); + /* Get device configuration from DT node or platform data. */ + ret = gpio_rcar_parse_pdata(p); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, p); + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + io = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!io || !irq) { - dev_err(&pdev->dev, "missing IRQ or IOMEM\n"); + dev_err(dev, "missing IRQ or IOMEM\n"); ret = -EINVAL; goto err0; } - p->base = devm_ioremap_nocache(&pdev->dev, io->start, - resource_size(io)); + p->base = devm_ioremap_nocache(dev, io->start, resource_size(io)); if (!p->base) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); + dev_err(dev, "failed to remap I/O memory\n"); ret = -ENXIO; goto err0; } @@ -309,6 +406,7 @@ static int gpio_rcar_probe(struct platform_device *pdev) gpio_chip->set = gpio_rcar_set; gpio_chip->to_irq = gpio_rcar_to_irq; gpio_chip->label = name; + gpio_chip->dev = dev; gpio_chip->owner = THIS_MODULE; gpio_chip->base = p->config.gpio_base; gpio_chip->ngpio = p->config.number_of_pins; @@ -317,10 +415,9 @@ static int gpio_rcar_probe(struct platform_device *pdev) irq_chip->name = name; irq_chip->irq_mask = gpio_rcar_irq_disable; irq_chip->irq_unmask = gpio_rcar_irq_enable; - irq_chip->irq_enable = gpio_rcar_irq_enable; - irq_chip->irq_disable = gpio_rcar_irq_disable; irq_chip->irq_set_type = gpio_rcar_irq_set_type; - irq_chip->flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_SET_TYPE_MASKED; + irq_chip->flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_SET_TYPE_MASKED + | IRQCHIP_MASK_ON_SUSPEND; p->irq_domain = irq_domain_add_simple(pdev->dev.of_node, p->config.number_of_pins, @@ -328,43 +425,47 @@ static int gpio_rcar_probe(struct platform_device *pdev) &gpio_rcar_irq_domain_ops, p); if (!p->irq_domain) { ret = -ENXIO; - dev_err(&pdev->dev, "cannot initialize irq domain\n"); - goto err1; + dev_err(dev, "cannot initialize irq domain\n"); + goto err0; } - if (devm_request_irq(&pdev->dev, irq->start, - gpio_rcar_irq_handler, 0, name, p)) { - dev_err(&pdev->dev, "failed to request IRQ\n"); + if (devm_request_irq(dev, irq->start, gpio_rcar_irq_handler, + IRQF_SHARED, name, p)) { + dev_err(dev, "failed to request IRQ\n"); ret = -ENOENT; goto err1; } ret = gpiochip_add(gpio_chip); if (ret) { - dev_err(&pdev->dev, "failed to add GPIO controller\n"); + dev_err(dev, "failed to add GPIO controller\n"); goto err1; } - dev_info(&pdev->dev, "driving %d GPIOs\n", p->config.number_of_pins); + dev_info(dev, "driving %d GPIOs\n", p->config.number_of_pins); /* warn in case of mismatch if irq base is specified */ if (p->config.irq_base) { ret = irq_find_mapping(p->irq_domain, 0); if (p->config.irq_base != ret) - dev_warn(&pdev->dev, "irq base mismatch (%u/%u)\n", + dev_warn(dev, "irq base mismatch (%u/%u)\n", p->config.irq_base, ret); } - ret = gpiochip_add_pin_range(gpio_chip, p->config.pctl_name, 0, - gpio_chip->base, gpio_chip->ngpio); - if (ret < 0) - dev_warn(&pdev->dev, "failed to add pin range\n"); + if (p->config.pctl_name) { + ret = gpiochip_add_pin_range(gpio_chip, p->config.pctl_name, 0, + gpio_chip->base, gpio_chip->ngpio); + if (ret < 0) + dev_warn(dev, "failed to add pin range\n"); + } return 0; err1: irq_domain_remove(p->irq_domain); err0: + pm_runtime_put(dev); + pm_runtime_disable(dev); return ret; } @@ -378,6 +479,8 @@ static int gpio_rcar_remove(struct platform_device *pdev) return ret; irq_domain_remove(p->irq_domain); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); return 0; } @@ -386,6 +489,7 @@ static struct platform_driver gpio_rcar_device_driver = { .remove = gpio_rcar_remove, .driver = { .name = "gpio_rcar", + .of_match_table = of_match_ptr(gpio_rcar_of_table), } }; |
