projects/linux·blob master · refs

293 lines · 7.0 KiB

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/of_address.h>
17#include <linux/of_irq.h>
18#include <linux/syscore_ops.h>
19
20#include "irq-loongson.h"
21
22/* Registers */
23#define LPC_INT_CTL 0x00
24#define LPC_INT_ENA 0x04
25#define LPC_INT_STS 0x08
26#define LPC_INT_CLR 0x0c
27#define LPC_INT_POL 0x10
28#define LPC_COUNT 16
29
30/* LPC_INT_CTL */
31#define LPC_INT_CTL_EN BIT(31)
32
33struct pch_lpc {
34 void __iomem *base;
35 struct irq_domain *lpc_domain;
36 raw_spinlock_t lpc_lock;
37 u32 saved_reg_ctl;
38 u32 saved_reg_ena;
39 u32 saved_reg_pol;
40};
41
42static struct pch_lpc *pch_lpc_priv;
43struct fwnode_handle *pch_lpc_handle;
44
45static void lpc_irq_ack(struct irq_data *d)
46{
47 unsigned long flags;
48 struct pch_lpc *priv = d->domain->host_data;
49
50 raw_spin_lock_irqsave(&priv->lpc_lock, flags);
51 writel(0x1 << d->hwirq, priv->base + LPC_INT_CLR);
52 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
53}
54
55static void lpc_irq_mask(struct irq_data *d)
56{
57 unsigned long flags;
58 struct pch_lpc *priv = d->domain->host_data;
59
60 raw_spin_lock_irqsave(&priv->lpc_lock, flags);
61 writel(readl(priv->base + LPC_INT_ENA) & (~(0x1 << (d->hwirq))),
62 priv->base + LPC_INT_ENA);
63 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
64}
65
66static void lpc_irq_unmask(struct irq_data *d)
67{
68 unsigned long flags;
69 struct pch_lpc *priv = d->domain->host_data;
70
71 raw_spin_lock_irqsave(&priv->lpc_lock, flags);
72 writel(readl(priv->base + LPC_INT_ENA) | (0x1 << (d->hwirq)),
73 priv->base + LPC_INT_ENA);
74 raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
75}
76
77static int lpc_irq_set_type(struct irq_data *d, unsigned int type)
78{
79 u32 val;
80 u32 mask = 0x1 << (d->hwirq);
81 struct pch_lpc *priv = d->domain->host_data;
82
83 if (!(type & IRQ_TYPE_LEVEL_MASK))
84 return 0;
85
86 val = readl(priv->base + LPC_INT_POL);
87
88 if (type == IRQ_TYPE_LEVEL_HIGH)
89 val |= mask;
90 else
91 val &= ~mask;
92
93 writel(val, priv->base + LPC_INT_POL);
94
95 return 0;
96}
97
98static const struct irq_chip pch_lpc_irq_chip = {
99 .name = "PCH LPC",
100 .irq_mask = lpc_irq_mask,
101 .irq_unmask = lpc_irq_unmask,
102 .irq_ack = lpc_irq_ack,
103 .irq_set_type = lpc_irq_set_type,
104 .flags = IRQCHIP_SKIP_SET_WAKE,
105};
106
107static void lpc_irq_dispatch(struct irq_desc *desc)
108{
109 u32 pending, bit;
110 struct irq_chip *chip = irq_desc_get_chip(desc);
111 struct pch_lpc *priv = irq_desc_get_handler_data(desc);
112
113 chained_irq_enter(chip, desc);
114
115 pending = readl(priv->base + LPC_INT_ENA);
116 pending &= readl(priv->base + LPC_INT_STS);
117 if (!pending)
118 spurious_interrupt();
119
120 while (pending) {
121 bit = __ffs(pending);
122
123 generic_handle_domain_irq(priv->lpc_domain, bit);
124 pending &= ~BIT(bit);
125 }
126 chained_irq_exit(chip, desc);
127}
128
129static int pch_lpc_map(struct irq_domain *d, unsigned int irq,
130 irq_hw_number_t hw)
131{
132 irq_set_chip_and_handler(irq, &pch_lpc_irq_chip, handle_level_irq);
133 return 0;
134}
135
136static const struct irq_domain_ops pch_lpc_domain_ops = {
137 .map = pch_lpc_map,
138 .translate = irq_domain_translate_twocell,
139};
140
141static void pch_lpc_reset(struct pch_lpc *priv)
142{
143 /* Enable the LPC interrupt, bit31: en bit30: edge */
144 writel(LPC_INT_CTL_EN, priv->base + LPC_INT_CTL);
145 writel(0, priv->base + LPC_INT_ENA);
146 /* Clear all 18-bit interrpt bit */
147 writel(GENMASK(17, 0), priv->base + LPC_INT_CLR);
148}
149
150static int pch_lpc_disabled(struct pch_lpc *priv)
151{
152 return (readl(priv->base + LPC_INT_ENA) == 0xffffffff) &&
153 (readl(priv->base + LPC_INT_STS) == 0xffffffff);
154}
155
156static int pch_lpc_suspend(void *data)
157{
158 pch_lpc_priv->saved_reg_ctl = readl(pch_lpc_priv->base + LPC_INT_CTL);
159 pch_lpc_priv->saved_reg_ena = readl(pch_lpc_priv->base + LPC_INT_ENA);
160 pch_lpc_priv->saved_reg_pol = readl(pch_lpc_priv->base + LPC_INT_POL);
161 return 0;
162}
163
164static void pch_lpc_resume(void *data)
165{
166 writel(pch_lpc_priv->saved_reg_ctl, pch_lpc_priv->base + LPC_INT_CTL);
167 writel(pch_lpc_priv->saved_reg_ena, pch_lpc_priv->base + LPC_INT_ENA);
168 writel(pch_lpc_priv->saved_reg_pol, pch_lpc_priv->base + LPC_INT_POL);
169}
170
171static const struct syscore_ops pch_lpc_syscore_ops = {
172 .suspend = pch_lpc_suspend,
173 .resume = pch_lpc_resume,
174};
175
176static struct syscore pch_lpc_syscore = {
177 .ops = &pch_lpc_syscore_ops,
178};
179
180static int __init pch_lpc_init(phys_addr_t addr, unsigned long size,
181 struct fwnode_handle *irq_handle, int parent_irq)
182{
183 struct pch_lpc *priv;
184
185 priv = kzalloc_obj(*priv);
186 if (!priv)
187 return -ENOMEM;
188
189 raw_spin_lock_init(&priv->lpc_lock);
190
191 priv->base = ioremap(addr, size);
192 if (!priv->base)
193 goto free_priv;
194
195 if (pch_lpc_disabled(priv)) {
196 pr_err("Failed to get LPC status\n");
197 goto iounmap_base;
198 }
199
200 /*
201 * The LPC interrupt controller is a legacy i8259-compatible device,
202 * which requires a static 1:1 mapping for IRQs 0-15.
203 * Use irq_domain_create_legacy to establish this static mapping early.
204 */
205 priv->lpc_domain = irq_domain_create_legacy(irq_handle, LPC_COUNT, 0, 0,
206 &pch_lpc_domain_ops, priv);
207 if (!priv->lpc_domain) {
208 pr_err("Failed to create IRQ domain\n");
209 goto iounmap_base;
210 }
211 pch_lpc_reset(priv);
212
213 irq_set_chained_handler_and_data(parent_irq, lpc_irq_dispatch, priv);
214
215 pch_lpc_priv = priv;
216 pch_lpc_handle = irq_handle;
217 register_syscore(&pch_lpc_syscore);
218
219 return 0;
220
221iounmap_base:
222 iounmap(priv->base);
223free_priv:
224 kfree(priv);
225
226 return -ENOMEM;
227}
228
229#ifdef CONFIG_ACPI
230int __init pch_lpc_acpi_init(struct irq_domain *parent, struct acpi_madt_lpc_pic *acpi_pchlpc)
231{
232 struct fwnode_handle *irq_handle;
233 struct irq_fwspec fwspec;
234 int parent_irq, ret;
235
236 irq_handle = irq_domain_alloc_named_fwnode("lpcintc");
237 if (!irq_handle) {
238 pr_err("Unable to allocate domain handle\n");
239 return -ENOMEM;
240 }
241
242 fwspec.fwnode = parent->fwnode;
243 fwspec.param[0] = acpi_pchlpc->cascade + GSI_MIN_PCH_IRQ;
244 fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
245 fwspec.param_count = 2;
246 parent_irq = irq_create_fwspec_mapping(&fwspec);
247 if (parent_irq <= 0) {
248 pr_err("Unable to map LPC parent interrupt\n");
249 irq_domain_free_fwnode(irq_handle);
250 return -ENOMEM;
251 }
252
253 ret = pch_lpc_init(acpi_pchlpc->address, acpi_pchlpc->size, irq_handle, parent_irq);
254 if (ret) {
255 irq_dispose_mapping(parent_irq);
256 irq_domain_free_fwnode(irq_handle);
257 return ret;
258 }
259
260 return 0;
261}
262#endif /* CONFIG_ACPI */
263
264#ifdef CONFIG_OF
265static int __init pch_lpc_of_init(struct device_node *node, struct device_node *parent)
266{
267 struct fwnode_handle *irq_handle;
268 struct resource res;
269 int parent_irq, ret;
270
271 if (of_address_to_resource(node, 0, &res))
272 return -EINVAL;
273
274 parent_irq = irq_of_parse_and_map(node, 0);
275 if (!parent_irq) {
276 pr_err("Failed to get the parent IRQ for LPC IRQs\n");
277 return -EINVAL;
278 }
279
280 irq_handle = of_fwnode_handle(node);
281
282 ret = pch_lpc_init(res.start, resource_size(&res), irq_handle,
283 parent_irq);
284 if (ret) {
285 irq_dispose_mapping(parent_irq);
286 return ret;
287 }
288
289 return 0;
290}
291
292IRQCHIP_DECLARE(pch_lpc, "loongson,ls7a-lpc", pch_lpc_of_init);
293#endif /* CONFIG_OF */