mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 01:24:47 +01:00
This was done entirely with mindless brute force, using
git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'
to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.
Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.
For the same reason the 'flex' versions will be done as a separate
conversion.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
241 lines
5.8 KiB
C
241 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Loongson LPC Interrupt Controller support
|
|
*
|
|
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "lpc: " fmt
|
|
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqchip.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/syscore_ops.h>
|
|
|
|
#include "irq-loongson.h"
|
|
|
|
/* Registers */
|
|
#define LPC_INT_CTL 0x00
|
|
#define LPC_INT_ENA 0x04
|
|
#define LPC_INT_STS 0x08
|
|
#define LPC_INT_CLR 0x0c
|
|
#define LPC_INT_POL 0x10
|
|
#define LPC_COUNT 16
|
|
|
|
/* LPC_INT_CTL */
|
|
#define LPC_INT_CTL_EN BIT(31)
|
|
|
|
struct pch_lpc {
|
|
void __iomem *base;
|
|
struct irq_domain *lpc_domain;
|
|
raw_spinlock_t lpc_lock;
|
|
u32 saved_reg_ctl;
|
|
u32 saved_reg_ena;
|
|
u32 saved_reg_pol;
|
|
};
|
|
|
|
static struct pch_lpc *pch_lpc_priv;
|
|
struct fwnode_handle *pch_lpc_handle;
|
|
|
|
static void lpc_irq_ack(struct irq_data *d)
|
|
{
|
|
unsigned long flags;
|
|
struct pch_lpc *priv = d->domain->host_data;
|
|
|
|
raw_spin_lock_irqsave(&priv->lpc_lock, flags);
|
|
writel(0x1 << d->hwirq, priv->base + LPC_INT_CLR);
|
|
raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
|
|
}
|
|
|
|
static void lpc_irq_mask(struct irq_data *d)
|
|
{
|
|
unsigned long flags;
|
|
struct pch_lpc *priv = d->domain->host_data;
|
|
|
|
raw_spin_lock_irqsave(&priv->lpc_lock, flags);
|
|
writel(readl(priv->base + LPC_INT_ENA) & (~(0x1 << (d->hwirq))),
|
|
priv->base + LPC_INT_ENA);
|
|
raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
|
|
}
|
|
|
|
static void lpc_irq_unmask(struct irq_data *d)
|
|
{
|
|
unsigned long flags;
|
|
struct pch_lpc *priv = d->domain->host_data;
|
|
|
|
raw_spin_lock_irqsave(&priv->lpc_lock, flags);
|
|
writel(readl(priv->base + LPC_INT_ENA) | (0x1 << (d->hwirq)),
|
|
priv->base + LPC_INT_ENA);
|
|
raw_spin_unlock_irqrestore(&priv->lpc_lock, flags);
|
|
}
|
|
|
|
static int lpc_irq_set_type(struct irq_data *d, unsigned int type)
|
|
{
|
|
u32 val;
|
|
u32 mask = 0x1 << (d->hwirq);
|
|
struct pch_lpc *priv = d->domain->host_data;
|
|
|
|
if (!(type & IRQ_TYPE_LEVEL_MASK))
|
|
return 0;
|
|
|
|
val = readl(priv->base + LPC_INT_POL);
|
|
|
|
if (type == IRQ_TYPE_LEVEL_HIGH)
|
|
val |= mask;
|
|
else
|
|
val &= ~mask;
|
|
|
|
writel(val, priv->base + LPC_INT_POL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_chip pch_lpc_irq_chip = {
|
|
.name = "PCH LPC",
|
|
.irq_mask = lpc_irq_mask,
|
|
.irq_unmask = lpc_irq_unmask,
|
|
.irq_ack = lpc_irq_ack,
|
|
.irq_set_type = lpc_irq_set_type,
|
|
.flags = IRQCHIP_SKIP_SET_WAKE,
|
|
};
|
|
|
|
static void lpc_irq_dispatch(struct irq_desc *desc)
|
|
{
|
|
u32 pending, bit;
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct pch_lpc *priv = irq_desc_get_handler_data(desc);
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
pending = readl(priv->base + LPC_INT_ENA);
|
|
pending &= readl(priv->base + LPC_INT_STS);
|
|
if (!pending)
|
|
spurious_interrupt();
|
|
|
|
while (pending) {
|
|
bit = __ffs(pending);
|
|
|
|
generic_handle_domain_irq(priv->lpc_domain, bit);
|
|
pending &= ~BIT(bit);
|
|
}
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static int pch_lpc_map(struct irq_domain *d, unsigned int irq,
|
|
irq_hw_number_t hw)
|
|
{
|
|
irq_set_chip_and_handler(irq, &pch_lpc_irq_chip, handle_level_irq);
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops pch_lpc_domain_ops = {
|
|
.map = pch_lpc_map,
|
|
.translate = irq_domain_translate_twocell,
|
|
};
|
|
|
|
static void pch_lpc_reset(struct pch_lpc *priv)
|
|
{
|
|
/* Enable the LPC interrupt, bit31: en bit30: edge */
|
|
writel(LPC_INT_CTL_EN, priv->base + LPC_INT_CTL);
|
|
writel(0, priv->base + LPC_INT_ENA);
|
|
/* Clear all 18-bit interrpt bit */
|
|
writel(GENMASK(17, 0), priv->base + LPC_INT_CLR);
|
|
}
|
|
|
|
static int pch_lpc_disabled(struct pch_lpc *priv)
|
|
{
|
|
return (readl(priv->base + LPC_INT_ENA) == 0xffffffff) &&
|
|
(readl(priv->base + LPC_INT_STS) == 0xffffffff);
|
|
}
|
|
|
|
static int pch_lpc_suspend(void *data)
|
|
{
|
|
pch_lpc_priv->saved_reg_ctl = readl(pch_lpc_priv->base + LPC_INT_CTL);
|
|
pch_lpc_priv->saved_reg_ena = readl(pch_lpc_priv->base + LPC_INT_ENA);
|
|
pch_lpc_priv->saved_reg_pol = readl(pch_lpc_priv->base + LPC_INT_POL);
|
|
return 0;
|
|
}
|
|
|
|
static void pch_lpc_resume(void *data)
|
|
{
|
|
writel(pch_lpc_priv->saved_reg_ctl, pch_lpc_priv->base + LPC_INT_CTL);
|
|
writel(pch_lpc_priv->saved_reg_ena, pch_lpc_priv->base + LPC_INT_ENA);
|
|
writel(pch_lpc_priv->saved_reg_pol, pch_lpc_priv->base + LPC_INT_POL);
|
|
}
|
|
|
|
static const struct syscore_ops pch_lpc_syscore_ops = {
|
|
.suspend = pch_lpc_suspend,
|
|
.resume = pch_lpc_resume,
|
|
};
|
|
|
|
static struct syscore pch_lpc_syscore = {
|
|
.ops = &pch_lpc_syscore_ops,
|
|
};
|
|
|
|
int __init pch_lpc_acpi_init(struct irq_domain *parent,
|
|
struct acpi_madt_lpc_pic *acpi_pchlpc)
|
|
{
|
|
int parent_irq;
|
|
struct pch_lpc *priv;
|
|
struct irq_fwspec fwspec;
|
|
struct fwnode_handle *irq_handle;
|
|
|
|
priv = kzalloc_obj(*priv);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
raw_spin_lock_init(&priv->lpc_lock);
|
|
|
|
priv->base = ioremap(acpi_pchlpc->address, acpi_pchlpc->size);
|
|
if (!priv->base)
|
|
goto free_priv;
|
|
|
|
if (pch_lpc_disabled(priv)) {
|
|
pr_err("Failed to get LPC status\n");
|
|
goto iounmap_base;
|
|
}
|
|
|
|
irq_handle = irq_domain_alloc_named_fwnode("lpcintc");
|
|
if (!irq_handle) {
|
|
pr_err("Unable to allocate domain handle\n");
|
|
goto iounmap_base;
|
|
}
|
|
|
|
/*
|
|
* The LPC interrupt controller is a legacy i8259-compatible device,
|
|
* which requires a static 1:1 mapping for IRQs 0-15.
|
|
* Use irq_domain_create_legacy to establish this static mapping early.
|
|
*/
|
|
priv->lpc_domain = irq_domain_create_legacy(irq_handle, LPC_COUNT, 0, 0,
|
|
&pch_lpc_domain_ops, priv);
|
|
if (!priv->lpc_domain) {
|
|
pr_err("Failed to create IRQ domain\n");
|
|
goto free_irq_handle;
|
|
}
|
|
pch_lpc_reset(priv);
|
|
|
|
fwspec.fwnode = parent->fwnode;
|
|
fwspec.param[0] = acpi_pchlpc->cascade + GSI_MIN_PCH_IRQ;
|
|
fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
|
|
fwspec.param_count = 2;
|
|
parent_irq = irq_create_fwspec_mapping(&fwspec);
|
|
irq_set_chained_handler_and_data(parent_irq, lpc_irq_dispatch, priv);
|
|
|
|
pch_lpc_priv = priv;
|
|
pch_lpc_handle = irq_handle;
|
|
register_syscore(&pch_lpc_syscore);
|
|
|
|
return 0;
|
|
|
|
free_irq_handle:
|
|
irq_domain_free_fwnode(irq_handle);
|
|
iounmap_base:
|
|
iounmap(priv->base);
|
|
free_priv:
|
|
kfree(priv);
|
|
|
|
return -ENOMEM;
|
|
}
|