diff options
Diffstat (limited to 'drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c')
-rw-r--r-- | drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c | 157 |
1 files changed, 131 insertions, 26 deletions
diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c b/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c index ded7d765af2e..08dd62b5cebe 100644 --- a/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c @@ -55,6 +55,8 @@ #define PM8XXX_MAX_GPIOS 44 +#define PM8XXX_GPIO_PHYSICAL_OFFSET 1 + /* custom pinconf parameters */ #define PM8XXX_QCOM_DRIVE_STRENGH (PIN_CONFIG_END + 1) #define PM8XXX_QCOM_PULL_UP_STRENGTH (PIN_CONFIG_END + 2) @@ -99,6 +101,9 @@ struct pm8xxx_gpio { struct pinctrl_desc desc; unsigned npins; + + struct fwnode_handle *fwnode; + struct irq_domain *domain; }; static const struct pinconf_generic_params pm8xxx_gpio_bindings[] = { @@ -499,11 +504,12 @@ static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset) if (pin->mode == PM8XXX_GPIO_MODE_OUTPUT) { ret = pin->output_value; - } else { + } else if (pin->irq >= 0) { ret = irq_get_irqchip_state(pin->irq, IRQCHIP_STATE_LINE_LEVEL, &state); if (!ret) ret = !!state; - } + } else + ret = -EINVAL; return ret; } @@ -533,7 +539,7 @@ static int pm8xxx_gpio_of_xlate(struct gpio_chip *chip, if (flags) *flags = gpio_desc->args[1]; - return gpio_desc->args[0] - 1; + return gpio_desc->args[0] - PM8XXX_GPIO_PHYSICAL_OFFSET; } @@ -541,8 +547,31 @@ static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { struct pm8xxx_gpio *pctrl = gpiochip_get_data(chip); struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + struct irq_fwspec fwspec; + int ret; + + fwspec.fwnode = pctrl->fwnode; + fwspec.param_count = 2; + fwspec.param[0] = offset + PM8XXX_GPIO_PHYSICAL_OFFSET; + fwspec.param[1] = IRQ_TYPE_EDGE_RISING; + + ret = irq_create_fwspec_mapping(&fwspec); + + /* + * Cache the IRQ since pm8xxx_gpio_get() needs this to get determine the + * line level. + */ + pin->irq = ret; + + return ret; +} + +static void pm8xxx_gpio_free(struct gpio_chip *chip, unsigned int offset) +{ + struct pm8xxx_gpio *pctrl = gpiochip_get_data(chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; - return pin->irq; + pin->irq = -1; } #ifdef CONFIG_DEBUG_FS @@ -571,7 +600,7 @@ static void pm8xxx_gpio_dbg_show_one(struct seq_file *s, "no", "high", "medium", "low" }; - seq_printf(s, " gpio%-2d:", offset + 1); + seq_printf(s, " gpio%-2d:", offset + PM8XXX_GPIO_PHYSICAL_OFFSET); if (pin->disable) { seq_puts(s, " ---"); } else { @@ -603,6 +632,7 @@ static void pm8xxx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) #endif static const struct gpio_chip pm8xxx_gpio_template = { + .free = pm8xxx_gpio_free, .direction_input = pm8xxx_gpio_direction_input, .direction_output = pm8xxx_gpio_direction_output, .get = pm8xxx_gpio_get, @@ -664,13 +694,75 @@ static int pm8xxx_pin_populate(struct pm8xxx_gpio *pctrl, return 0; } +static struct irq_chip pm8xxx_irq_chip = { + .name = "ssbi-gpio", + .irq_mask_ack = irq_chip_mask_ack_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_set_type = irq_chip_set_type_parent, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, +}; + +static int pm8xxx_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + struct pm8xxx_gpio *pctrl = container_of(domain->host_data, + struct pm8xxx_gpio, chip); + + if (fwspec->param_count != 2 || fwspec->param[0] < 1 || + fwspec->param[0] > pctrl->chip.ngpio) + return -EINVAL; + + *hwirq = fwspec->param[0] - PM8XXX_GPIO_PHYSICAL_OFFSET; + *type = fwspec->param[1]; + + return 0; +} + +static int pm8xxx_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct pm8xxx_gpio *pctrl = container_of(domain->host_data, + struct pm8xxx_gpio, chip); + struct irq_fwspec *fwspec = data; + struct irq_fwspec parent_fwspec; + irq_hw_number_t hwirq; + unsigned int type; + int ret, i; + + ret = pm8xxx_domain_translate(domain, fwspec, &hwirq, &type); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) + irq_domain_set_info(domain, virq + i, hwirq + i, + &pm8xxx_irq_chip, pctrl, handle_level_irq, + NULL, NULL); + + parent_fwspec.fwnode = domain->parent->fwnode; + parent_fwspec.param_count = 2; + parent_fwspec.param[0] = hwirq + 0xc0; + parent_fwspec.param[1] = fwspec->param[1]; + + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); +} + +static const struct irq_domain_ops pm8xxx_domain_ops = { + .activate = gpiochip_irq_domain_activate, + .alloc = pm8xxx_domain_alloc, + .deactivate = gpiochip_irq_domain_deactivate, + .free = irq_domain_free_irqs_common, + .translate = pm8xxx_domain_translate, +}; + static const struct of_device_id pm8xxx_gpio_of_match[] = { - { .compatible = "qcom,pm8018-gpio" }, - { .compatible = "qcom,pm8038-gpio" }, - { .compatible = "qcom,pm8058-gpio" }, - { .compatible = "qcom,pm8917-gpio" }, - { .compatible = "qcom,pm8921-gpio" }, - { .compatible = "qcom,ssbi-gpio" }, + { .compatible = "qcom,pm8018-gpio", .data = (void *) 6 }, + { .compatible = "qcom,pm8038-gpio", .data = (void *) 12 }, + { .compatible = "qcom,pm8058-gpio", .data = (void *) 44 }, + { .compatible = "qcom,pm8917-gpio", .data = (void *) 38 }, + { .compatible = "qcom,pm8921-gpio", .data = (void *) 44 }, { }, }; MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match); @@ -678,22 +770,18 @@ MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match); static int pm8xxx_gpio_probe(struct platform_device *pdev) { struct pm8xxx_pin_data *pin_data; + struct irq_domain *parent_domain; + struct device_node *parent_node; struct pinctrl_pin_desc *pins; struct pm8xxx_gpio *pctrl; - int ret; - int i, npins; + int ret, i; pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL); if (!pctrl) return -ENOMEM; pctrl->dev = &pdev->dev; - npins = platform_irq_count(pdev); - if (!npins) - return -EINVAL; - if (npins < 0) - return npins; - pctrl->npins = npins; + pctrl->npins = (uintptr_t) device_get_match_data(&pdev->dev); pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!pctrl->regmap) { @@ -720,12 +808,7 @@ static int pm8xxx_gpio_probe(struct platform_device *pdev) for (i = 0; i < pctrl->desc.npins; i++) { pin_data[i].reg = SSBI_REG_ADDR_GPIO(i); - pin_data[i].irq = platform_get_irq(pdev, i); - if (pin_data[i].irq < 0) { - dev_err(&pdev->dev, - "missing interrupts for pin %d\n", i); - return pin_data[i].irq; - } + pin_data[i].irq = -1; ret = pm8xxx_pin_populate(pctrl, &pin_data[i]); if (ret) @@ -756,10 +839,29 @@ static int pm8xxx_gpio_probe(struct platform_device *pdev) pctrl->chip.of_gpio_n_cells = 2; pctrl->chip.label = dev_name(pctrl->dev); pctrl->chip.ngpio = pctrl->npins; + + parent_node = of_irq_find_parent(pctrl->dev->of_node); + if (!parent_node) + return -ENXIO; + + parent_domain = irq_find_host(parent_node); + of_node_put(parent_node); + if (!parent_domain) + return -ENXIO; + + pctrl->fwnode = of_node_to_fwnode(pctrl->dev->of_node); + pctrl->domain = irq_domain_create_hierarchy(parent_domain, 0, + pctrl->chip.ngpio, + pctrl->fwnode, + &pm8xxx_domain_ops, + &pctrl->chip); + if (!pctrl->domain) + return -ENODEV; + ret = gpiochip_add_data(&pctrl->chip, pctrl); if (ret) { dev_err(&pdev->dev, "failed register gpiochip\n"); - return ret; + goto err_chip_add_data; } /* @@ -789,6 +891,8 @@ static int pm8xxx_gpio_probe(struct platform_device *pdev) unregister_gpiochip: gpiochip_remove(&pctrl->chip); +err_chip_add_data: + irq_domain_remove(pctrl->domain); return ret; } @@ -798,6 +902,7 @@ static int pm8xxx_gpio_remove(struct platform_device *pdev) struct pm8xxx_gpio *pctrl = platform_get_drvdata(pdev); gpiochip_remove(&pctrl->chip); + irq_domain_remove(pctrl->domain); return 0; } |