Loading drivers/irqchip/qcom-pdc.c +117 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #include <linux/irqdomain.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_device.h> Loading @@ -19,7 +20,11 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/qcom_scm.h> #include <linux/ipc_logging.h> #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)) Loading @@ -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) { Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -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, Loading @@ -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); Loading include/linux/soc/qcom/irq.h +15 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ #ifndef __QCOM_IRQ_H #define __QCOM_IRQ_H struct irq_domain; #define GPIO_NO_WAKE_IRQ ~0U /** Loading @@ -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 Loading
drivers/irqchip/qcom-pdc.c +117 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #include <linux/irqdomain.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_device.h> Loading @@ -19,7 +20,11 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/qcom_scm.h> #include <linux/ipc_logging.h> #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)) Loading @@ -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) { Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -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, Loading @@ -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); Loading
include/linux/soc/qcom/irq.h +15 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ #ifndef __QCOM_IRQ_H #define __QCOM_IRQ_H struct irq_domain; #define GPIO_NO_WAKE_IRQ ~0U /** Loading @@ -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