pinctrl: exynos: Use one IRQ domain per pin bank
Instead of registering one IRQ domain for all pin banks of a pin controller, this patch implements registration of per-bank domains. At a cost of a little memory overhead (~2.5KiB for all GPIO interrupts of Exynos4x12) it simplifies driver code and device tree sources, because GPIO interrupts can be now specified per banks. Example: device { /* ... */ interrupt-parent = <&gpa1>; interrupts = <3 0>; /* ... */ }; Signed-off-by: Tomasz Figa <t.figa@samsung.com> Reviewed-by: Kyungmin Park <kyungmin.park@samsung.com> Acked-by: Thomas Abraham <thomas.abraham@linaro.org> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:

committed by
Linus Walleij

parent
1b6056d6db
commit
595be7268a
@@ -46,16 +46,12 @@
|
|||||||
compatible = "samsung,pinctrl-exynos4210";
|
compatible = "samsung,pinctrl-exynos4210";
|
||||||
reg = <0x11400000 0x1000>;
|
reg = <0x11400000 0x1000>;
|
||||||
interrupts = <0 47 0>;
|
interrupts = <0 47 0>;
|
||||||
interrupt-controller;
|
|
||||||
#interrupt-cells = <2>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pinctrl_1: pinctrl@11000000 {
|
pinctrl_1: pinctrl@11000000 {
|
||||||
compatible = "samsung,pinctrl-exynos4210";
|
compatible = "samsung,pinctrl-exynos4210";
|
||||||
reg = <0x11000000 0x1000>;
|
reg = <0x11000000 0x1000>;
|
||||||
interrupts = <0 46 0>;
|
interrupts = <0 46 0>;
|
||||||
interrupt-controller;
|
|
||||||
#interrupt-cells = <2>;
|
|
||||||
|
|
||||||
wakup_eint: wakeup-interrupt-controller {
|
wakup_eint: wakeup-interrupt-controller {
|
||||||
compatible = "samsung,exynos4210-wakeup-eint";
|
compatible = "samsung,exynos4210-wakeup-eint";
|
||||||
|
@@ -40,46 +40,46 @@ static const struct of_device_id exynos_wkup_irq_ids[] = {
|
|||||||
|
|
||||||
static void exynos_gpio_irq_unmask(struct irq_data *irqd)
|
static void exynos_gpio_irq_unmask(struct irq_data *irqd)
|
||||||
{
|
{
|
||||||
struct samsung_pinctrl_drv_data *d = irqd->domain->host_data;
|
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
|
||||||
struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd);
|
struct samsung_pinctrl_drv_data *d = bank->drvdata;
|
||||||
unsigned long reg_mask = d->ctrl->geint_mask + edata->eint_offset;
|
unsigned long reg_mask = d->ctrl->geint_mask + bank->eint_offset;
|
||||||
unsigned long mask;
|
unsigned long mask;
|
||||||
|
|
||||||
mask = readl(d->virt_base + reg_mask);
|
mask = readl(d->virt_base + reg_mask);
|
||||||
mask &= ~(1 << edata->pin);
|
mask &= ~(1 << irqd->hwirq);
|
||||||
writel(mask, d->virt_base + reg_mask);
|
writel(mask, d->virt_base + reg_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void exynos_gpio_irq_mask(struct irq_data *irqd)
|
static void exynos_gpio_irq_mask(struct irq_data *irqd)
|
||||||
{
|
{
|
||||||
struct samsung_pinctrl_drv_data *d = irqd->domain->host_data;
|
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
|
||||||
struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd);
|
struct samsung_pinctrl_drv_data *d = bank->drvdata;
|
||||||
unsigned long reg_mask = d->ctrl->geint_mask + edata->eint_offset;
|
unsigned long reg_mask = d->ctrl->geint_mask + bank->eint_offset;
|
||||||
unsigned long mask;
|
unsigned long mask;
|
||||||
|
|
||||||
mask = readl(d->virt_base + reg_mask);
|
mask = readl(d->virt_base + reg_mask);
|
||||||
mask |= 1 << edata->pin;
|
mask |= 1 << irqd->hwirq;
|
||||||
writel(mask, d->virt_base + reg_mask);
|
writel(mask, d->virt_base + reg_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void exynos_gpio_irq_ack(struct irq_data *irqd)
|
static void exynos_gpio_irq_ack(struct irq_data *irqd)
|
||||||
{
|
{
|
||||||
struct samsung_pinctrl_drv_data *d = irqd->domain->host_data;
|
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
|
||||||
struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd);
|
struct samsung_pinctrl_drv_data *d = bank->drvdata;
|
||||||
unsigned long reg_pend = d->ctrl->geint_pend + edata->eint_offset;
|
unsigned long reg_pend = d->ctrl->geint_pend + bank->eint_offset;
|
||||||
|
|
||||||
writel(1 << edata->pin, d->virt_base + reg_pend);
|
writel(1 << irqd->hwirq, d->virt_base + reg_pend);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int exynos_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
|
static int exynos_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
|
||||||
{
|
{
|
||||||
struct samsung_pinctrl_drv_data *d = irqd->domain->host_data;
|
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
|
||||||
|
struct samsung_pinctrl_drv_data *d = bank->drvdata;
|
||||||
struct samsung_pin_ctrl *ctrl = d->ctrl;
|
struct samsung_pin_ctrl *ctrl = d->ctrl;
|
||||||
struct exynos_geint_data *edata = irq_data_get_irq_handler_data(irqd);
|
unsigned int pin = irqd->hwirq;
|
||||||
struct samsung_pin_bank *bank = edata->bank;
|
unsigned int shift = EXYNOS_EINT_CON_LEN * pin;
|
||||||
unsigned int shift = EXYNOS_EINT_CON_LEN * edata->pin;
|
|
||||||
unsigned int con, trig_type;
|
unsigned int con, trig_type;
|
||||||
unsigned long reg_con = ctrl->geint_con + edata->eint_offset;
|
unsigned long reg_con = ctrl->geint_con + bank->eint_offset;
|
||||||
unsigned int mask;
|
unsigned int mask;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -114,7 +114,7 @@ static int exynos_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
|
|||||||
writel(con, d->virt_base + reg_con);
|
writel(con, d->virt_base + reg_con);
|
||||||
|
|
||||||
reg_con = bank->pctl_offset;
|
reg_con = bank->pctl_offset;
|
||||||
shift = edata->pin * bank->func_width;
|
shift = pin * bank->func_width;
|
||||||
mask = (1 << bank->func_width) - 1;
|
mask = (1 << bank->func_width) - 1;
|
||||||
|
|
||||||
con = readl(d->virt_base + reg_con);
|
con = readl(d->virt_base + reg_con);
|
||||||
@@ -136,81 +136,23 @@ static struct irq_chip exynos_gpio_irq_chip = {
|
|||||||
.irq_set_type = exynos_gpio_irq_set_type,
|
.irq_set_type = exynos_gpio_irq_set_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* given a controller-local external gpio interrupt number, prepare the handler
|
|
||||||
* data for it.
|
|
||||||
*/
|
|
||||||
static struct exynos_geint_data *exynos_get_eint_data(irq_hw_number_t hw,
|
|
||||||
struct samsung_pinctrl_drv_data *d)
|
|
||||||
{
|
|
||||||
struct samsung_pin_bank *bank = d->ctrl->pin_banks;
|
|
||||||
struct exynos_geint_data *eint_data;
|
|
||||||
unsigned int nr_banks = d->ctrl->nr_banks, idx;
|
|
||||||
unsigned int irq_base = 0;
|
|
||||||
|
|
||||||
if (hw >= d->ctrl->nr_gint) {
|
|
||||||
dev_err(d->dev, "unsupported ext-gpio interrupt\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (idx = 0; idx < nr_banks; idx++, bank++) {
|
|
||||||
if (bank->eint_type != EINT_TYPE_GPIO)
|
|
||||||
continue;
|
|
||||||
if ((hw >= irq_base) && (hw < (irq_base + bank->nr_pins)))
|
|
||||||
break;
|
|
||||||
irq_base += bank->nr_pins;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx == nr_banks) {
|
|
||||||
dev_err(d->dev, "pin bank not found for ext-gpio interrupt\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
eint_data = devm_kzalloc(d->dev, sizeof(*eint_data), GFP_KERNEL);
|
|
||||||
if (!eint_data) {
|
|
||||||
dev_err(d->dev, "no memory for eint-gpio data\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
eint_data->bank = bank;
|
|
||||||
eint_data->pin = hw - irq_base;
|
|
||||||
eint_data->eint_offset = bank->eint_offset;
|
|
||||||
return eint_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int exynos_gpio_irq_map(struct irq_domain *h, unsigned int virq,
|
static int exynos_gpio_irq_map(struct irq_domain *h, unsigned int virq,
|
||||||
irq_hw_number_t hw)
|
irq_hw_number_t hw)
|
||||||
{
|
{
|
||||||
struct samsung_pinctrl_drv_data *d = h->host_data;
|
struct samsung_pin_bank *b = h->host_data;
|
||||||
struct exynos_geint_data *eint_data;
|
|
||||||
|
|
||||||
eint_data = exynos_get_eint_data(hw, d);
|
irq_set_chip_data(virq, b);
|
||||||
if (!eint_data)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
irq_set_handler_data(virq, eint_data);
|
|
||||||
irq_set_chip_data(virq, h->host_data);
|
|
||||||
irq_set_chip_and_handler(virq, &exynos_gpio_irq_chip,
|
irq_set_chip_and_handler(virq, &exynos_gpio_irq_chip,
|
||||||
handle_level_irq);
|
handle_level_irq);
|
||||||
set_irq_flags(virq, IRQF_VALID);
|
set_irq_flags(virq, IRQF_VALID);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void exynos_gpio_irq_unmap(struct irq_domain *h, unsigned int virq)
|
|
||||||
{
|
|
||||||
struct samsung_pinctrl_drv_data *d = h->host_data;
|
|
||||||
struct exynos_geint_data *eint_data;
|
|
||||||
|
|
||||||
eint_data = irq_get_handler_data(virq);
|
|
||||||
devm_kfree(d->dev, eint_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* irq domain callbacks for external gpio interrupt controller.
|
* irq domain callbacks for external gpio interrupt controller.
|
||||||
*/
|
*/
|
||||||
static const struct irq_domain_ops exynos_gpio_irqd_ops = {
|
static const struct irq_domain_ops exynos_gpio_irqd_ops = {
|
||||||
.map = exynos_gpio_irq_map,
|
.map = exynos_gpio_irq_map,
|
||||||
.unmap = exynos_gpio_irq_unmap,
|
|
||||||
.xlate = irq_domain_xlate_twocell,
|
.xlate = irq_domain_xlate_twocell,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,7 +171,7 @@ static irqreturn_t exynos_eint_gpio_irq(int irq, void *data)
|
|||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
bank += (group - 1);
|
bank += (group - 1);
|
||||||
|
|
||||||
virq = irq_linear_revmap(d->gpio_irqd, bank->irq_base + pin);
|
virq = irq_linear_revmap(bank->irq_domain, pin);
|
||||||
if (!virq)
|
if (!virq)
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
generic_handle_irq(virq);
|
generic_handle_irq(virq);
|
||||||
@@ -242,8 +184,10 @@ static irqreturn_t exynos_eint_gpio_irq(int irq, void *data)
|
|||||||
*/
|
*/
|
||||||
static int exynos_eint_gpio_init(struct samsung_pinctrl_drv_data *d)
|
static int exynos_eint_gpio_init(struct samsung_pinctrl_drv_data *d)
|
||||||
{
|
{
|
||||||
|
struct samsung_pin_bank *bank;
|
||||||
struct device *dev = d->dev;
|
struct device *dev = d->dev;
|
||||||
unsigned int ret;
|
unsigned int ret;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
if (!d->irq) {
|
if (!d->irq) {
|
||||||
dev_err(dev, "irq number not available\n");
|
dev_err(dev, "irq number not available\n");
|
||||||
@@ -257,11 +201,16 @@ static int exynos_eint_gpio_init(struct samsung_pinctrl_drv_data *d)
|
|||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->gpio_irqd = irq_domain_add_linear(dev->of_node, d->ctrl->nr_gint,
|
bank = d->ctrl->pin_banks;
|
||||||
&exynos_gpio_irqd_ops, d);
|
for (i = 0; i < d->ctrl->nr_banks; ++i, ++bank) {
|
||||||
if (!d->gpio_irqd) {
|
if (bank->eint_type != EINT_TYPE_GPIO)
|
||||||
dev_err(dev, "gpio irq domain allocation failed\n");
|
continue;
|
||||||
return -ENXIO;
|
bank->irq_domain = irq_domain_add_linear(bank->of_node,
|
||||||
|
bank->nr_pins, &exynos_gpio_irqd_ops, bank);
|
||||||
|
if (!bank->irq_domain) {
|
||||||
|
dev_err(dev, "gpio irq domain add failed\n");
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -73,18 +73,6 @@
|
|||||||
.name = id \
|
.name = id \
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* struct exynos_geint_data: gpio eint specific data for irq_chip callbacks.
|
|
||||||
* @bank: pin bank from which this gpio interrupt originates.
|
|
||||||
* @pin: pin number within the bank.
|
|
||||||
* @eint_offset: offset to be added to the con/pend/mask register bank base.
|
|
||||||
*/
|
|
||||||
struct exynos_geint_data {
|
|
||||||
struct samsung_pin_bank *bank;
|
|
||||||
u32 pin;
|
|
||||||
u32 eint_offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct exynos_weint_data: irq specific data for all the wakeup interrupts
|
* struct exynos_weint_data: irq specific data for all the wakeup interrupts
|
||||||
* generated by the external wakeup interrupt controller.
|
* generated by the external wakeup interrupt controller.
|
||||||
|
@@ -813,10 +813,6 @@ static struct samsung_pin_ctrl *samsung_pinctrl_get_soc_data(
|
|||||||
bank->drvdata = d;
|
bank->drvdata = d;
|
||||||
bank->pin_base = ctrl->nr_pins;
|
bank->pin_base = ctrl->nr_pins;
|
||||||
ctrl->nr_pins += bank->nr_pins;
|
ctrl->nr_pins += bank->nr_pins;
|
||||||
if (bank->eint_type == EINT_TYPE_GPIO) {
|
|
||||||
bank->irq_base = ctrl->nr_gint;
|
|
||||||
ctrl->nr_gint += bank->nr_pins;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for_each_child_of_node(node, np) {
|
for_each_child_of_node(node, np) {
|
||||||
|
@@ -109,10 +109,10 @@ struct samsung_pinctrl_drv_data;
|
|||||||
* @conpdn_width: width of the sleep mode function selector bin field.
|
* @conpdn_width: width of the sleep mode function selector bin field.
|
||||||
* @pudpdn_width: width of the sleep mode pull up/down selector bit field.
|
* @pudpdn_width: width of the sleep mode pull up/down selector bit field.
|
||||||
* @eint_type: type of the external interrupt supported by the bank.
|
* @eint_type: type of the external interrupt supported by the bank.
|
||||||
* @irq_base: starting controller local irq number of the bank.
|
|
||||||
* @name: name to be prefixed for each pin in this pin bank.
|
* @name: name to be prefixed for each pin in this pin bank.
|
||||||
* @of_node: OF node of the bank.
|
* @of_node: OF node of the bank.
|
||||||
* @drvdata: link to controller driver data
|
* @drvdata: link to controller driver data
|
||||||
|
* @irq_domain: IRQ domain of the bank.
|
||||||
*/
|
*/
|
||||||
struct samsung_pin_bank {
|
struct samsung_pin_bank {
|
||||||
u32 pctl_offset;
|
u32 pctl_offset;
|
||||||
@@ -125,10 +125,10 @@ struct samsung_pin_bank {
|
|||||||
u8 pudpdn_width;
|
u8 pudpdn_width;
|
||||||
enum eint_type eint_type;
|
enum eint_type eint_type;
|
||||||
u32 eint_offset;
|
u32 eint_offset;
|
||||||
u32 irq_base;
|
|
||||||
char *name;
|
char *name;
|
||||||
struct device_node *of_node;
|
struct device_node *of_node;
|
||||||
struct samsung_pinctrl_drv_data *drvdata;
|
struct samsung_pinctrl_drv_data *drvdata;
|
||||||
|
struct irq_domain *irq_domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +137,6 @@ struct samsung_pin_bank {
|
|||||||
* @nr_banks: number of pin banks.
|
* @nr_banks: number of pin banks.
|
||||||
* @base: starting system wide pin number.
|
* @base: starting system wide pin number.
|
||||||
* @nr_pins: number of pins supported by the controller.
|
* @nr_pins: number of pins supported by the controller.
|
||||||
* @nr_gint: number of external gpio interrupts supported.
|
|
||||||
* @nr_wint: number of external wakeup interrupts supported.
|
* @nr_wint: number of external wakeup interrupts supported.
|
||||||
* @geint_con: offset of the ext-gpio controller registers.
|
* @geint_con: offset of the ext-gpio controller registers.
|
||||||
* @geint_mask: offset of the ext-gpio interrupt mask registers.
|
* @geint_mask: offset of the ext-gpio interrupt mask registers.
|
||||||
@@ -158,7 +157,6 @@ struct samsung_pin_ctrl {
|
|||||||
|
|
||||||
u32 base;
|
u32 base;
|
||||||
u32 nr_pins;
|
u32 nr_pins;
|
||||||
u32 nr_gint;
|
|
||||||
u32 nr_wint;
|
u32 nr_wint;
|
||||||
|
|
||||||
u32 geint_con;
|
u32 geint_con;
|
||||||
@@ -205,7 +203,6 @@ struct samsung_pinctrl_drv_data {
|
|||||||
const struct samsung_pmx_func *pmx_functions;
|
const struct samsung_pmx_func *pmx_functions;
|
||||||
unsigned int nr_functions;
|
unsigned int nr_functions;
|
||||||
|
|
||||||
struct irq_domain *gpio_irqd;
|
|
||||||
struct irq_domain *wkup_irqd;
|
struct irq_domain *wkup_irqd;
|
||||||
|
|
||||||
struct gpio_chip *gc;
|
struct gpio_chip *gc;
|
||||||
|
Reference in New Issue
Block a user