mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.0 KiB
234 lines
6.0 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* CLPS711X IRQ driver |
|
* |
|
* Copyright (C) 2013 Alexander Shiyan <[email protected]> |
|
*/ |
|
|
|
#include <linux/io.h> |
|
#include <linux/irq.h> |
|
#include <linux/irqchip.h> |
|
#include <linux/irqdomain.h> |
|
#include <linux/of_address.h> |
|
#include <linux/of_irq.h> |
|
#include <linux/slab.h> |
|
|
|
#include <asm/exception.h> |
|
#include <asm/mach/irq.h> |
|
|
|
#define CLPS711X_INTSR1 (0x0240) |
|
#define CLPS711X_INTMR1 (0x0280) |
|
#define CLPS711X_BLEOI (0x0600) |
|
#define CLPS711X_MCEOI (0x0640) |
|
#define CLPS711X_TEOI (0x0680) |
|
#define CLPS711X_TC1EOI (0x06c0) |
|
#define CLPS711X_TC2EOI (0x0700) |
|
#define CLPS711X_RTCEOI (0x0740) |
|
#define CLPS711X_UMSEOI (0x0780) |
|
#define CLPS711X_COEOI (0x07c0) |
|
#define CLPS711X_INTSR2 (0x1240) |
|
#define CLPS711X_INTMR2 (0x1280) |
|
#define CLPS711X_SRXEOF (0x1600) |
|
#define CLPS711X_KBDEOI (0x1700) |
|
#define CLPS711X_INTSR3 (0x2240) |
|
#define CLPS711X_INTMR3 (0x2280) |
|
|
|
static const struct { |
|
#define CLPS711X_FLAG_EN (1 << 0) |
|
#define CLPS711X_FLAG_FIQ (1 << 1) |
|
unsigned int flags; |
|
phys_addr_t eoi; |
|
} clps711x_irqs[] = { |
|
[1] = { CLPS711X_FLAG_FIQ, CLPS711X_BLEOI, }, |
|
[3] = { CLPS711X_FLAG_FIQ, CLPS711X_MCEOI, }, |
|
[4] = { CLPS711X_FLAG_EN, CLPS711X_COEOI, }, |
|
[5] = { CLPS711X_FLAG_EN, }, |
|
[6] = { CLPS711X_FLAG_EN, }, |
|
[7] = { CLPS711X_FLAG_EN, }, |
|
[8] = { CLPS711X_FLAG_EN, CLPS711X_TC1EOI, }, |
|
[9] = { CLPS711X_FLAG_EN, CLPS711X_TC2EOI, }, |
|
[10] = { CLPS711X_FLAG_EN, CLPS711X_RTCEOI, }, |
|
[11] = { CLPS711X_FLAG_EN, CLPS711X_TEOI, }, |
|
[12] = { CLPS711X_FLAG_EN, }, |
|
[13] = { CLPS711X_FLAG_EN, }, |
|
[14] = { CLPS711X_FLAG_EN, CLPS711X_UMSEOI, }, |
|
[15] = { CLPS711X_FLAG_EN, CLPS711X_SRXEOF, }, |
|
[16] = { CLPS711X_FLAG_EN, CLPS711X_KBDEOI, }, |
|
[17] = { CLPS711X_FLAG_EN, }, |
|
[18] = { CLPS711X_FLAG_EN, }, |
|
[28] = { CLPS711X_FLAG_EN, }, |
|
[29] = { CLPS711X_FLAG_EN, }, |
|
[32] = { CLPS711X_FLAG_FIQ, }, |
|
}; |
|
|
|
static struct { |
|
void __iomem *base; |
|
void __iomem *intmr[3]; |
|
void __iomem *intsr[3]; |
|
struct irq_domain *domain; |
|
struct irq_domain_ops ops; |
|
} *clps711x_intc; |
|
|
|
static asmlinkage void __exception_irq_entry clps711x_irqh(struct pt_regs *regs) |
|
{ |
|
u32 irqstat; |
|
|
|
do { |
|
irqstat = readw_relaxed(clps711x_intc->intmr[0]) & |
|
readw_relaxed(clps711x_intc->intsr[0]); |
|
if (irqstat) |
|
handle_domain_irq(clps711x_intc->domain, |
|
fls(irqstat) - 1, regs); |
|
|
|
irqstat = readw_relaxed(clps711x_intc->intmr[1]) & |
|
readw_relaxed(clps711x_intc->intsr[1]); |
|
if (irqstat) |
|
handle_domain_irq(clps711x_intc->domain, |
|
fls(irqstat) - 1 + 16, regs); |
|
} while (irqstat); |
|
} |
|
|
|
static void clps711x_intc_eoi(struct irq_data *d) |
|
{ |
|
irq_hw_number_t hwirq = irqd_to_hwirq(d); |
|
|
|
writel_relaxed(0, clps711x_intc->base + clps711x_irqs[hwirq].eoi); |
|
} |
|
|
|
static void clps711x_intc_mask(struct irq_data *d) |
|
{ |
|
irq_hw_number_t hwirq = irqd_to_hwirq(d); |
|
void __iomem *intmr = clps711x_intc->intmr[hwirq / 16]; |
|
u32 tmp; |
|
|
|
tmp = readl_relaxed(intmr); |
|
tmp &= ~(1 << (hwirq % 16)); |
|
writel_relaxed(tmp, intmr); |
|
} |
|
|
|
static void clps711x_intc_unmask(struct irq_data *d) |
|
{ |
|
irq_hw_number_t hwirq = irqd_to_hwirq(d); |
|
void __iomem *intmr = clps711x_intc->intmr[hwirq / 16]; |
|
u32 tmp; |
|
|
|
tmp = readl_relaxed(intmr); |
|
tmp |= 1 << (hwirq % 16); |
|
writel_relaxed(tmp, intmr); |
|
} |
|
|
|
static struct irq_chip clps711x_intc_chip = { |
|
.name = "clps711x-intc", |
|
.irq_eoi = clps711x_intc_eoi, |
|
.irq_mask = clps711x_intc_mask, |
|
.irq_unmask = clps711x_intc_unmask, |
|
}; |
|
|
|
static int __init clps711x_intc_irq_map(struct irq_domain *h, unsigned int virq, |
|
irq_hw_number_t hw) |
|
{ |
|
irq_flow_handler_t handler = handle_level_irq; |
|
unsigned int flags = 0; |
|
|
|
if (!clps711x_irqs[hw].flags) |
|
return 0; |
|
|
|
if (clps711x_irqs[hw].flags & CLPS711X_FLAG_FIQ) { |
|
handler = handle_bad_irq; |
|
flags |= IRQ_NOAUTOEN; |
|
} else if (clps711x_irqs[hw].eoi) { |
|
handler = handle_fasteoi_irq; |
|
} |
|
|
|
/* Clear down pending interrupt */ |
|
if (clps711x_irqs[hw].eoi) |
|
writel_relaxed(0, clps711x_intc->base + clps711x_irqs[hw].eoi); |
|
|
|
irq_set_chip_and_handler(virq, &clps711x_intc_chip, handler); |
|
irq_modify_status(virq, IRQ_NOPROBE, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int __init _clps711x_intc_init(struct device_node *np, |
|
phys_addr_t base, resource_size_t size) |
|
{ |
|
int err; |
|
|
|
clps711x_intc = kzalloc(sizeof(*clps711x_intc), GFP_KERNEL); |
|
if (!clps711x_intc) |
|
return -ENOMEM; |
|
|
|
clps711x_intc->base = ioremap(base, size); |
|
if (!clps711x_intc->base) { |
|
err = -ENOMEM; |
|
goto out_kfree; |
|
} |
|
|
|
clps711x_intc->intsr[0] = clps711x_intc->base + CLPS711X_INTSR1; |
|
clps711x_intc->intmr[0] = clps711x_intc->base + CLPS711X_INTMR1; |
|
clps711x_intc->intsr[1] = clps711x_intc->base + CLPS711X_INTSR2; |
|
clps711x_intc->intmr[1] = clps711x_intc->base + CLPS711X_INTMR2; |
|
clps711x_intc->intsr[2] = clps711x_intc->base + CLPS711X_INTSR3; |
|
clps711x_intc->intmr[2] = clps711x_intc->base + CLPS711X_INTMR3; |
|
|
|
/* Mask all interrupts */ |
|
writel_relaxed(0, clps711x_intc->intmr[0]); |
|
writel_relaxed(0, clps711x_intc->intmr[1]); |
|
writel_relaxed(0, clps711x_intc->intmr[2]); |
|
|
|
err = irq_alloc_descs(-1, 0, ARRAY_SIZE(clps711x_irqs), numa_node_id()); |
|
if (err < 0) |
|
goto out_iounmap; |
|
|
|
clps711x_intc->ops.map = clps711x_intc_irq_map; |
|
clps711x_intc->ops.xlate = irq_domain_xlate_onecell; |
|
clps711x_intc->domain = |
|
irq_domain_add_legacy(np, ARRAY_SIZE(clps711x_irqs), |
|
0, 0, &clps711x_intc->ops, NULL); |
|
if (!clps711x_intc->domain) { |
|
err = -ENOMEM; |
|
goto out_irqfree; |
|
} |
|
|
|
irq_set_default_host(clps711x_intc->domain); |
|
set_handle_irq(clps711x_irqh); |
|
|
|
#ifdef CONFIG_FIQ |
|
init_FIQ(0); |
|
#endif |
|
|
|
return 0; |
|
|
|
out_irqfree: |
|
irq_free_descs(0, ARRAY_SIZE(clps711x_irqs)); |
|
|
|
out_iounmap: |
|
iounmap(clps711x_intc->base); |
|
|
|
out_kfree: |
|
kfree(clps711x_intc); |
|
|
|
return err; |
|
} |
|
|
|
void __init clps711x_intc_init(phys_addr_t base, resource_size_t size) |
|
{ |
|
BUG_ON(_clps711x_intc_init(NULL, base, size)); |
|
} |
|
|
|
#ifdef CONFIG_IRQCHIP |
|
static int __init clps711x_intc_init_dt(struct device_node *np, |
|
struct device_node *parent) |
|
{ |
|
struct resource res; |
|
int err; |
|
|
|
err = of_address_to_resource(np, 0, &res); |
|
if (err) |
|
return err; |
|
|
|
return _clps711x_intc_init(np, res.start, resource_size(&res)); |
|
} |
|
IRQCHIP_DECLARE(clps711x, "cirrus,ep7209-intc", clps711x_intc_init_dt); |
|
#endif
|
|
|