| b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | From cf908990d4a8ccdb73ee4484aa8cadad379ca314 Mon Sep 17 00:00:00 2001 | 
|  | 2 | From: Jonas Gorski <jogo@openwrt.org> | 
|  | 3 | Date: Sun, 30 Nov 2014 14:54:27 +0100 | 
|  | 4 | Subject: [PATCH 2/5] irqchip: add support for bcm6345-style external | 
|  | 5 | interrupt controller | 
|  | 6 |  | 
|  | 7 | Signed-off-by: Jonas Gorski <jogo@openwrt.org> | 
|  | 8 | --- | 
|  | 9 | .../interrupt-controller/brcm,bcm6345-ext-intc.txt |   29 ++ | 
|  | 10 | drivers/irqchip/Kconfig                            |    4 + | 
|  | 11 | drivers/irqchip/Makefile                           |    1 + | 
|  | 12 | drivers/irqchip/irq-bcm6345-ext.c                  |  287 ++++++++++++++++++++ | 
|  | 13 | include/linux/irqchip/irq-bcm6345-ext.h            |   14 + | 
|  | 14 | 5 files changed, 335 insertions(+) | 
|  | 15 | create mode 100644 Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-ext-intc.txt | 
|  | 16 | create mode 100644 drivers/irqchip/irq-bcm6345-ext.c | 
|  | 17 | create mode 100644 include/linux/irqchip/irq-bcm6345-ext.h | 
|  | 18 |  | 
|  | 19 | --- /dev/null | 
|  | 20 | +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm6345-ext-intc.txt | 
|  | 21 | @@ -0,0 +1,29 @@ | 
|  | 22 | +Broadcom BCM6345-style external interrupt controller | 
|  | 23 | + | 
|  | 24 | +Required properties: | 
|  | 25 | + | 
|  | 26 | +- compatible: Should be "brcm,bcm6345-ext-intc" or "brcm,bcm6318-ext-intc". | 
|  | 27 | +- reg: Specifies the base physical addresses and size of the registers. | 
|  | 28 | +- interrupt-controller: identifies the node as an interrupt controller. | 
|  | 29 | +- #interrupt-cells: Specifies the number of cells needed to encode an interrupt | 
|  | 30 | +  source, Should be 2. | 
|  | 31 | +- interrupt-parent: Specifies the phandle to the parent interrupt controller | 
|  | 32 | +  this one is cascaded from. | 
|  | 33 | +- interrupts: Specifies the interrupt line(s) in the interrupt-parent controller | 
|  | 34 | +  node, valid values depend on the type of parent interrupt controller. | 
|  | 35 | + | 
|  | 36 | +Optional properties: | 
|  | 37 | + | 
|  | 38 | +- brcm,field-width: Size of each field (mask, clear, sense, ...) in bits in the | 
|  | 39 | +  register. Defaults to 4. | 
|  | 40 | + | 
|  | 41 | +Example: | 
|  | 42 | + | 
|  | 43 | +ext_intc: interrupt-controller@10000018 { | 
|  | 44 | +	compatible = "brcm,bcm6345-ext-intc"; | 
|  | 45 | +	interrupt-parent = <&periph_intc>; | 
|  | 46 | +	#interrupt-cells = <2>; | 
|  | 47 | +	reg = <0x10000018 0x4>; | 
|  | 48 | +	interrupt-controller; | 
|  | 49 | +	interrupts = <24>, <25>, <26>, <27>; | 
|  | 50 | +}; | 
|  | 51 | --- a/drivers/irqchip/Kconfig | 
|  | 52 | +++ b/drivers/irqchip/Kconfig | 
|  | 53 | @@ -145,6 +145,10 @@ config DAVINCI_CP_INTC | 
|  | 54 | select GENERIC_IRQ_CHIP | 
|  | 55 | select IRQ_DOMAIN | 
|  | 56 |  | 
|  | 57 | +config BCM6345_EXT_IRQ | 
|  | 58 | +	bool | 
|  | 59 | +	select IRQ_DOMAIN | 
|  | 60 | + | 
|  | 61 | config BCM6345_PERIPH_IRQ | 
|  | 62 | bool | 
|  | 63 | select IRQ_DOMAIN | 
|  | 64 | --- a/drivers/irqchip/Makefile | 
|  | 65 | +++ b/drivers/irqchip/Makefile | 
|  | 66 | @@ -17,6 +17,7 @@ obj-$(CONFIG_ARCH_MMP)			+= irq-mmp.o | 
|  | 67 | obj-$(CONFIG_IRQ_MXS)			+= irq-mxs.o | 
|  | 68 | obj-$(CONFIG_ARCH_TEGRA)		+= irq-tegra.o | 
|  | 69 | obj-$(CONFIG_ARCH_S3C24XX)		+= irq-s3c24xx.o | 
|  | 70 | +obj-$(CONFIG_BCM6345_EXT_IRQ)		+= irq-bcm6345-ext.o | 
|  | 71 | obj-$(CONFIG_BCM6345_PERIPH_IRQ)	+= irq-bcm6345-periph.o | 
|  | 72 | obj-$(CONFIG_DW_APB_ICTL)		+= irq-dw-apb-ictl.o | 
|  | 73 | obj-$(CONFIG_CLPS711X_IRQCHIP)		+= irq-clps711x.o | 
|  | 74 | --- /dev/null | 
|  | 75 | +++ b/drivers/irqchip/irq-bcm6345-ext.c | 
|  | 76 | @@ -0,0 +1,301 @@ | 
|  | 77 | +/* | 
|  | 78 | + * This file is subject to the terms and conditions of the GNU General Public | 
|  | 79 | + * License.  See the file "COPYING" in the main directory of this archive | 
|  | 80 | + * for more details. | 
|  | 81 | + * | 
|  | 82 | + * Copyright (C) 2014 Jonas Gorski <jogo@openwrt.org> | 
|  | 83 | + */ | 
|  | 84 | + | 
|  | 85 | +#include <linux/ioport.h> | 
|  | 86 | +#include <linux/irq.h> | 
|  | 87 | +#include <linux/irqchip.h> | 
|  | 88 | +#include <linux/irqchip/chained_irq.h> | 
|  | 89 | +#include <linux/irqchip/irq-bcm6345-ext.h> | 
|  | 90 | +#include <linux/kernel.h> | 
|  | 91 | +#include <linux/of.h> | 
|  | 92 | +#include <linux/of_irq.h> | 
|  | 93 | +#include <linux/of_address.h> | 
|  | 94 | +#include <linux/slab.h> | 
|  | 95 | +#include <linux/spinlock.h> | 
|  | 96 | + | 
|  | 97 | +#ifdef CONFIG_BCM63XX | 
|  | 98 | +#include <asm/mach-bcm63xx/bcm63xx_irq.h> | 
|  | 99 | + | 
|  | 100 | +#define VIRQ_BASE		IRQ_EXTERNAL_BASE | 
|  | 101 | +#else | 
|  | 102 | +#define VIRQ_BASE		0 | 
|  | 103 | +#endif | 
|  | 104 | + | 
|  | 105 | +#define MAX_IRQS		4 | 
|  | 106 | + | 
|  | 107 | +#define EXTIRQ_CFG_SENSE	0 | 
|  | 108 | +#define EXTIRQ_CFG_STAT		1 | 
|  | 109 | +#define EXTIRQ_CFG_CLEAR	2 | 
|  | 110 | +#define EXTIRQ_CFG_MASK		3 | 
|  | 111 | +#define EXTIRQ_CFG_BOTHEDGE	4 | 
|  | 112 | +#define EXTIRQ_CFG_LEVELSENSE	5 | 
|  | 113 | + | 
|  | 114 | +struct intc_data { | 
|  | 115 | +	struct irq_chip chip; | 
|  | 116 | +	struct irq_domain *domain; | 
|  | 117 | +	raw_spinlock_t lock; | 
|  | 118 | + | 
|  | 119 | +	int parent_irq[MAX_IRQS]; | 
|  | 120 | +	void __iomem *reg; | 
|  | 121 | +	int shift; | 
|  | 122 | +	unsigned int toggle_clear_on_ack:1; | 
|  | 123 | +}; | 
|  | 124 | + | 
|  | 125 | +static void bcm6345_ext_intc_irq_handle(struct irq_desc *desc) | 
|  | 126 | +{ | 
|  | 127 | +	struct intc_data *data = irq_desc_get_handler_data(desc); | 
|  | 128 | +	struct irq_chip *chip = irq_desc_get_chip(desc); | 
|  | 129 | +	unsigned int irq = irq_desc_get_irq(desc); | 
|  | 130 | +	unsigned int idx; | 
|  | 131 | + | 
|  | 132 | +	chained_irq_enter(chip, desc); | 
|  | 133 | + | 
|  | 134 | +	for (idx = 0; idx < MAX_IRQS; idx++) { | 
|  | 135 | +		if (data->parent_irq[idx] != irq) | 
|  | 136 | +			continue; | 
|  | 137 | + | 
|  | 138 | +		generic_handle_irq(irq_find_mapping(data->domain, idx)); | 
|  | 139 | +	} | 
|  | 140 | + | 
|  | 141 | +	chained_irq_exit(chip, desc); | 
|  | 142 | +} | 
|  | 143 | + | 
|  | 144 | +static void bcm6345_ext_intc_irq_ack(struct irq_data *data) | 
|  | 145 | +{ | 
|  | 146 | +	struct intc_data *priv = data->domain->host_data; | 
|  | 147 | +	irq_hw_number_t hwirq = irqd_to_hwirq(data); | 
|  | 148 | +	u32 reg; | 
|  | 149 | + | 
|  | 150 | +	raw_spin_lock(&priv->lock); | 
|  | 151 | +	reg = __raw_readl(priv->reg); | 
|  | 152 | +	__raw_writel(reg | (1 << (hwirq + EXTIRQ_CFG_CLEAR * priv->shift)), | 
|  | 153 | +		     priv->reg); | 
|  | 154 | +	if (priv->toggle_clear_on_ack) | 
|  | 155 | +		__raw_writel(reg, priv->reg); | 
|  | 156 | +	raw_spin_unlock(&priv->lock); | 
|  | 157 | +} | 
|  | 158 | + | 
|  | 159 | +static void bcm6345_ext_intc_irq_mask(struct irq_data *data) | 
|  | 160 | +{ | 
|  | 161 | +	struct intc_data *priv = data->domain->host_data; | 
|  | 162 | +	irq_hw_number_t hwirq = irqd_to_hwirq(data); | 
|  | 163 | +	u32 reg; | 
|  | 164 | + | 
|  | 165 | +	raw_spin_lock(&priv->lock); | 
|  | 166 | +	reg = __raw_readl(priv->reg); | 
|  | 167 | +	reg &= ~(1 << (hwirq + EXTIRQ_CFG_MASK * priv->shift)); | 
|  | 168 | +	__raw_writel(reg, priv->reg); | 
|  | 169 | +	raw_spin_unlock(&priv->lock); | 
|  | 170 | +} | 
|  | 171 | + | 
|  | 172 | +static void bcm6345_ext_intc_irq_unmask(struct irq_data *data) | 
|  | 173 | +{ | 
|  | 174 | +	struct intc_data *priv = data->domain->host_data; | 
|  | 175 | +	irq_hw_number_t hwirq = irqd_to_hwirq(data); | 
|  | 176 | +	u32 reg; | 
|  | 177 | + | 
|  | 178 | +	raw_spin_lock(&priv->lock); | 
|  | 179 | +	reg = __raw_readl(priv->reg); | 
|  | 180 | +	reg |= 1 << (hwirq + EXTIRQ_CFG_MASK * priv->shift); | 
|  | 181 | +	__raw_writel(reg, priv->reg); | 
|  | 182 | +	raw_spin_unlock(&priv->lock); | 
|  | 183 | +} | 
|  | 184 | + | 
|  | 185 | +static int bcm6345_ext_intc_set_type(struct irq_data *data, | 
|  | 186 | +				     unsigned int flow_type) | 
|  | 187 | +{ | 
|  | 188 | +	struct intc_data *priv = data->domain->host_data; | 
|  | 189 | +	irq_hw_number_t hwirq = irqd_to_hwirq(data); | 
|  | 190 | +	bool levelsense = 0, sense = 0, bothedge = 0; | 
|  | 191 | +	u32 reg; | 
|  | 192 | + | 
|  | 193 | +	flow_type &= IRQ_TYPE_SENSE_MASK; | 
|  | 194 | + | 
|  | 195 | +	if (flow_type == IRQ_TYPE_NONE) | 
|  | 196 | +		flow_type = IRQ_TYPE_LEVEL_LOW; | 
|  | 197 | + | 
|  | 198 | +	switch (flow_type) { | 
|  | 199 | +	case IRQ_TYPE_EDGE_BOTH: | 
|  | 200 | +		bothedge = 1; | 
|  | 201 | +		break; | 
|  | 202 | + | 
|  | 203 | +	case IRQ_TYPE_EDGE_RISING: | 
|  | 204 | +		sense = 1; | 
|  | 205 | +		break; | 
|  | 206 | + | 
|  | 207 | +	case IRQ_TYPE_EDGE_FALLING: | 
|  | 208 | +		break; | 
|  | 209 | + | 
|  | 210 | +	case IRQ_TYPE_LEVEL_HIGH: | 
|  | 211 | +		levelsense = 1; | 
|  | 212 | +		sense = 1; | 
|  | 213 | +		break; | 
|  | 214 | + | 
|  | 215 | +	case IRQ_TYPE_LEVEL_LOW: | 
|  | 216 | +		levelsense = 1; | 
|  | 217 | +		break; | 
|  | 218 | + | 
|  | 219 | +	default: | 
|  | 220 | +		pr_err("bogus flow type combination given!\n"); | 
|  | 221 | +		return -EINVAL; | 
|  | 222 | +	} | 
|  | 223 | + | 
|  | 224 | +	raw_spin_lock(&priv->lock); | 
|  | 225 | +	reg = __raw_readl(priv->reg); | 
|  | 226 | + | 
|  | 227 | +	if (levelsense) | 
|  | 228 | +		reg |= 1 << (hwirq + EXTIRQ_CFG_LEVELSENSE * priv->shift); | 
|  | 229 | +	else | 
|  | 230 | +		reg &= ~(1 << (hwirq + EXTIRQ_CFG_LEVELSENSE * priv->shift)); | 
|  | 231 | +	if (sense) | 
|  | 232 | +		reg |= 1 << (hwirq + EXTIRQ_CFG_SENSE * priv->shift); | 
|  | 233 | +	else | 
|  | 234 | +		reg &= ~(1 << (hwirq + EXTIRQ_CFG_SENSE * priv->shift)); | 
|  | 235 | +	if (bothedge) | 
|  | 236 | +		reg |= 1 << (hwirq + EXTIRQ_CFG_BOTHEDGE * priv->shift); | 
|  | 237 | +	else | 
|  | 238 | +		reg &= ~(1 << (hwirq + EXTIRQ_CFG_BOTHEDGE * priv->shift)); | 
|  | 239 | + | 
|  | 240 | +	__raw_writel(reg, priv->reg); | 
|  | 241 | +	raw_spin_unlock(&priv->lock); | 
|  | 242 | + | 
|  | 243 | +	irqd_set_trigger_type(data, flow_type); | 
|  | 244 | +	if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) | 
|  | 245 | +		irq_set_handler_locked(data, handle_level_irq); | 
|  | 246 | +	else | 
|  | 247 | +		irq_set_handler_locked(data, handle_edge_irq); | 
|  | 248 | + | 
|  | 249 | +	return 0; | 
|  | 250 | +} | 
|  | 251 | + | 
|  | 252 | +static int bcm6345_ext_intc_map(struct irq_domain *d, unsigned int irq, | 
|  | 253 | +				irq_hw_number_t hw) | 
|  | 254 | +{ | 
|  | 255 | +	struct intc_data *priv = d->host_data; | 
|  | 256 | + | 
|  | 257 | +	irq_set_chip_and_handler(irq, &priv->chip, handle_level_irq); | 
|  | 258 | + | 
|  | 259 | +	return 0; | 
|  | 260 | +} | 
|  | 261 | + | 
|  | 262 | +static const struct irq_domain_ops bcm6345_ext_domain_ops = { | 
|  | 263 | +	.xlate = irq_domain_xlate_twocell, | 
|  | 264 | +	.map = bcm6345_ext_intc_map, | 
|  | 265 | +}; | 
|  | 266 | + | 
|  | 267 | +static int __init __bcm6345_ext_intc_init(struct device_node *node, | 
|  | 268 | +					  int num_irqs, int *irqs, | 
|  | 269 | +					  void __iomem *reg, int shift, | 
|  | 270 | +					  bool toggle_clear_on_ack) | 
|  | 271 | +{ | 
|  | 272 | +	struct intc_data *data; | 
|  | 273 | +	unsigned int i; | 
|  | 274 | +	int start = VIRQ_BASE; | 
|  | 275 | + | 
|  | 276 | +	data = kzalloc(sizeof(*data), GFP_KERNEL); | 
|  | 277 | +	if (!data) | 
|  | 278 | +		return -ENOMEM; | 
|  | 279 | + | 
|  | 280 | +	raw_spin_lock_init(&data->lock); | 
|  | 281 | + | 
|  | 282 | +	for (i = 0; i < num_irqs; i++) { | 
|  | 283 | +		data->parent_irq[i] = irqs[i]; | 
|  | 284 | + | 
|  | 285 | +		irq_set_handler_data(irqs[i], data); | 
|  | 286 | +		irq_set_chained_handler(irqs[i], bcm6345_ext_intc_irq_handle); | 
|  | 287 | +	} | 
|  | 288 | + | 
|  | 289 | +	data->reg = reg; | 
|  | 290 | +	data->shift = shift; | 
|  | 291 | +	data->toggle_clear_on_ack = toggle_clear_on_ack; | 
|  | 292 | + | 
|  | 293 | +	data->chip.name = "bcm6345-ext-intc"; | 
|  | 294 | +	data->chip.irq_ack = bcm6345_ext_intc_irq_ack; | 
|  | 295 | +	data->chip.irq_mask = bcm6345_ext_intc_irq_mask; | 
|  | 296 | +	data->chip.irq_unmask = bcm6345_ext_intc_irq_unmask; | 
|  | 297 | +	data->chip.irq_set_type = bcm6345_ext_intc_set_type; | 
|  | 298 | + | 
|  | 299 | +	/* | 
|  | 300 | +	 * If we have less than 4 irqs, this is the second controller on | 
|  | 301 | +	 * bcm63xx. So increase the VIRQ start to not overlap with the first | 
|  | 302 | +	 * one, but only do so if we actually use a non-zero start. | 
|  | 303 | +	 * | 
|  | 304 | +	 * This can be removed when bcm63xx has no legacy users anymore. | 
|  | 305 | +	 */ | 
|  | 306 | +	if (start && num_irqs < 4) | 
|  | 307 | +		start += 4; | 
|  | 308 | + | 
|  | 309 | +	data->domain = irq_domain_add_simple(node, num_irqs, start, | 
|  | 310 | +					     &bcm6345_ext_domain_ops, data); | 
|  | 311 | +	if (!data->domain) { | 
|  | 312 | +		kfree(data); | 
|  | 313 | +		return -ENOMEM; | 
|  | 314 | +	} | 
|  | 315 | + | 
|  | 316 | +	return 0; | 
|  | 317 | +} | 
|  | 318 | + | 
|  | 319 | +void __init bcm6345_ext_intc_init(int num_irqs, int *irqs, void __iomem *reg, | 
|  | 320 | +				  int shift) | 
|  | 321 | +{ | 
|  | 322 | +	__bcm6345_ext_intc_init(NULL, num_irqs, irqs, reg, shift, false); | 
|  | 323 | +} | 
|  | 324 | + | 
|  | 325 | +#ifdef CONFIG_OF | 
|  | 326 | +static int __init bcm6345_ext_intc_of_init(struct device_node *node, | 
|  | 327 | +					   struct device_node *parent) | 
|  | 328 | +{ | 
|  | 329 | +	int num_irqs, ret = -EINVAL; | 
|  | 330 | +	unsigned i; | 
|  | 331 | +	void __iomem *base; | 
|  | 332 | +	int irqs[MAX_IRQS] = { 0 }; | 
|  | 333 | +	u32 shift; | 
|  | 334 | +	bool toggle_clear_on_ack = false; | 
|  | 335 | + | 
|  | 336 | +	num_irqs = of_irq_count(node); | 
|  | 337 | + | 
|  | 338 | +	if (!num_irqs || num_irqs > MAX_IRQS) | 
|  | 339 | +		return -EINVAL; | 
|  | 340 | + | 
|  | 341 | +	if (of_property_read_u32(node, "brcm,field-width", &shift)) | 
|  | 342 | +		shift = 4; | 
|  | 343 | + | 
|  | 344 | +	/* on BCM6318 setting CLEAR seems to continuously mask interrupts */ | 
|  | 345 | +	if (of_device_is_compatible(node, "brcm,bcm6318-ext-intc")) | 
|  | 346 | +		toggle_clear_on_ack = true; | 
|  | 347 | + | 
|  | 348 | +	for (i = 0; i < num_irqs; i++) { | 
|  | 349 | +		irqs[i] = irq_of_parse_and_map(node, i); | 
|  | 350 | +		if (!irqs[i]) { | 
|  | 351 | +			ret = -ENOMEM; | 
|  | 352 | +			goto out_unmap; | 
|  | 353 | +		} | 
|  | 354 | +	} | 
|  | 355 | + | 
|  | 356 | +	base = of_iomap(node, 0); | 
|  | 357 | +	if (!base) | 
|  | 358 | +		goto out_unmap; | 
|  | 359 | + | 
|  | 360 | +	ret = __bcm6345_ext_intc_init(node, num_irqs, irqs, base, shift, | 
|  | 361 | +				      toggle_clear_on_ack); | 
|  | 362 | +	if (!ret) | 
|  | 363 | +		return 0; | 
|  | 364 | +out_unmap: | 
|  | 365 | +	iounmap(base); | 
|  | 366 | + | 
|  | 367 | +	for (i = 0; i < num_irqs; i++) | 
|  | 368 | +		irq_dispose_mapping(irqs[i]); | 
|  | 369 | + | 
|  | 370 | +	return ret; | 
|  | 371 | +} | 
|  | 372 | + | 
|  | 373 | +IRQCHIP_DECLARE(bcm6318_ext_intc, "brcm,bcm6318-ext-intc", | 
|  | 374 | +		bcm6345_ext_intc_of_init); | 
|  | 375 | +IRQCHIP_DECLARE(bcm6345_ext_intc, "brcm,bcm6345-ext-intc", | 
|  | 376 | +		bcm6345_ext_intc_of_init); | 
|  | 377 | +#endif | 
|  | 378 | --- /dev/null | 
|  | 379 | +++ b/include/linux/irqchip/irq-bcm6345-ext.h | 
|  | 380 | @@ -0,0 +1,14 @@ | 
|  | 381 | +/* | 
|  | 382 | + * This file is subject to the terms and conditions of the GNU General Public | 
|  | 383 | + * License.  See the file "COPYING" in the main directory of this archive | 
|  | 384 | + * for more details. | 
|  | 385 | + * | 
|  | 386 | + * Copyright (C) 2014 Jonas Gorski <jogo@openwrt.org> | 
|  | 387 | + */ | 
|  | 388 | + | 
|  | 389 | +#ifndef __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H | 
|  | 390 | +#define __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H | 
|  | 391 | + | 
|  | 392 | +void bcm6345_ext_intc_init(int n_irqs, int *irqs, void __iomem *reg, int shift); | 
|  | 393 | + | 
|  | 394 | +#endif /* __INCLUDE_LINUX_IRQCHIP_IRQ_BCM6345_EXT_H */ |