diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 750bc455bd3c..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 @@ -19,7 +20,11 @@ #include #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)) @@ -36,10 +41,19 @@ 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_ipc_log; static void pdc_reg_write(int reg, u32 i, u32 val) { @@ -84,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); } @@ -110,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); } @@ -118,9 +134,64 @@ 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); } +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); + 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; +} + /* * 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 +229,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; @@ -188,6 +261,15 @@ 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)) { + ret = spi_configure_type(parent_hwirq, type); + if (ret) + return ret; + } return irq_chip_set_type_parent(d, type); } @@ -274,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); } @@ -321,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); } @@ -374,9 +460,18 @@ 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; + struct resource res; int ret; pdc_base = of_iomap(node, 0); @@ -407,6 +502,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 +540,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