linux/drivers/irqchip/irq-mxs.c

241 lines
6.1 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 1 Based on 2 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin street fifth floor boston ma 02110 1301 usa this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option [no]_[pad]_[ctrl] any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin street fifth floor boston ma 02110 1301 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 176 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Jilayne Lovejoy <opensource@jilayne.com> Reviewed-by: Steve Winslow <swinslow@gmail.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190519154040.652910950@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-19 21:51:31 +08:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de>
* Add Alphascale ASM9260 support.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/stmp_device.h>
#include <asm/exception.h>
#include "alphascale_asm9260-icoll.h"
/*
* this device provide 4 offsets for each register:
* 0x0 - plain read write mode
* 0x4 - set mode, OR logic.
* 0x8 - clr mode, XOR logic.
* 0xc - togle mode.
*/
#define SET_REG 4
#define CLR_REG 8
#define HW_ICOLL_VECTOR 0x0000
#define HW_ICOLL_LEVELACK 0x0010
#define HW_ICOLL_CTRL 0x0020
#define HW_ICOLL_STAT_OFFSET 0x0070
#define HW_ICOLL_INTERRUPT0 0x0120
#define HW_ICOLL_INTERRUPTn(n) ((n) * 0x10)
#define BM_ICOLL_INTR_ENABLE BIT(2)
#define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 0x1
#define ICOLL_NUM_IRQS 128
enum icoll_type {
ICOLL,
ASM9260_ICOLL,
};
struct icoll_priv {
void __iomem *vector;
void __iomem *levelack;
void __iomem *ctrl;
void __iomem *stat;
void __iomem *intr;
void __iomem *clear;
enum icoll_type type;
};
static struct icoll_priv icoll_priv;
static struct irq_domain *icoll_domain;
/* calculate bit offset depending on number of interrupt per register */
static u32 icoll_intr_bitshift(struct irq_data *d, u32 bit)
{
/*
* mask lower part of hwirq to convert it
* in 0, 1, 2 or 3 and then multiply it by 8 (or shift by 3)
*/
return bit << ((d->hwirq & 3) << 3);
}
/* calculate mem offset depending on number of interrupt per register */
static void __iomem *icoll_intr_reg(struct irq_data *d)
{
/* offset = hwirq / intr_per_reg * 0x10 */
return icoll_priv.intr + ((d->hwirq >> 2) * 0x10);
}
static void icoll_ack_irq(struct irq_data *d)
{
/*
* The Interrupt Collector is able to prioritize irqs.
* Currently only level 0 is used. So acking can use
* BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 unconditionally.
*/
__raw_writel(BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0,
icoll_priv.levelack);
}
static void icoll_mask_irq(struct irq_data *d)
{
__raw_writel(BM_ICOLL_INTR_ENABLE,
icoll_priv.intr + CLR_REG + HW_ICOLL_INTERRUPTn(d->hwirq));
}
static void icoll_unmask_irq(struct irq_data *d)
{
__raw_writel(BM_ICOLL_INTR_ENABLE,
icoll_priv.intr + SET_REG + HW_ICOLL_INTERRUPTn(d->hwirq));
}
static void asm9260_mask_irq(struct irq_data *d)
{
__raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE),
icoll_intr_reg(d) + CLR_REG);
}
static void asm9260_unmask_irq(struct irq_data *d)
{
__raw_writel(ASM9260_BM_CLEAR_BIT(d->hwirq),
icoll_priv.clear +
ASM9260_HW_ICOLL_CLEARn(d->hwirq));
__raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE),
icoll_intr_reg(d) + SET_REG);
}
static struct irq_chip mxs_icoll_chip = {
.irq_ack = icoll_ack_irq,
.irq_mask = icoll_mask_irq,
.irq_unmask = icoll_unmask_irq,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SKIP_SET_WAKE,
};
static struct irq_chip asm9260_icoll_chip = {
.irq_ack = icoll_ack_irq,
.irq_mask = asm9260_mask_irq,
.irq_unmask = asm9260_unmask_irq,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SKIP_SET_WAKE,
};
asmlinkage void __exception_irq_entry icoll_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
irqnr = __raw_readl(icoll_priv.stat);
__raw_writel(irqnr, icoll_priv.vector);
generic_handle_domain_irq(icoll_domain, irqnr);
}
static int icoll_irq_domain_map(struct irq_domain *d, unsigned int virq,
irq_hw_number_t hw)
{
struct irq_chip *chip;
if (icoll_priv.type == ICOLL)
chip = &mxs_icoll_chip;
else
chip = &asm9260_icoll_chip;
irq_set_chip_and_handler(virq, chip, handle_level_irq);
return 0;
}
static const struct irq_domain_ops icoll_irq_domain_ops = {
.map = icoll_irq_domain_map,
.xlate = irq_domain_xlate_onecell,
};
static void __init icoll_add_domain(struct device_node *np,
int num)
{
icoll_domain = irq_domain_add_linear(np, num,
&icoll_irq_domain_ops, NULL);
if (!icoll_domain)
panic("%pOF: unable to create irq domain", np);
}
static void __iomem * __init icoll_init_iobase(struct device_node *np)
{
void __iomem *icoll_base;
icoll_base = of_io_request_and_map(np, 0, np->name);
if (IS_ERR(icoll_base))
panic("%pOF: unable to map resource", np);
return icoll_base;
}
static int __init icoll_of_init(struct device_node *np,
struct device_node *interrupt_parent)
{
void __iomem *icoll_base;
icoll_priv.type = ICOLL;
icoll_base = icoll_init_iobase(np);
icoll_priv.vector = icoll_base + HW_ICOLL_VECTOR;
icoll_priv.levelack = icoll_base + HW_ICOLL_LEVELACK;
icoll_priv.ctrl = icoll_base + HW_ICOLL_CTRL;
icoll_priv.stat = icoll_base + HW_ICOLL_STAT_OFFSET;
icoll_priv.intr = icoll_base + HW_ICOLL_INTERRUPT0;
icoll_priv.clear = NULL;
/*
* Interrupt Collector reset, which initializes the priority
* for each irq to level 0.
*/
stmp_reset_block(icoll_priv.ctrl);
icoll_add_domain(np, ICOLL_NUM_IRQS);
set_handle_irq(icoll_handle_irq);
return 0;
}
IRQCHIP_DECLARE(mxs, "fsl,icoll", icoll_of_init);
static int __init asm9260_of_init(struct device_node *np,
struct device_node *interrupt_parent)
{
void __iomem *icoll_base;
int i;
icoll_priv.type = ASM9260_ICOLL;
icoll_base = icoll_init_iobase(np);
icoll_priv.vector = icoll_base + ASM9260_HW_ICOLL_VECTOR;
icoll_priv.levelack = icoll_base + ASM9260_HW_ICOLL_LEVELACK;
icoll_priv.ctrl = icoll_base + ASM9260_HW_ICOLL_CTRL;
icoll_priv.stat = icoll_base + ASM9260_HW_ICOLL_STAT_OFFSET;
icoll_priv.intr = icoll_base + ASM9260_HW_ICOLL_INTERRUPT0;
icoll_priv.clear = icoll_base + ASM9260_HW_ICOLL_CLEAR0;
writel_relaxed(ASM9260_BM_CTRL_IRQ_ENABLE,
icoll_priv.ctrl);
/*
* ASM9260 don't provide reset bit. So, we need to set level 0
* manually.
*/
for (i = 0; i < 16 * 0x10; i += 0x10)
writel(0, icoll_priv.intr + i);
icoll_add_domain(np, ASM9260_NUM_IRQS);
set_handle_irq(icoll_handle_irq);
return 0;
}
IRQCHIP_DECLARE(asm9260, "alphascale,asm9260-icoll", asm9260_of_init);