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