diff options
| author | Josh Cartwright <joshc@codeaurora.org> | 2014-02-12 14:44:25 -0500 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-02-15 14:55:28 -0500 |
| commit | 67b563f1f258739eefa79d9ce6c3177f670481df (patch) | |
| tree | e1e5f797f693867651b54b2174c3ddec1178b974 /drivers/spmi | |
| parent | 39ae93e3a31d0c9ca99e35b754a9f3c6f1db2bee (diff) | |
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>
Diffstat (limited to 'drivers/spmi')
| -rw-r--r-- | drivers/spmi/Kconfig | 1 | ||||
| -rw-r--r-- | 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 80b79013fd1e..075bd79e1ac4 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig | |||
| @@ -13,6 +13,7 @@ if SPMI | |||
| 13 | config SPMI_MSM_PMIC_ARB | 13 | config SPMI_MSM_PMIC_ARB |
| 14 | tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" | 14 | tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" |
| 15 | depends on ARM | 15 | depends on ARM |
| 16 | depends on IRQ_DOMAIN | ||
| 16 | depends on ARCH_MSM || COMPILE_TEST | 17 | depends on ARCH_MSM || COMPILE_TEST |
| 17 | default ARCH_MSM | 18 | default ARCH_MSM |
| 18 | help | 19 | help |
diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 2dd27e8d140d..246e03a18c94 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c | |||
| @@ -13,6 +13,9 @@ | |||
| 13 | #include <linux/err.h> | 13 | #include <linux/err.h> |
| 14 | #include <linux/interrupt.h> | 14 | #include <linux/interrupt.h> |
| 15 | #include <linux/io.h> | 15 | #include <linux/io.h> |
| 16 | #include <linux/irqchip/chained_irq.h> | ||
| 17 | #include <linux/irqdomain.h> | ||
| 18 | #include <linux/irq.h> | ||
| 16 | #include <linux/kernel.h> | 19 | #include <linux/kernel.h> |
| 17 | #include <linux/module.h> | 20 | #include <linux/module.h> |
| 18 | #include <linux/of.h> | 21 | #include <linux/of.h> |
| @@ -103,6 +106,14 @@ enum pmic_arb_cmd_op_code { | |||
| 103 | * @cnfg: address of the PMIC Arbiter configuration registers. | 106 | * @cnfg: address of the PMIC Arbiter configuration registers. |
| 104 | * @lock: lock to synchronize accesses. | 107 | * @lock: lock to synchronize accesses. |
| 105 | * @channel: which channel to use for accesses. | 108 | * @channel: which channel to use for accesses. |
| 109 | * @irq: PMIC ARB interrupt. | ||
| 110 | * @ee: the current Execution Environment | ||
| 111 | * @min_apid: minimum APID (used for bounding IRQ search) | ||
| 112 | * @max_apid: maximum APID | ||
| 113 | * @mapping_table: in-memory copy of PPID -> APID mapping table. | ||
| 114 | * @domain: irq domain object for PMIC IRQ domain | ||
| 115 | * @spmic: SPMI controller object | ||
| 116 | * @apid_to_ppid: cached mapping from APID to PPID | ||
| 106 | */ | 117 | */ |
| 107 | struct spmi_pmic_arb_dev { | 118 | struct spmi_pmic_arb_dev { |
| 108 | void __iomem *base; | 119 | void __iomem *base; |
| @@ -110,6 +121,14 @@ struct spmi_pmic_arb_dev { | |||
| 110 | void __iomem *cnfg; | 121 | void __iomem *cnfg; |
| 111 | raw_spinlock_t lock; | 122 | raw_spinlock_t lock; |
| 112 | u8 channel; | 123 | u8 channel; |
| 124 | int irq; | ||
| 125 | u8 ee; | ||
| 126 | u8 min_apid; | ||
| 127 | u8 max_apid; | ||
| 128 | u32 mapping_table[SPMI_MAPPING_TABLE_LEN]; | ||
| 129 | struct irq_domain *domain; | ||
| 130 | struct spmi_controller *spmic; | ||
| 131 | u16 apid_to_ppid[256]; | ||
| 113 | }; | 132 | }; |
| 114 | 133 | ||
| 115 | static inline u32 pmic_arb_base_read(struct spmi_pmic_arb_dev *dev, u32 offset) | 134 | 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, | |||
| 306 | return rc; | 325 | return rc; |
| 307 | } | 326 | } |
| 308 | 327 | ||
| 328 | enum qpnpint_regs { | ||
| 329 | QPNPINT_REG_RT_STS = 0x10, | ||
| 330 | QPNPINT_REG_SET_TYPE = 0x11, | ||
| 331 | QPNPINT_REG_POLARITY_HIGH = 0x12, | ||
| 332 | QPNPINT_REG_POLARITY_LOW = 0x13, | ||
| 333 | QPNPINT_REG_LATCHED_CLR = 0x14, | ||
| 334 | QPNPINT_REG_EN_SET = 0x15, | ||
| 335 | QPNPINT_REG_EN_CLR = 0x16, | ||
| 336 | QPNPINT_REG_LATCHED_STS = 0x18, | ||
| 337 | }; | ||
| 338 | |||
| 339 | struct spmi_pmic_arb_qpnpint_type { | ||
| 340 | u8 type; /* 1 -> edge */ | ||
| 341 | u8 polarity_high; | ||
| 342 | u8 polarity_low; | ||
| 343 | } __packed; | ||
| 344 | |||
| 345 | /* Simplified accessor functions for irqchip callbacks */ | ||
| 346 | static void qpnpint_spmi_write(struct irq_data *d, u8 reg, void *buf, | ||
| 347 | size_t len) | ||
| 348 | { | ||
| 349 | struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d); | ||
| 350 | u8 sid = d->hwirq >> 24; | ||
| 351 | u8 per = d->hwirq >> 16; | ||
| 352 | |||
| 353 | if (pmic_arb_write_cmd(pa->spmic, SPMI_CMD_EXT_WRITEL, sid, | ||
| 354 | (per << 8) + reg, buf, len)) | ||
| 355 | dev_err_ratelimited(&pa->spmic->dev, | ||
| 356 | "failed irqchip transaction on %x\n", | ||
| 357 | d->irq); | ||
| 358 | } | ||
| 359 | |||
| 360 | static void qpnpint_spmi_read(struct irq_data *d, u8 reg, void *buf, size_t len) | ||
| 361 | { | ||
| 362 | struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d); | ||
| 363 | u8 sid = d->hwirq >> 24; | ||
| 364 | u8 per = d->hwirq >> 16; | ||
| 365 | |||
| 366 | if (pmic_arb_read_cmd(pa->spmic, SPMI_CMD_EXT_READL, sid, | ||
| 367 | (per << 8) + reg, buf, len)) | ||
| 368 | dev_err_ratelimited(&pa->spmic->dev, | ||
| 369 | "failed irqchip transaction on %x\n", | ||
| 370 | d->irq); | ||
| 371 | } | ||
| 372 | |||
| 373 | static void periph_interrupt(struct spmi_pmic_arb_dev *pa, u8 apid) | ||
| 374 | { | ||
| 375 | unsigned int irq; | ||
| 376 | u32 status; | ||
| 377 | int id; | ||
| 378 | |||
| 379 | status = readl_relaxed(pa->intr + SPMI_PIC_IRQ_STATUS(apid)); | ||
| 380 | while (status) { | ||
| 381 | id = ffs(status) - 1; | ||
| 382 | status &= ~(1 << id); | ||
| 383 | irq = irq_find_mapping(pa->domain, | ||
| 384 | pa->apid_to_ppid[apid] << 16 | ||
| 385 | | id << 8 | ||
| 386 | | apid); | ||
| 387 | generic_handle_irq(irq); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | static void pmic_arb_chained_irq(unsigned int irq, struct irq_desc *desc) | ||
| 392 | { | ||
| 393 | struct spmi_pmic_arb_dev *pa = irq_get_handler_data(irq); | ||
| 394 | struct irq_chip *chip = irq_get_chip(irq); | ||
| 395 | void __iomem *intr = pa->intr; | ||
| 396 | int first = pa->min_apid >> 5; | ||
| 397 | int last = pa->max_apid >> 5; | ||
| 398 | u32 status; | ||
| 399 | int i, id; | ||
| 400 | |||
| 401 | chained_irq_enter(chip, desc); | ||
| 402 | |||
| 403 | for (i = first; i <= last; ++i) { | ||
| 404 | status = readl_relaxed(intr + | ||
| 405 | SPMI_PIC_OWNER_ACC_STATUS(pa->ee, i)); | ||
| 406 | while (status) { | ||
| 407 | id = ffs(status) - 1; | ||
| 408 | status &= ~(1 << id); | ||
| 409 | periph_interrupt(pa, id + i * 32); | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | chained_irq_exit(chip, desc); | ||
| 414 | } | ||
| 415 | |||
| 416 | static void qpnpint_irq_ack(struct irq_data *d) | ||
| 417 | { | ||
| 418 | struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d); | ||
| 419 | u8 irq = d->hwirq >> 8; | ||
| 420 | u8 apid = d->hwirq; | ||
| 421 | unsigned long flags; | ||
| 422 | u8 data; | ||
| 423 | |||
| 424 | raw_spin_lock_irqsave(&pa->lock, flags); | ||
| 425 | writel_relaxed(1 << irq, pa->intr + SPMI_PIC_IRQ_CLEAR(apid)); | ||
| 426 | raw_spin_unlock_irqrestore(&pa->lock, flags); | ||
| 427 | |||
| 428 | data = 1 << irq; | ||
| 429 | qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1); | ||
| 430 | } | ||
| 431 | |||
| 432 | static void qpnpint_irq_mask(struct irq_data *d) | ||
| 433 | { | ||
| 434 | struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d); | ||
| 435 | u8 irq = d->hwirq >> 8; | ||
| 436 | u8 apid = d->hwirq; | ||
| 437 | unsigned long flags; | ||
| 438 | u32 status; | ||
| 439 | u8 data; | ||
| 440 | |||
| 441 | raw_spin_lock_irqsave(&pa->lock, flags); | ||
| 442 | status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid)); | ||
| 443 | if (status & SPMI_PIC_ACC_ENABLE_BIT) { | ||
| 444 | status = status & ~SPMI_PIC_ACC_ENABLE_BIT; | ||
| 445 | writel_relaxed(status, pa->intr + SPMI_PIC_ACC_ENABLE(apid)); | ||
| 446 | } | ||
| 447 | raw_spin_unlock_irqrestore(&pa->lock, flags); | ||
| 448 | |||
| 449 | data = 1 << irq; | ||
