aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch
diff options
context:
space:
mode:
authorJohn Crispin <john@openwrt.org>2014-08-30 09:32:58 +0000
committerJohn Crispin <john@openwrt.org>2014-08-30 09:32:58 +0000
commit3c1f6e358d4f1da4cf79083996544ce909f21b5f (patch)
tree212892dbf4b51bc026d8aca5a12f45cafcef1b84 /target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch
parent926f000b99d31b9d4495c112149377c0da66dbc1 (diff)
downloadupstream-3c1f6e358d4f1da4cf79083996544ce909f21b5f.tar.gz
upstream-3c1f6e358d4f1da4cf79083996544ce909f21b5f.tar.bz2
upstream-3c1f6e358d4f1da4cf79083996544ce909f21b5f.zip
ipq806x: Add support for IPQ806x chip family
Patches are generated using the "format-patch" command from the following location: *https://www.codeaurora.org/cgit/quic/kernel/galak-msm/log/?h=apq_ipq_base *rev=0771849495b4128cac2faf7d49c85c729fc48b20 Patches numbered 76/77/102/103 have already been integrated in 3.14.12, so they're not in this list. All these patches are either integrated are pending integration into kernel.org, therefore these patches should go away once the kernel gets upgraded to 3.16. Support is currently limited to AP148 board but can be extended to other platforms in the future. These changes do not cover ethernet connectivity. Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> SVN-Revision: 42334
Diffstat (limited to 'target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch')
-rw-r--r--target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch494
1 files changed, 494 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch b/target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch
new file mode 100644
index 0000000000..cbd49b0f76
--- /dev/null
+++ b/target/linux/ipq806x/patches/0057-spmi-pmic_arb-add-support-for-interrupt-handling.patch
@@ -0,0 +1,494 @@
+From b5bc51d44485c7ce0ca180a8c5de11a206f686e8 Mon Sep 17 00:00:00 2001
+From: Josh Cartwright <joshc@codeaurora.org>
+Date: Wed, 12 Feb 2014 13:44:25 -0600
+Subject: [PATCH 057/182] spmi: pmic_arb: add support for interrupt handling
+
+The Qualcomm PMIC Arbiter, in addition to being a basic SPMI controller,
+also implements interrupt handling for slave devices. Note, this is
+outside the scope of SPMI, as SPMI leaves interrupt handling completely
+unspecified.
+
+Extend the driver to provide a irq_chip implementation and chained irq
+handling which allows for these interrupts to be used.
+
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Josh Cartwright <joshc@codeaurora.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/spmi/Kconfig | 1 +
+ drivers/spmi/spmi-pmic-arb.c | 377 +++++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 376 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
+index 80b7901..075bd79 100644
+--- a/drivers/spmi/Kconfig
++++ b/drivers/spmi/Kconfig
+@@ -13,6 +13,7 @@ if SPMI
+ config SPMI_MSM_PMIC_ARB
+ tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)"
+ depends on ARM
++ depends on IRQ_DOMAIN
+ depends on ARCH_MSM || COMPILE_TEST
+ default ARCH_MSM
+ help
+diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c
+index 2dd27e8..246e03a 100644
+--- a/drivers/spmi/spmi-pmic-arb.c
++++ b/drivers/spmi/spmi-pmic-arb.c
+@@ -13,6 +13,9 @@
+ #include <linux/err.h>
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
++#include <linux/irqchip/chained_irq.h>
++#include <linux/irqdomain.h>
++#include <linux/irq.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+@@ -103,6 +106,14 @@ enum pmic_arb_cmd_op_code {
+ * @cnfg: address of the PMIC Arbiter configuration registers.
+ * @lock: lock to synchronize accesses.
+ * @channel: which channel to use for accesses.
++ * @irq: PMIC ARB interrupt.
++ * @ee: the current Execution Environment
++ * @min_apid: minimum APID (used for bounding IRQ search)
++ * @max_apid: maximum APID
++ * @mapping_table: in-memory copy of PPID -> APID mapping table.
++ * @domain: irq domain object for PMIC IRQ domain
++ * @spmic: SPMI controller object
++ * @apid_to_ppid: cached mapping from APID to PPID
+ */
+ struct spmi_pmic_arb_dev {
+ void __iomem *base;
+@@ -110,6 +121,14 @@ struct spmi_pmic_arb_dev {
+ void __iomem *cnfg;
+ raw_spinlock_t lock;
+ u8 channel;
++ int irq;
++ u8 ee;
++ u8 min_apid;
++ u8 max_apid;
++ u32 mapping_table[SPMI_MAPPING_TABLE_LEN];
++ struct irq_domain *domain;
++ struct spmi_controller *spmic;
++ u16 apid_to_ppid[256];
+ };
+
+ static inline u32 pmic_arb_base_read(struct spmi_pmic_arb_dev *dev, u32 offset)
+@@ -306,12 +325,316 @@ static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
+ return rc;
+ }
+
++enum qpnpint_regs {
++ QPNPINT_REG_RT_STS = 0x10,
++ QPNPINT_REG_SET_TYPE = 0x11,
++ QPNPINT_REG_POLARITY_HIGH = 0x12,
++ QPNPINT_REG_POLARITY_LOW = 0x13,
++ QPNPINT_REG_LATCHED_CLR = 0x14,
++ QPNPINT_REG_EN_SET = 0x15,
++ QPNPINT_REG_EN_CLR = 0x16,
++ QPNPINT_REG_LATCHED_STS = 0x18,
++};
++
++struct spmi_pmic_arb_qpnpint_type {
++ u8 type; /* 1 -> edge */
++ u8 polarity_high;
++ u8 polarity_low;
++} __packed;
++
++/* Simplified accessor functions for irqchip callbacks */
++static void qpnpint_spmi_write(struct irq_data *d, u8 reg, void *buf,
++ size_t len)
++{
++ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
++ u8 sid = d->hwirq >> 24;
++ u8 per = d->hwirq >> 16;
++
++ if (pmic_arb_write_cmd(pa->spmic, SPMI_CMD_EXT_WRITEL, sid,
++ (per << 8) + reg, buf, len))
++ dev_err_ratelimited(&pa->spmic->dev,
++ "failed irqchip transaction on %x\n",
++ d->irq);
++}
++
++static void qpnpint_spmi_read(struct irq_data *d, u8 reg, void *buf, size_t len)
++{
++ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
++ u8 sid = d->hwirq >> 24;
++ u8 per = d->hwirq >> 16;
++
++ if (pmic_arb_read_cmd(pa->spmic, SPMI_CMD_EXT_READL, sid,
++ (per << 8) + reg, buf, len))
++ dev_err_ratelimited(&pa->spmic->dev,
++ "failed irqchip transaction on %x\n",
++ d->irq);
++}
++
++static void periph_interrupt(struct spmi_pmic_arb_dev *pa, u8 apid)
++{
++ unsigned int irq;
++ u32 status;
++ int id;
++
++ status = readl_relaxed(pa->intr + SPMI_PIC_IRQ_STATUS(apid));
++ while (status) {
++ id = ffs(status) - 1;
++ status &= ~(1 << id);
++ irq = irq_find_mapping(pa->domain,
++ pa->apid_to_ppid[apid] << 16
++ | id << 8
++ | apid);
++ generic_handle_irq(irq);
++ }
++}
++
++static void pmic_arb_chained_irq(unsigned int irq, struct irq_desc *desc)
++{
++ struct spmi_pmic_arb_dev *pa = irq_get_handler_data(irq);
++ struct irq_chip *chip = irq_get_chip(irq);
++ void __iomem *intr = pa->intr;
++ int first = pa->min_apid >> 5;
++ int last = pa->max_apid >> 5;
++ u32 status;
++ int i, id;
++
++ chained_irq_enter(chip, desc);
++
++ for (i = first; i <= last; ++i) {
++ status = readl_relaxed(intr +
++ SPMI_PIC_OWNER_ACC_STATUS(pa->ee, i));
++ while (status) {
++ id = ffs(status) - 1;
++ status &= ~(1 << id);
++ periph_interrupt(pa, id + i * 32);
++ }
++ }
++
++ chained_irq_exit(chip, desc);
++}
++
++static void qpnpint_irq_ack(struct irq_data *d)
++{
++ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
++ u8 irq = d->hwirq >> 8;
++ u8 apid = d->hwirq;
++ unsigned long flags;
++ u8 data;
++
++ raw_spin_lock_irqsave(&pa->lock, flags);
++ writel_relaxed(1 << irq, pa->intr + SPMI_PIC_IRQ_CLEAR(apid));
++ raw_spin_unlock_irqrestore(&pa->lock, flags);
++
++ data = 1 << irq;
++ qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1);
++}
++
++static void qpnpint_irq_mask(struct irq_data *d)
++{
++ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
++ u8 irq = d->hwirq >> 8;
++ u8 apid = d->hwirq;
++ unsigned long flags;
++ u32 status;
++ u8 data;
++
++ raw_spin_lock_irqsave(&pa->lock, flags);
++ status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid));
++ if (status & SPMI_PIC_ACC_ENABLE_BIT) {
++ status = status & ~SPMI_PIC_ACC_ENABLE_BIT;
++ writel_relaxed(status, pa->intr + SPMI_PIC_ACC_ENABLE(apid));
++ }
++ raw_spin_unlock_irqrestore(&pa->lock, flags);
++
++ data = 1 << irq;
++ qpnpint_spmi_write(d, QPNPINT_REG_EN_CLR, &data, 1);
++}
++
++static void qpnpint_irq_unmask(struct irq_data *d)
++{
++ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
++ u8 irq = d->hwirq >> 8;
++ u8 apid = d->hwirq;
++ unsigned long flags;
++ u32 status;
++ u8 data;
++
++ raw_spin_lock_irqsave(&pa->lock, flags);
++ status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid));
++ if (!(status & SPMI_PIC_ACC_ENABLE_BIT)) {
++ writel_relaxed(status | SPMI_PIC_ACC_ENABLE_BIT,
++ pa->intr + SPMI_PIC_ACC_ENABLE(apid));
++ }
++ raw_spin_unlock_irqrestore(&pa->lock, flags);
++
++ data = 1 << irq;
++ qpnpint_spmi_write(d, QPNPINT_REG_EN_SET, &data, 1);
++}
++
++static void qpnpint_irq_enable(struct irq_data *d)
++{
++ u8 irq = d->hwirq >> 8;
++ u8 data;
++
++ qpnpint_irq_unmask(d);
++
++ data = 1 << irq;
++ qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1);
++}
++
++static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type)
++{
++ struct spmi_pmic_arb_qpnpint_type type;
++ u8 irq = d->hwirq >> 8;
++
++ qpnpint_spmi_read(d, QPNPINT_REG_SET_TYPE, &type, sizeof(type));
++
++ if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
++ type.type |= 1 << irq;
++ if (flow_type & IRQF_TRIGGER_RISING)
++ type.polarity_high |= 1 << irq;
++ if (flow_type & IRQF_TRIGGER_FALLING)
++ type.polarity_low |= 1 << irq;
++ } else {
++ if ((flow_type & (IRQF_TRIGGER_HIGH)) &&
++ (flow_type & (IRQF_TRIGGER_LOW)))
++ return -EINVAL;
++
++ type.type &= ~(1 << irq); /* level trig */
++ if (flow_type & IRQF_TRIGGER_HIGH)
++ type.polarity_high |= 1 << irq;
++ else
++ type.polarity_low |= 1 << irq;
++ }
++
++ qpnpint_spmi_write(d, QPNPINT_REG_SET_TYPE, &type, sizeof(type));
++ return 0;
++}
++
++static struct irq_chip pmic_arb_irqchip = {
++ .name = "pmic_arb",
++ .irq_enable = qpnpint_irq_enable,
++ .irq_ack = qpnpint_irq_ack,
++ .irq_mask = qpnpint_irq_mask,
++ .irq_unmask = qpnpint_irq_unmask,
++ .irq_set_type = qpnpint_irq_set_type,
++ .flags = IRQCHIP_MASK_ON_SUSPEND
++ | IRQCHIP_SKIP_SET_WAKE,
++};
++
++struct spmi_pmic_arb_irq_spec {
++ unsigned slave:4;
++ unsigned per:8;
++ unsigned irq:3;
++};
++
++static int search_mapping_table(struct spmi_pmic_arb_dev *pa,
++ struct spmi_pmic_arb_irq_spec *spec,
++ u8 *apid)
++{
++ u16 ppid = spec->slave << 8 | spec->per;
++ u32 *mapping_table = pa->mapping_table;
++ int index = 0, i;
++ u32 data;
++
++ for (i = 0; i < SPMI_MAPPING_TABLE_TREE_DEPTH; ++i) {
++ data = mapping_table[index];
++
++ if (ppid & (1 << SPMI_MAPPING_BIT_INDEX(data))) {
++ if (SPMI_MAPPING_BIT_IS_1_FLAG(data)) {
++ index = SPMI_MAPPING_BIT_IS_1_RESULT(data);
++ } else {
++ *apid = SPMI_MAPPING_BIT_IS_1_RESULT(data);
++ return 0;
++ }
++ } else {
++ if (SPMI_MAPPING_BIT_IS_0_FLAG(data)) {
++ index = SPMI_MAPPING_BIT_IS_0_RESULT(data);
++ } else {
++ *apid = SPMI_MAPPING_BIT_IS_0_RESULT(data);
++ return 0;
++ }
++ }
++ }
++
++ return -ENODEV;
++}
++
++static int qpnpint_irq_domain_dt_translate(struct irq_domain *d,
++ struct device_node *controller,
++ const u32 *intspec,
++ unsigned int intsize,
++ unsigned long *out_hwirq,
++ unsigned int *out_type)
++{
++ struct spmi_pmic_arb_dev *pa = d->host_data;
++ struct spmi_pmic_arb_irq_spec spec;
++ int err;
++ u8 apid;
++
++ dev_dbg(&pa->spmic->dev,
++ "intspec[0] 0x%1x intspec[1] 0x%02x intspec[2] 0x%02x\n",
++ intspec[0], intspec[1], intspec[2]);
++
++ if (d->of_node != controller)
++ return -EINVAL;
++ if (intsize != 4)
++ return -EINVAL;
++ if (intspec[0] > 0xF || intspec[1] > 0xFF || intspec[2] > 0x7)
++ return -EINVAL;
++
++ spec.slave = intspec[0];
++ spec.per = intspec[1];
++ spec.irq = intspec[2];
++
++ err = search_mapping_table(pa, &spec, &apid);
++ if (err)
++ return err;
++
++ pa->apid_to_ppid[apid] = spec.slave << 8 | spec.per;
++
++ /* Keep track of {max,min}_apid for bounding search during interrupt */
++ if (apid > pa->max_apid)
++ pa->max_apid = apid;
++ if (apid < pa->min_apid)
++ pa->min_apid = apid;
++
++ *out_hwirq = spec.slave << 24
++ | spec.per << 16
++ | spec.irq << 8
++ | apid;
++ *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;
++
++ dev_dbg(&pa->spmic->dev, "out_hwirq = %lu\n", *out_hwirq);
++
++ return 0;
++}
++
++static int qpnpint_irq_domain_map(struct irq_domain *d,
++ unsigned int virq,
++ irq_hw_number_t hwirq)
++{
++ struct spmi_pmic_arb_dev *pa = d->host_data;
++
++ dev_dbg(&pa->spmic->dev, "virq = %u, hwirq = %lu\n", virq, hwirq);
++
++ irq_set_chip_and_handler(virq, &pmic_arb_irqchip, handle_level_irq);
++ irq_set_chip_data(virq, d->host_data);
++ irq_set_noprobe(virq);
++ return 0;
++}
++
++static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
++ .map = qpnpint_irq_domain_map,
++ .xlate = qpnpint_irq_domain_dt_translate,
++};
++
+ static int spmi_pmic_arb_probe(struct platform_device *pdev)
+ {
+ struct spmi_pmic_arb_dev *pa;
+ struct spmi_controller *ctrl;
+ struct resource *res;
+- u32 channel;
++ u32 channel, ee;
+ int err, i;
+
+ ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*pa));
+@@ -319,6 +642,7 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
+ return -ENOMEM;
+
+ pa = spmi_controller_get_drvdata(ctrl);
++ pa->spmic = ctrl;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
+ pa->base = devm_ioremap_resource(&ctrl->dev, res);
+@@ -341,6 +665,12 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
+ goto err_put_ctrl;
+ }
+
++ pa->irq = platform_get_irq_byname(pdev, "periph_irq");
++ if (pa->irq < 0) {
++ err = pa->irq;
++ goto err_put_ctrl;
++ }
++
+ err = of_property_read_u32(pdev->dev.of_node, "qcom,channel", &channel);
+ if (err) {
+ dev_err(&pdev->dev, "channel unspecified.\n");
+@@ -355,6 +685,29 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
+
+ pa->channel = channel;
+
++ err = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &ee);
++ if (err) {
++ dev_err(&pdev->dev, "EE unspecified.\n");
++ goto err_put_ctrl;
++ }
++
++ if (ee > 5) {
++ dev_err(&pdev->dev, "invalid EE (%u) specified\n", ee);
++ err = -EINVAL;
++ goto err_put_ctrl;
++ }
++
++ pa->ee = ee;
++
++ for (i = 0; i < ARRAY_SIZE(pa->mapping_table); ++i)
++ pa->mapping_table[i] = readl_relaxed(
++ pa->cnfg + SPMI_MAPPING_TABLE_REG(i));
++
++ /* Initialize max_apid/min_apid to the opposite bounds, during
++ * the irq domain translation, we are sure to update these */
++ pa->max_apid = 0;
++ pa->min_apid = PMIC_ARB_MAX_PERIPHS - 1;
++
+ platform_set_drvdata(pdev, ctrl);
+ raw_spin_lock_init(&pa->lock);
+
+@@ -362,15 +715,31 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
+ ctrl->read_cmd = pmic_arb_read_cmd;
+ ctrl->write_cmd = pmic_arb_write_cmd;
+
++ dev_dbg(&pdev->dev, "adding irq domain\n");
++ pa->domain = irq_domain_add_tree(pdev->dev.of_node,
++ &pmic_arb_irq_domain_ops, pa);
++ if (!pa->domain) {
++ dev_err(&pdev->dev, "unable to create irq_domain\n");
++ err = -ENOMEM;
++ goto err_put_ctrl;
++ }
++
++ irq_set_handler_data(pa->irq, pa);
++ irq_set_chained_handler(pa->irq, pmic_arb_chained_irq);
++
+ err = spmi_controller_add(ctrl);
+ if (err)
+- goto err_put_ctrl;
++ goto err_domain_remove;
+
+ dev_dbg(&ctrl->dev, "PMIC Arb Version 0x%x\n",
+ pmic_arb_base_read(pa, PMIC_ARB_VERSION));
+
+ return 0;
+
++err_domain_remove:
++ irq_set_chained_handler(pa->irq, NULL);
++ irq_set_handler_data(pa->irq, NULL);
++ irq_domain_remove(pa->domain);
+ err_put_ctrl:
+ spmi_controller_put(ctrl);
+ return err;
+@@ -379,7 +748,11 @@ err_put_ctrl:
+ static int spmi_pmic_arb_remove(struct platform_device *pdev)
+ {
+ struct spmi_controller *ctrl = platform_get_drvdata(pdev);
++ struct spmi_pmic_arb_dev *pa = spmi_controller_get_drvdata(ctrl);
+ spmi_controller_remove(ctrl);
++ irq_set_chained_handler(pa->irq, NULL);
++ irq_set_handler_data(pa->irq, NULL);
++ irq_domain_remove(pa->domain);
+ spmi_controller_put(ctrl);
+ return 0;
+ }
+--
+1.7.10.4
+