summaryrefslogtreecommitdiffstats
path: root/drivers/pinctrl
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pinctrl')
-rw-r--r--drivers/pinctrl/qcom/Kconfig2
-rw-r--r--drivers/pinctrl/qcom/pinctrl-spmi-gpio.c141
-rw-r--r--drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c157
3 files changed, 250 insertions, 50 deletions
diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig
index 836e9f3eae4c..2e66ab72c10b 100644
--- a/drivers/pinctrl/qcom/Kconfig
+++ b/drivers/pinctrl/qcom/Kconfig
@@ -137,6 +137,7 @@ config PINCTRL_QCOM_SPMI_PMIC
select PINMUX
select PINCONF
select GENERIC_PINCONF
+ select IRQ_DOMAIN_HIERARCHY
help
This is the pinctrl, pinmux, pinconf and gpiolib driver for the
Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips,
@@ -149,6 +150,7 @@ config PINCTRL_QCOM_SSBI_PMIC
select PINMUX
select PINCONF
select GENERIC_PINCONF
+ select IRQ_DOMAIN_HIERARCHY
help
This is the pinctrl, pinmux, pinconf and gpiolib driver for the
Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips,
diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c
index 4458d44dfcf6..cb512c7a5251 100644
--- a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c
+++ b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c
@@ -12,6 +12,7 @@
*/
#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -136,7 +137,6 @@ enum pmic_gpio_func_index {
/**
* struct pmic_gpio_pad - keep current GPIO settings
* @base: Address base in SPMI device.
- * @irq: IRQ number which this GPIO generate.
* @is_enabled: Set to false when GPIO should be put in high Z state.
* @out_value: Cached pin output value
* @have_buffer: Set to true if GPIO output could be configured in push-pull,
@@ -156,7 +156,6 @@ enum pmic_gpio_func_index {
*/
struct pmic_gpio_pad {
u16 base;
- int irq;
bool is_enabled;
bool out_value;
bool have_buffer;
@@ -179,6 +178,8 @@ struct pmic_gpio_state {
struct regmap *map;
struct pinctrl_dev *ctrl;
struct gpio_chip chip;
+ struct fwnode_handle *fwnode;
+ struct irq_domain *domain;
};
static const struct pinconf_generic_params pmic_gpio_bindings[] = {
@@ -761,11 +762,18 @@ static int pmic_gpio_of_xlate(struct gpio_chip *chip,
static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned pin)
{
struct pmic_gpio_state *state = gpiochip_get_data(chip);
- struct pmic_gpio_pad *pad;
+ struct irq_fwspec fwspec;
- pad = state->ctrl->desc->pins[pin].drv_data;
+ fwspec.fwnode = state->fwnode;
+ fwspec.param_count = 2;
+ fwspec.param[0] = pin + PMIC_GPIO_PHYSICAL_OFFSET;
+ /*
+ * Set the type to a safe value temporarily. This will be overwritten
+ * later with the proper value by irq_set_type.
+ */
+ fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
- return pad->irq;
+ return irq_create_fwspec_mapping(&fwspec);
}
static void pmic_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
@@ -935,8 +943,79 @@ static int pmic_gpio_populate(struct pmic_gpio_state *state,
return 0;
}
+static struct irq_chip pmic_gpio_irq_chip = {
+ .name = "spmi-gpio",
+ .irq_ack = irq_chip_ack_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_set_wake = irq_chip_set_wake_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int pmic_gpio_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ struct pmic_gpio_state *state = container_of(domain->host_data,
+ struct pmic_gpio_state,
+ chip);
+
+ if (fwspec->param_count != 2 ||
+ fwspec->param[0] < 1 || fwspec->param[0] > state->chip.ngpio)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0] - PMIC_GPIO_PHYSICAL_OFFSET;
+ *type = fwspec->param[1];
+
+ return 0;
+}
+
+static int pmic_gpio_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct pmic_gpio_state *state = container_of(domain->host_data,
+ struct pmic_gpio_state,
+ chip);
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ irq_hw_number_t hwirq;
+ unsigned int type;
+ int ret, i;
+
+ ret = pmic_gpio_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,
+ &pmic_gpio_irq_chip, state,
+ handle_level_irq, NULL, NULL);
+
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 4;
+ parent_fwspec.param[0] = 0;
+ parent_fwspec.param[1] = hwirq + 0xc0;
+ parent_fwspec.param[2] = 0;
+ parent_fwspec.param[3] = fwspec->param[1];
+
+ return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+}
+
+static const struct irq_domain_ops pmic_gpio_domain_ops = {
+ .activate = gpiochip_irq_domain_activate,
+ .alloc = pmic_gpio_domain_alloc,
+ .deactivate = gpiochip_irq_domain_deactivate,
+ .free = irq_domain_free_irqs_common,
+ .translate = pmic_gpio_domain_translate,
+};
+
static int pmic_gpio_probe(struct platform_device *pdev)
{
+ struct irq_domain *parent_domain;
+ struct device_node *parent_node;
struct device *dev = &pdev->dev;
struct pinctrl_pin_desc *pindesc;
struct pinctrl_desc *pctrldesc;
@@ -951,13 +1030,7 @@ static int pmic_gpio_probe(struct platform_device *pdev)
return ret;
}
- npins = platform_irq_count(pdev);
- if (!npins)
- return -EINVAL;
- if (npins < 0)
- return npins;
-
- BUG_ON(npins > ARRAY_SIZE(pmic_gpio_groups));
+ npins = (uintptr_t) device_get_match_data(&pdev->dev);
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
if (!state)
@@ -999,10 +1072,6 @@ static int pmic_gpio_probe(struct platform_device *pdev)
pindesc->number = i;
pindesc->name = pmic_gpio_groups[i];
- pad->irq = platform_get_irq(pdev, i);
- if (pad->irq < 0)
- return pad->irq;
-
pad->base = reg + i * PMIC_GPIO_ADDRESS_RANGE;
ret = pmic_gpio_populate(state, pad);
@@ -1022,10 +1091,28 @@ static int pmic_gpio_probe(struct platform_device *pdev)
if (IS_ERR(state->ctrl))
return PTR_ERR(state->ctrl);
+ parent_node = of_irq_find_parent(state->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;
+
+ state->fwnode = of_node_to_fwnode(state->dev->of_node);
+ state->domain = irq_domain_create_hierarchy(parent_domain, 0,
+ state->chip.ngpio,
+ state->fwnode,
+ &pmic_gpio_domain_ops,
+ &state->chip);
+ if (!state->domain)
+ return -ENODEV;
+
ret = gpiochip_add_data(&state->chip, state);
if (ret) {
dev_err(state->dev, "can't add gpio chip\n");
- return ret;
+ goto err_chip_add_data;
}
/*
@@ -1051,6 +1138,8 @@ static int pmic_gpio_probe(struct platform_device *pdev)
err_range:
gpiochip_remove(&state->chip);
+err_chip_add_data:
+ irq_domain_remove(state->domain);
return ret;
}
@@ -1059,17 +1148,21 @@ static int pmic_gpio_remove(struct platform_device *pdev)
struct pmic_gpio_state *state = platform_get_drvdata(pdev);
gpiochip_remove(&state->chip);
+ irq_domain_remove(state->domain);
return 0;
}
static const struct of_device_id pmic_gpio_of_match[] = {
- { .compatible = "qcom,pm8916-gpio" }, /* 4 GPIO's */
- { .compatible = "qcom,pm8941-gpio" }, /* 36 GPIO's */
- { .compatible = "qcom,pm8994-gpio" }, /* 22 GPIO's */
- { .compatible = "qcom,pmi8994-gpio" }, /* 10 GPIO's */
- { .compatible = "qcom,pma8084-gpio" }, /* 22 GPIO's */
- { .compatible = "qcom,pms405-gpio" }, /* 12 GPIO's, holes on 1 9 10 */
- { .compatible = "qcom,spmi-gpio" }, /* Generic */
+ { .compatible = "qcom,pm8005-gpio", .data = (void *) 4 },
+ { .compatible = "qcom,pm8916-gpio", .data = (void *) 4 },
+ { .compatible = "qcom,pm8941-gpio", .data = (void *) 36 },
+ { .compatible = "qcom,pm8994-gpio", .data = (void *) 22 },
+ { .compatible = "qcom,pmi8994-gpio", .data = (void *) 10 },
+ { .compatible = "qcom,pm8998-gpio", .data = (void *) 26 },
+ { .compatible = "qcom,pmi8998-gpio", .data = (void *) 14 },
+ { .compatible = "qcom,pma8084-gpio", .data = (void *) 22 },
+ /* pms405 has 12 GPIOs with holes on 1, 9, and 10 */
+ { .compatible = "qcom,pms405-gpio", .data = (void *) 12 },
{ },
};
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;
}