From 1582c6bcd9e69349bfe96b017f7d03ba61ddf244 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 13 Feb 2019 13:21:55 -0700 Subject: [PATCH 1/2] irqchip: qcom-pdc: Additionally set type in SPI config registers GPIOs that can be configured as wakeup are routed to the PDC wakeup interrupt controller and from there to the GIC interrupt controller. On some QCOM SoCs, the interface to the GIC for wakeup capable GPIOs have additional hardware registers that need to be configured as well to match the trigger type of the GPIO. This register interfaces the PDC to the GIC and therefore updated from the PDC driver. Typically, the firmware intializes the interface registers for the wakeup capable GPIOs with commonly used GPIO trigger type, but it is possible that a platform may want to use the GPIO differently. So, in addition to configuring the PDC, configure the interface registers as well. Change-Id: Icab7d9eeb77b5dab9d6a5a8280ce568da40b6ca6 Signed-off-by: Lina Iyer --- drivers/irqchip/qcom-pdc.c | 93 ++++++++++++++++++++++++++++++++++++ include/linux/soc/qcom/irq.h | 15 ++++++ 2 files changed, 108 insertions(+) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 750bc455bd3c..64ff060893ff 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -19,6 +19,8 @@ #include #include +#include + #define PDC_MAX_IRQS 168 #define PDC_MAX_GPIO_IRQS 256 @@ -36,10 +38,18 @@ struct pdc_pin_region { u32 cnt; }; +struct spi_cfg_regs { + u64 start; + void __iomem *base; + resource_size_t size; + bool scm_io; +}; + static DEFINE_RAW_SPINLOCK(pdc_lock); static void __iomem *pdc_base; static struct pdc_pin_region *pdc_region; static int pdc_region_cnt; +static struct spi_cfg_regs *spi_cfg; static void pdc_reg_write(int reg, u32 i, u32 val) { @@ -121,6 +131,57 @@ static void qcom_pdc_gic_unmask(struct irq_data *d) irq_chip_unmask_parent(d); } +static u32 __spi_pin_read(unsigned int pin) +{ + void __iomem *cfg_reg = spi_cfg->base + pin * 4; + u64 scm_cfg_reg = spi_cfg->start + pin * 4; + + if (spi_cfg->scm_io) { + unsigned int val; + + qcom_scm_io_readl(scm_cfg_reg, &val); + return val; + } else { + return readl_relaxed(cfg_reg); + } +} + +static void __spi_pin_write(unsigned int pin, unsigned int val) +{ + void __iomem *cfg_reg = spi_cfg->base + pin * 4; + u64 scm_cfg_reg = spi_cfg->start + pin * 4; + + if (spi_cfg->scm_io) + qcom_scm_io_writel(scm_cfg_reg, val); + else + writel_relaxed(val, cfg_reg); +} + +static int spi_configure_type(irq_hw_number_t hwirq, unsigned int type) +{ + int spi = hwirq - 32; + u32 pin = spi / 32; + u32 mask = BIT(spi % 32); + u32 val; + unsigned long flags; + + if (!spi_cfg) + return 0; + + if (pin * 4 > spi_cfg->size) + return -EFAULT; + + raw_spin_lock_irqsave(&pdc_lock, flags); + val = __spi_pin_read(pin); + val &= ~mask; + if (type & IRQ_TYPE_LEVEL_MASK) + val |= mask; + __spi_pin_write(pin, val); + raw_spin_unlock_irqrestore(&pdc_lock, flags); + + return 0; +} + /* * GIC does not handle falling edge or active low. To allow falling edge and * active low interrupts to be handled at GIC, PDC has an inverter that inverts @@ -158,7 +219,9 @@ enum pdc_irq_config_bits { static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) { int pin_out = d->hwirq; + int parent_hwirq = d->parent_data->hwirq; enum pdc_irq_config_bits pdc_type; + int ret; if (pin_out == GPIO_NO_WAKE_IRQ) return 0; @@ -189,6 +252,13 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) pdc_reg_write(IRQ_i_CFG, pin_out, pdc_type); + /* Additionally, configure (only) the GPIO in the f/w */ + if (irq_domain_qcom_handle_wakeup(d->domain)) { + ret = spi_configure_type(parent_hwirq, type); + if (ret) + return ret; + } + return irq_chip_set_type_parent(d, type); } @@ -377,6 +447,7 @@ static int pdc_setup_pin_mapping(struct device_node *np) static int qcom_pdc_init(struct device_node *node, struct device_node *parent) { struct irq_domain *parent_domain, *pdc_domain, *pdc_gpio_domain; + struct resource res; int ret; pdc_base = of_iomap(node, 0); @@ -407,6 +478,27 @@ static int qcom_pdc_init(struct device_node *node, struct device_node *parent) goto fail; } + ret = of_address_to_resource(node, 1, &res); + if (!ret) { + spi_cfg = kcalloc(1, sizeof(*spi_cfg), GFP_KERNEL); + if (!spi_cfg) { + ret = -ENOMEM; + goto remove; + } + spi_cfg->scm_io = of_find_property(node, + "qcom,scm-spi-cfg", NULL); + spi_cfg->size = resource_size(&res); + if (spi_cfg->scm_io) { + spi_cfg->start = res.start; + } else { + spi_cfg->base = ioremap(res.start, spi_cfg->size); + if (!spi_cfg->base) { + ret = -ENOMEM; + goto remove; + } + } + } + pdc_gpio_domain = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, PDC_MAX_GPIO_IRQS, @@ -424,6 +516,7 @@ static int qcom_pdc_init(struct device_node *node, struct device_node *parent) remove: irq_domain_remove(pdc_domain); + kfree(spi_cfg); fail: kfree(pdc_region); iounmap(pdc_base); diff --git a/include/linux/soc/qcom/irq.h b/include/linux/soc/qcom/irq.h index 85ac4b6cf3fb..59a459e0ee12 100644 --- a/include/linux/soc/qcom/irq.h +++ b/include/linux/soc/qcom/irq.h @@ -3,6 +3,8 @@ #ifndef __QCOM_IRQ_H #define __QCOM_IRQ_H +struct irq_domain; + #define GPIO_NO_WAKE_IRQ ~0U /** @@ -16,4 +18,17 @@ #define IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP (IRQ_DOMAIN_FLAG_NONCORE << 0) #define IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP (IRQ_DOMAIN_FLAG_NONCORE << 1) +/** + * irq_domain_qcom_handle_wakeup: Return if the domain handles interrupt + * configuration + * @d: irq domain + * + * This QCOM specific irq domain call returns if the interrupt controller + * requires the interrupt be masked at the child interrupt controller. + */ +static inline bool irq_domain_qcom_handle_wakeup(struct irq_domain *d) +{ + return (d->flags & IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP); +} + #endif From 47e18613bc6a50ab8dabe8dff756016519ecc5df Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Fri, 31 May 2019 09:22:08 -0700 Subject: [PATCH 2/2] irqchip: qcom-pdc: Add IPC logging support Add IPC logging support for debugging PDC interrupts. Change-Id: I541acccc44cdc617a72d44ac8b3528bbc05b2e69 Signed-off-by: Lina Iyer --- drivers/irqchip/qcom-pdc.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 64ff060893ff..5d5983cb48e5 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -20,8 +21,10 @@ #include #include +#include #define PDC_MAX_IRQS 168 +#define PDC_IPC_LOG_SZ 2 #define PDC_MAX_GPIO_IRQS 256 #define CLEAR_INTR(reg, intr) (reg & ~(1 << intr)) @@ -50,6 +53,7 @@ static void __iomem *pdc_base; static struct pdc_pin_region *pdc_region; static int pdc_region_cnt; static struct spi_cfg_regs *spi_cfg; +static void *pdc_ipc_log; static void pdc_reg_write(int reg, u32 i, u32 val) { @@ -94,6 +98,7 @@ static void pdc_enable_intr(struct irq_data *d, bool on) enable = pdc_reg_read(IRQ_ENABLE_BANK, index); enable = on ? ENABLE_INTR(enable, mask) : CLEAR_INTR(enable, mask); pdc_reg_write(IRQ_ENABLE_BANK, index, enable); + ipc_log_string(pdc_ipc_log, "PIN=%d enable=%d", d->hwirq, on); raw_spin_unlock(&pdc_lock); } @@ -120,6 +125,7 @@ static void qcom_pdc_gic_mask(struct irq_data *d) if (d->hwirq == GPIO_NO_WAKE_IRQ) return; + ipc_log_string(pdc_ipc_log, "PIN=%d mask", d->hwirq); irq_chip_mask_parent(d); } @@ -128,6 +134,7 @@ static void qcom_pdc_gic_unmask(struct irq_data *d) if (d->hwirq == GPIO_NO_WAKE_IRQ) return; + ipc_log_string(pdc_ipc_log, "PIN=%d unmask", d->hwirq); irq_chip_unmask_parent(d); } @@ -177,6 +184,9 @@ static int spi_configure_type(irq_hw_number_t hwirq, unsigned int type) if (type & IRQ_TYPE_LEVEL_MASK) val |= mask; __spi_pin_write(pin, val); + ipc_log_string(pdc_ipc_log, + "SPI config: GIC-SPI=%d (reg=%d,bit=%d) val=%d", + spi, pin, spi % 32, type & IRQ_TYPE_LEVEL_MASK); raw_spin_unlock_irqrestore(&pdc_lock, flags); return 0; @@ -251,6 +261,8 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) } pdc_reg_write(IRQ_i_CFG, pin_out, pdc_type); + ipc_log_string(pdc_ipc_log, "Set type: PIN=%d pdc_type=%d gic_type=%d", + pin_out, pdc_type, type); /* Additionally, configure (only) the GPIO in the f/w */ if (irq_domain_qcom_handle_wakeup(d->domain)) { @@ -344,6 +356,8 @@ static int qcom_pdc_alloc(struct irq_domain *domain, unsigned int virq, parent_fwspec.param[1] = parent_hwirq; parent_fwspec.param[2] = type; + ipc_log_string(pdc_ipc_log, "Alloc: PIN=%d GIC-SPI=%d", + hwirq, parent_hwirq); return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); } @@ -391,6 +405,8 @@ static int qcom_pdc_gpio_alloc(struct irq_domain *domain, unsigned int virq, parent_fwspec.param[1] = parent_hwirq; parent_fwspec.param[2] = type; + ipc_log_string(pdc_ipc_log, "GPIO alloc: PIN=%d GIC-SPI=%d", + hwirq, parent_hwirq); return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); } @@ -444,6 +460,14 @@ static int pdc_setup_pin_mapping(struct device_node *np) return 0; } +static int __init qcom_pdc_early_init(void) +{ + pdc_ipc_log = ipc_log_context_create(PDC_IPC_LOG_SZ, "pdc", 0); + + return 0; +} +module_init(qcom_pdc_early_init); + static int qcom_pdc_init(struct device_node *node, struct device_node *parent) { struct irq_domain *parent_domain, *pdc_domain, *pdc_gpio_domain;