1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Loongson LPC Interrupt Controller support 4 * 5 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited 6 */ 7 8 #define pr_fmt(fmt) "lpc: " fmt 9 10 #include <linux/interrupt.h> 11 #include <linux/irq.h> 12 #include <linux/irqchip.h> 13 #include <linux/irqchip/chained_irq.h> 14 #include <linux/irqdomain.h> 15 #include <linux/kernel.h> 16 #include <linux/syscore_ops.h> 17 18 /* Registers */ 19 #define LPC_INT_CTL 0x00 20 #define LPC_INT_ENA 0x04 21 #define LPC_INT_STS 0x08 22 #define LPC_INT_CLR 0x0c 23 #define LPC_INT_POL 0x10 24 #define LPC_COUNT 16 25 26 /* LPC_INT_CTL */ 27 #define LPC_INT_CTL_EN BIT(31) 28 29 struct pch_lpc { 30 void __iomem *base; 31 struct irq_domain *lpc_domain; 32 raw_spinlock_t lpc_lock; 33 u32 saved_reg_ctl; 34 u32 saved_reg_ena; 35 u32 saved_reg_pol; 36 }; 37 38 static struct pch_lpc *pch_lpc_priv; 39 struct fwnode_handle *pch_lpc_handle; 40 41 static void lpc_irq_ack(struct irq_data *d) 42 { 43 unsigned long flags; 44 struct pch_lpc *priv = d->domain->host_data; 45 46 raw_spin_lock_irqsave(&priv->lpc_lock, flags); 47 writel(0x1 << d->hwirq, priv->base + LPC_INT_CLR); 48 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags); 49 } 50 51 static void lpc_irq_mask(struct irq_data *d) 52 { 53 unsigned long flags; 54 struct pch_lpc *priv = d->domain->host_data; 55 56 raw_spin_lock_irqsave(&priv->lpc_lock, flags); 57 writel(readl(priv->base + LPC_INT_ENA) & (~(0x1 << (d->hwirq))), 58 priv->base + LPC_INT_ENA); 59 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags); 60 } 61 62 static void lpc_irq_unmask(struct irq_data *d) 63 { 64 unsigned long flags; 65 struct pch_lpc *priv = d->domain->host_data; 66 67 raw_spin_lock_irqsave(&priv->lpc_lock, flags); 68 writel(readl(priv->base + LPC_INT_ENA) | (0x1 << (d->hwirq)), 69 priv->base + LPC_INT_ENA); 70 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags); 71 } 72 73 static int lpc_irq_set_type(struct irq_data *d, unsigned int type) 74 { 75 u32 val; 76 u32 mask = 0x1 << (d->hwirq); 77 struct pch_lpc *priv = d->domain->host_data; 78 79 if (!(type & IRQ_TYPE_LEVEL_MASK)) 80 return 0; 81 82 val = readl(priv->base + LPC_INT_POL); 83 84 if (type == IRQ_TYPE_LEVEL_HIGH) 85 val |= mask; 86 else 87 val &= ~mask; 88 89 writel(val, priv->base + LPC_INT_POL); 90 91 return 0; 92 } 93 94 static const struct irq_chip pch_lpc_irq_chip = { 95 .name = "PCH LPC", 96 .irq_mask = lpc_irq_mask, 97 .irq_unmask = lpc_irq_unmask, 98 .irq_ack = lpc_irq_ack, 99 .irq_set_type = lpc_irq_set_type, 100 .flags = IRQCHIP_SKIP_SET_WAKE, 101 }; 102 103 static void lpc_irq_dispatch(struct irq_desc *desc) 104 { 105 u32 pending, bit; 106 struct irq_chip *chip = irq_desc_get_chip(desc); 107 struct pch_lpc *priv = irq_desc_get_handler_data(desc); 108 109 chained_irq_enter(chip, desc); 110 111 pending = readl(priv->base + LPC_INT_ENA); 112 pending &= readl(priv->base + LPC_INT_STS); 113 if (!pending) 114 spurious_interrupt(); 115 116 while (pending) { 117 bit = __ffs(pending); 118 119 generic_handle_domain_irq(priv->lpc_domain, bit); 120 pending &= ~BIT(bit); 121 } 122 chained_irq_exit(chip, desc); 123 } 124 125 static int pch_lpc_map(struct irq_domain *d, unsigned int irq, 126 irq_hw_number_t hw) 127 { 128 irq_set_chip_and_handler(irq, &pch_lpc_irq_chip, handle_level_irq); 129 return 0; 130 } 131 132 static const struct irq_domain_ops pch_lpc_domain_ops = { 133 .map = pch_lpc_map, 134 .translate = irq_domain_translate_twocell, 135 }; 136 137 static void pch_lpc_reset(struct pch_lpc *priv) 138 { 139 /* Enable the LPC interrupt, bit31: en bit30: edge */ 140 writel(LPC_INT_CTL_EN, priv->base + LPC_INT_CTL); 141 writel(0, priv->base + LPC_INT_ENA); 142 /* Clear all 18-bit interrpt bit */ 143 writel(GENMASK(17, 0), priv->base + LPC_INT_CLR); 144 } 145 146 static int pch_lpc_disabled(struct pch_lpc *priv) 147 { 148 return (readl(priv->base + LPC_INT_ENA) == 0xffffffff) && 149 (readl(priv->base + LPC_INT_STS) == 0xffffffff); 150 } 151 152 static int pch_lpc_suspend(void) 153 { 154 pch_lpc_priv->saved_reg_ctl = readl(pch_lpc_priv->base + LPC_INT_CTL); 155 pch_lpc_priv->saved_reg_ena = readl(pch_lpc_priv->base + LPC_INT_ENA); 156 pch_lpc_priv->saved_reg_pol = readl(pch_lpc_priv->base + LPC_INT_POL); 157 return 0; 158 } 159 160 static void pch_lpc_resume(void) 161 { 162 writel(pch_lpc_priv->saved_reg_ctl, pch_lpc_priv->base + LPC_INT_CTL); 163 writel(pch_lpc_priv->saved_reg_ena, pch_lpc_priv->base + LPC_INT_ENA); 164 writel(pch_lpc_priv->saved_reg_pol, pch_lpc_priv->base + LPC_INT_POL); 165 } 166 167 static struct syscore_ops pch_lpc_syscore_ops = { 168 .suspend = pch_lpc_suspend, 169 .resume = pch_lpc_resume, 170 }; 171 172 int __init pch_lpc_acpi_init(struct irq_domain *parent, 173 struct acpi_madt_lpc_pic *acpi_pchlpc) 174 { 175 int parent_irq; 176 struct pch_lpc *priv; 177 struct irq_fwspec fwspec; 178 struct fwnode_handle *irq_handle; 179 180 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 181 if (!priv) 182 return -ENOMEM; 183 184 raw_spin_lock_init(&priv->lpc_lock); 185 186 priv->base = ioremap(acpi_pchlpc->address, acpi_pchlpc->size); 187 if (!priv->base) 188 goto free_priv; 189 190 if (pch_lpc_disabled(priv)) { 191 pr_err("Failed to get LPC status\n"); 192 goto iounmap_base; 193 } 194 195 irq_handle = irq_domain_alloc_named_fwnode("lpcintc"); 196 if (!irq_handle) { 197 pr_err("Unable to allocate domain handle\n"); 198 goto iounmap_base; 199 } 200 201 priv->lpc_domain = irq_domain_create_linear(irq_handle, LPC_COUNT, 202 &pch_lpc_domain_ops, priv); 203 if (!priv->lpc_domain) { 204 pr_err("Failed to create IRQ domain\n"); 205 goto free_irq_handle; 206 } 207 pch_lpc_reset(priv); 208 209 fwspec.fwnode = parent->fwnode; 210 fwspec.param[0] = acpi_pchlpc->cascade + GSI_MIN_PCH_IRQ; 211 fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH; 212 fwspec.param_count = 2; 213 parent_irq = irq_create_fwspec_mapping(&fwspec); 214 irq_set_chained_handler_and_data(parent_irq, lpc_irq_dispatch, priv); 215 216 pch_lpc_priv = priv; 217 pch_lpc_handle = irq_handle; 218 register_syscore_ops(&pch_lpc_syscore_ops); 219 220 return 0; 221 222 free_irq_handle: 223 irq_domain_free_fwnode(irq_handle); 224 iounmap_base: 225 iounmap(priv->base); 226 free_priv: 227 kfree(priv); 228 229 return -ENOMEM; 230 } 231