Skip to content

Commit ee73f14

Browse files
chenhuacaiMarc Zyngier
authored andcommitted
irqchip: Add Loongson PCH LPC controller support
PCH-LPC stands for "LPC Interrupts" that described in Section 24.3 of "Loongson 7A1000 Bridge User Manual". For more information please refer Documentation/loongarch/irq-chip-model.rst. Co-developed-by: Jianmin Lv <lvjianmin@loongson.cn> Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn> Signed-off-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/1658314292-35346-8-git-send-email-lvjianmin@loongson.cn
1 parent 2dfded4 commit ee73f14

File tree

5 files changed

+216
-3
lines changed

5 files changed

+216
-3
lines changed

arch/loongarch/include/asm/irq.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ struct irq_domain *eiointc_acpi_init(struct irq_domain *parent,
112112

113113
struct irq_domain *htvec_acpi_init(struct irq_domain *parent,
114114
struct acpi_madt_ht_pic *acpi_htvec);
115-
struct irq_domain *pch_lpc_acpi_init(struct irq_domain *parent,
115+
int pch_lpc_acpi_init(struct irq_domain *parent,
116116
struct acpi_madt_lpc_pic *acpi_pchlpc);
117117
struct irq_domain *pch_msi_acpi_init(struct irq_domain *parent,
118118
struct acpi_madt_msi_pic *acpi_pchmsi);
@@ -129,7 +129,7 @@ extern struct acpi_madt_bio_pic *acpi_pchpic[MAX_IO_PICS];
129129

130130
extern struct irq_domain *cpu_domain;
131131
extern struct irq_domain *liointc_domain;
132-
extern struct irq_domain *pch_lpc_domain;
132+
extern struct fwnode_handle *pch_lpc_handle;
133133
extern struct irq_domain *pch_msi_domain[MAX_IO_PICS];
134134
extern struct irq_domain *pch_pic_domain[MAX_IO_PICS];
135135

arch/loongarch/kernel/irq.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ EXPORT_PER_CPU_SYMBOL(irq_stat);
2727

2828
struct irq_domain *cpu_domain;
2929
struct irq_domain *liointc_domain;
30-
struct irq_domain *pch_lpc_domain;
3130
struct irq_domain *pch_msi_domain[MAX_IO_PICS];
3231
struct irq_domain *pch_pic_domain[MAX_IO_PICS];
3332

drivers/irqchip/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,14 @@ config LOONGSON_PCH_MSI
591591
help
592592
Support for the Loongson PCH MSI Controller.
593593

594+
config LOONGSON_PCH_LPC
595+
bool "Loongson PCH LPC Controller"
596+
depends on MACH_LOONGSON64
597+
default (MACH_LOONGSON64 && LOONGARCH)
598+
select IRQ_DOMAIN_HIERARCHY
599+
help
600+
Support for the Loongson PCH LPC Controller.
601+
594602
config MST_IRQ
595603
bool "MStar Interrupt Controller"
596604
depends on ARCH_MEDIATEK || ARCH_MSTARV7 || COMPILE_TEST

drivers/irqchip/Makefile

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

0 commit comments

Comments
 (0)