*/
#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
/**
* 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,
*/
struct pmic_gpio_pad {
u16 base;
- int irq;
bool is_enabled;
bool out_value;
bool have_buffer;
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[] = {
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)
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] >= 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;
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);
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;
}
/*
err_range:
gpiochip_remove(&state->chip);
+err_chip_add_data:
+ irq_domain_remove(state->domain);
return ret;
}
struct pmic_gpio_state *state = platform_get_drvdata(pdev);
gpiochip_remove(&state->chip);
+ irq_domain_remove(state->domain);
return 0;
}