cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

irq-mst-intc.c (7246B)


      1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
      2/*
      3 * Copyright (c) 2020 MediaTek Inc.
      4 * Author Mark-PK Tsai <mark-pk.tsai@mediatek.com>
      5 */
      6#include <linux/interrupt.h>
      7#include <linux/io.h>
      8#include <linux/irq.h>
      9#include <linux/irqchip.h>
     10#include <linux/irqdomain.h>
     11#include <linux/of.h>
     12#include <linux/of_address.h>
     13#include <linux/of_irq.h>
     14#include <linux/slab.h>
     15#include <linux/spinlock.h>
     16#include <linux/syscore_ops.h>
     17
     18#define MST_INTC_MAX_IRQS	64
     19
     20#define INTC_MASK		0x0
     21#define INTC_REV_POLARITY	0x10
     22#define INTC_EOI		0x20
     23
     24#ifdef CONFIG_PM_SLEEP
     25static LIST_HEAD(mst_intc_list);
     26#endif
     27
     28struct mst_intc_chip_data {
     29	raw_spinlock_t	lock;
     30	unsigned int	irq_start, nr_irqs;
     31	void __iomem	*base;
     32	bool		no_eoi;
     33#ifdef CONFIG_PM_SLEEP
     34	struct list_head entry;
     35	u16 saved_polarity_conf[DIV_ROUND_UP(MST_INTC_MAX_IRQS, 16)];
     36#endif
     37};
     38
     39static void mst_set_irq(struct irq_data *d, u32 offset)
     40{
     41	irq_hw_number_t hwirq = irqd_to_hwirq(d);
     42	struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
     43	u16 val, mask;
     44	unsigned long flags;
     45
     46	mask = 1 << (hwirq % 16);
     47	offset += (hwirq / 16) * 4;
     48
     49	raw_spin_lock_irqsave(&cd->lock, flags);
     50	val = readw_relaxed(cd->base + offset) | mask;
     51	writew_relaxed(val, cd->base + offset);
     52	raw_spin_unlock_irqrestore(&cd->lock, flags);
     53}
     54
     55static void mst_clear_irq(struct irq_data *d, u32 offset)
     56{
     57	irq_hw_number_t hwirq = irqd_to_hwirq(d);
     58	struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
     59	u16 val, mask;
     60	unsigned long flags;
     61
     62	mask = 1 << (hwirq % 16);
     63	offset += (hwirq / 16) * 4;
     64
     65	raw_spin_lock_irqsave(&cd->lock, flags);
     66	val = readw_relaxed(cd->base + offset) & ~mask;
     67	writew_relaxed(val, cd->base + offset);
     68	raw_spin_unlock_irqrestore(&cd->lock, flags);
     69}
     70
     71static void mst_intc_mask_irq(struct irq_data *d)
     72{
     73	mst_set_irq(d, INTC_MASK);
     74	irq_chip_mask_parent(d);
     75}
     76
     77static void mst_intc_unmask_irq(struct irq_data *d)
     78{
     79	mst_clear_irq(d, INTC_MASK);
     80	irq_chip_unmask_parent(d);
     81}
     82
     83static void mst_intc_eoi_irq(struct irq_data *d)
     84{
     85	struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d);
     86
     87	if (!cd->no_eoi)
     88		mst_set_irq(d, INTC_EOI);
     89
     90	irq_chip_eoi_parent(d);
     91}
     92
     93static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
     94{
     95	switch (type) {
     96	case IRQ_TYPE_LEVEL_LOW:
     97	case IRQ_TYPE_EDGE_FALLING:
     98		mst_set_irq(data, INTC_REV_POLARITY);
     99		break;
    100	case IRQ_TYPE_LEVEL_HIGH:
    101	case IRQ_TYPE_EDGE_RISING:
    102		mst_clear_irq(data, INTC_REV_POLARITY);
    103		break;
    104	default:
    105		return -EINVAL;
    106	}
    107
    108	return irq_chip_set_type_parent(data, IRQ_TYPE_LEVEL_HIGH);
    109}
    110
    111static struct irq_chip mst_intc_chip = {
    112	.name			= "mst-intc",
    113	.irq_mask		= mst_intc_mask_irq,
    114	.irq_unmask		= mst_intc_unmask_irq,
    115	.irq_eoi		= mst_intc_eoi_irq,
    116	.irq_get_irqchip_state	= irq_chip_get_parent_state,
    117	.irq_set_irqchip_state	= irq_chip_set_parent_state,
    118	.irq_set_affinity	= irq_chip_set_affinity_parent,
    119	.irq_set_vcpu_affinity	= irq_chip_set_vcpu_affinity_parent,
    120	.irq_set_type		= mst_irq_chip_set_type,
    121	.irq_retrigger		= irq_chip_retrigger_hierarchy,
    122	.flags			= IRQCHIP_SET_TYPE_MASKED |
    123				  IRQCHIP_SKIP_SET_WAKE |
    124				  IRQCHIP_MASK_ON_SUSPEND,
    125};
    126
    127#ifdef CONFIG_PM_SLEEP
    128static void mst_intc_polarity_save(struct mst_intc_chip_data *cd)
    129{
    130	int i;
    131	void __iomem *addr = cd->base + INTC_REV_POLARITY;
    132
    133	for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
    134		cd->saved_polarity_conf[i] = readw_relaxed(addr + i * 4);
    135}
    136
    137static void mst_intc_polarity_restore(struct mst_intc_chip_data *cd)
    138{
    139	int i;
    140	void __iomem *addr = cd->base + INTC_REV_POLARITY;
    141
    142	for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
    143		writew_relaxed(cd->saved_polarity_conf[i], addr + i * 4);
    144}
    145
    146static void mst_irq_resume(void)
    147{
    148	struct mst_intc_chip_data *cd;
    149
    150	list_for_each_entry(cd, &mst_intc_list, entry)
    151		mst_intc_polarity_restore(cd);
    152}
    153
    154static int mst_irq_suspend(void)
    155{
    156	struct mst_intc_chip_data *cd;
    157
    158	list_for_each_entry(cd, &mst_intc_list, entry)
    159		mst_intc_polarity_save(cd);
    160	return 0;
    161}
    162
    163static struct syscore_ops mst_irq_syscore_ops = {
    164	.suspend	= mst_irq_suspend,
    165	.resume		= mst_irq_resume,
    166};
    167
    168static int __init mst_irq_pm_init(void)
    169{
    170	register_syscore_ops(&mst_irq_syscore_ops);
    171	return 0;
    172}
    173late_initcall(mst_irq_pm_init);
    174#endif
    175
    176static int mst_intc_domain_translate(struct irq_domain *d,
    177				     struct irq_fwspec *fwspec,
    178				     unsigned long *hwirq,
    179				     unsigned int *type)
    180{
    181	struct mst_intc_chip_data *cd = d->host_data;
    182
    183	if (is_of_node(fwspec->fwnode)) {
    184		if (fwspec->param_count != 3)
    185			return -EINVAL;
    186
    187		/* No PPI should point to this domain */
    188		if (fwspec->param[0] != 0)
    189			return -EINVAL;
    190
    191		if (fwspec->param[1] >= cd->nr_irqs)
    192			return -EINVAL;
    193
    194		*hwirq = fwspec->param[1];
    195		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
    196		return 0;
    197	}
    198
    199	return -EINVAL;
    200}
    201
    202static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq,
    203				 unsigned int nr_irqs, void *data)
    204{
    205	int i;
    206	irq_hw_number_t hwirq;
    207	struct irq_fwspec parent_fwspec, *fwspec = data;
    208	struct mst_intc_chip_data *cd = domain->host_data;
    209
    210	/* Not GIC compliant */
    211	if (fwspec->param_count != 3)
    212		return -EINVAL;
    213
    214	/* No PPI should point to this domain */
    215	if (fwspec->param[0])
    216		return -EINVAL;
    217
    218	hwirq = fwspec->param[1];
    219	for (i = 0; i < nr_irqs; i++)
    220		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
    221					      &mst_intc_chip,
    222					      domain->host_data);
    223
    224	parent_fwspec = *fwspec;
    225	parent_fwspec.fwnode = domain->parent->fwnode;
    226	parent_fwspec.param[1] = cd->irq_start + hwirq;
    227
    228	/*
    229	 * mst-intc latch the interrupt request if it's edge triggered,
    230	 * so the output signal to parent GIC is always level sensitive.
    231	 * And if the irq signal is active low, configure it to active high
    232	 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit.
    233	 */
    234	parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
    235
    236	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec);
    237}
    238
    239static const struct irq_domain_ops mst_intc_domain_ops = {
    240	.translate	= mst_intc_domain_translate,
    241	.alloc		= mst_intc_domain_alloc,
    242	.free		= irq_domain_free_irqs_common,
    243};
    244
    245static int __init mst_intc_of_init(struct device_node *dn,
    246				   struct device_node *parent)
    247{
    248	struct irq_domain *domain, *domain_parent;
    249	struct mst_intc_chip_data *cd;
    250	u32 irq_start, irq_end;
    251
    252	domain_parent = irq_find_host(parent);
    253	if (!domain_parent) {
    254		pr_err("mst-intc: interrupt-parent not found\n");
    255		return -EINVAL;
    256	}
    257
    258	if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) ||
    259	    of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end))
    260		return -EINVAL;
    261
    262	cd = kzalloc(sizeof(*cd), GFP_KERNEL);
    263	if (!cd)
    264		return -ENOMEM;
    265
    266	cd->base = of_iomap(dn, 0);
    267	if (!cd->base) {
    268		kfree(cd);
    269		return -ENOMEM;
    270	}
    271
    272	cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi");
    273	raw_spin_lock_init(&cd->lock);
    274	cd->irq_start = irq_start;
    275	cd->nr_irqs = irq_end - irq_start + 1;
    276	domain = irq_domain_add_hierarchy(domain_parent, 0, cd->nr_irqs, dn,
    277					  &mst_intc_domain_ops, cd);
    278	if (!domain) {
    279		iounmap(cd->base);
    280		kfree(cd);
    281		return -ENOMEM;
    282	}
    283
    284#ifdef CONFIG_PM_SLEEP
    285	INIT_LIST_HEAD(&cd->entry);
    286	list_add_tail(&cd->entry, &mst_intc_list);
    287#endif
    288	return 0;
    289}
    290
    291IRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init);