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 29struct 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 38static struct pch_lpc *pch_lpc_priv; 39struct fwnode_handle *pch_lpc_handle; 40 41static 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 51static 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 62static 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 73static 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 94static 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 103static 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 125static 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 132static const struct irq_domain_ops pch_lpc_domain_ops = { 133 .map = pch_lpc_map, 134 .translate = irq_domain_translate_twocell, 135}; 136 137static 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 146static 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 152static 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 160static 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 167static struct syscore_ops pch_lpc_syscore_ops = { 168 .suspend = pch_lpc_suspend, 169 .resume = pch_lpc_resume, 170}; 171 172int __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 222free_irq_handle: 223 irq_domain_free_fwnode(irq_handle); 224iounmap_base: 225 iounmap(priv->base); 226free_priv: 227 kfree(priv); 228 229 return -ENOMEM; 230} 231