162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SH7760 DMABRG IRQ handling
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2007 MSC Vertriebsges.m.b.H, Manuel Lauss <mlau@msc-ge.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/interrupt.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <asm/dma.h>
1262306a36Sopenharmony_ci#include <asm/dmabrg.h>
1362306a36Sopenharmony_ci#include <asm/io.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/*
1662306a36Sopenharmony_ci * The DMABRG is a special DMA unit within the SH7760. It does transfers
1762306a36Sopenharmony_ci * from USB-SRAM/Audio units to main memory (and also the LCDC; but that
1862306a36Sopenharmony_ci * part is sensibly placed  in the LCDC  registers and requires no irqs)
1962306a36Sopenharmony_ci * It has 3 IRQ lines which trigger 10 events, and works independently
2062306a36Sopenharmony_ci * from the traditional SH DMAC (although it blocks usage of DMAC 0)
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * BRGIRQID   | component | dir | meaning      | source
2362306a36Sopenharmony_ci * -----------------------------------------------------
2462306a36Sopenharmony_ci *     0      | USB-DMA   | ... | xfer done    | DMABRGI1
2562306a36Sopenharmony_ci *     1      | USB-UAE   | ... | USB addr err.| DMABRGI0
2662306a36Sopenharmony_ci *     2      | HAC0/SSI0 | play| all done     | DMABRGI1
2762306a36Sopenharmony_ci *     3      | HAC0/SSI0 | play| half done    | DMABRGI2
2862306a36Sopenharmony_ci *     4      | HAC0/SSI0 | rec | all done     | DMABRGI1
2962306a36Sopenharmony_ci *     5      | HAC0/SSI0 | rec | half done    | DMABRGI2
3062306a36Sopenharmony_ci *     6      | HAC1/SSI1 | play| all done     | DMABRGI1
3162306a36Sopenharmony_ci *     7      | HAC1/SSI1 | play| half done    | DMABRGI2
3262306a36Sopenharmony_ci *     8      | HAC1/SSI1 | rec | all done     | DMABRGI1
3362306a36Sopenharmony_ci *     9      | HAC1/SSI1 | rec | half done    | DMABRGI2
3462306a36Sopenharmony_ci *
3562306a36Sopenharmony_ci * all can be enabled/disabled in the DMABRGCR register,
3662306a36Sopenharmony_ci * as well as checked if they occurred.
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * DMABRGI0 services  USB  DMA  Address  errors,  but it still must be
3962306a36Sopenharmony_ci * enabled/acked in the DMABRGCR register.  USB-DMA complete indicator
4062306a36Sopenharmony_ci * is grouped together with the audio buffer end indicators, too bad...
4162306a36Sopenharmony_ci *
4262306a36Sopenharmony_ci * DMABRGCR:	Bits 31-24: audio-dma ENABLE flags,
4362306a36Sopenharmony_ci *		Bits 23-16: audio-dma STATUS flags,
4462306a36Sopenharmony_ci *		Bits  9-8:  USB error/xfer ENABLE,
4562306a36Sopenharmony_ci *		Bits  1-0:  USB error/xfer STATUS.
4662306a36Sopenharmony_ci *	Ack an IRQ by writing 0 to the STATUS flag.
4762306a36Sopenharmony_ci *	Mask IRQ by writing 0 to ENABLE flag.
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * Usage is almost like with any other IRQ:
5062306a36Sopenharmony_ci *  dmabrg_request_irq(BRGIRQID, handler, data)
5162306a36Sopenharmony_ci *  dmabrg_free_irq(BRGIRQID)
5262306a36Sopenharmony_ci *
5362306a36Sopenharmony_ci * handler prototype:  void brgirqhandler(void *data)
5462306a36Sopenharmony_ci */
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci#define DMARSRA		0xfe090000
5762306a36Sopenharmony_ci#define DMAOR		0xffa00040
5862306a36Sopenharmony_ci#define DMACHCR0	0xffa0000c
5962306a36Sopenharmony_ci#define DMABRGCR	0xfe3c0000
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define DMAOR_BRG	0x0000c000
6262306a36Sopenharmony_ci#define DMAOR_DMEN	0x00000001
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#define DMABRGI0	68
6562306a36Sopenharmony_ci#define DMABRGI1	69
6662306a36Sopenharmony_ci#define DMABRGI2	70
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistruct dmabrg_handler {
6962306a36Sopenharmony_ci	void (*handler)(void *);
7062306a36Sopenharmony_ci	void *data;
7162306a36Sopenharmony_ci} *dmabrg_handlers;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic inline void dmabrg_call_handler(int i)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	dmabrg_handlers[i].handler(dmabrg_handlers[i].data);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/*
7962306a36Sopenharmony_ci * main DMABRG irq handler. It acks irqs and then
8062306a36Sopenharmony_ci * handles every set and unmasked bit sequentially.
8162306a36Sopenharmony_ci * No locking and no validity checks; it should be
8262306a36Sopenharmony_ci * as fast as possible (audio!)
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_cistatic irqreturn_t dmabrg_irq(int irq, void *data)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	unsigned long dcr;
8762306a36Sopenharmony_ci	unsigned int i;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
9062306a36Sopenharmony_ci	__raw_writel(dcr & ~0x00ff0003, DMABRGCR);	/* ack all */
9162306a36Sopenharmony_ci	dcr &= dcr >> 8;	/* ignore masked */
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/* USB stuff, get it out of the way first */
9462306a36Sopenharmony_ci	if (dcr & 1)
9562306a36Sopenharmony_ci		dmabrg_call_handler(DMABRGIRQ_USBDMA);
9662306a36Sopenharmony_ci	if (dcr & 2)
9762306a36Sopenharmony_ci		dmabrg_call_handler(DMABRGIRQ_USBDMAERR);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/* Audio */
10062306a36Sopenharmony_ci	dcr >>= 16;
10162306a36Sopenharmony_ci	while (dcr) {
10262306a36Sopenharmony_ci		i = __ffs(dcr);
10362306a36Sopenharmony_ci		dcr &= dcr - 1;
10462306a36Sopenharmony_ci		dmabrg_call_handler(i + DMABRGIRQ_A0TXF);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci	return IRQ_HANDLED;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void dmabrg_disable_irq(unsigned int dmairq)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	unsigned long dcr;
11262306a36Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
11362306a36Sopenharmony_ci	dcr &= ~(1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8));
11462306a36Sopenharmony_ci	__raw_writel(dcr, DMABRGCR);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic void dmabrg_enable_irq(unsigned int dmairq)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	unsigned long dcr;
12062306a36Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
12162306a36Sopenharmony_ci	dcr |= (1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8));
12262306a36Sopenharmony_ci	__raw_writel(dcr, DMABRGCR);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ciint dmabrg_request_irq(unsigned int dmairq, void(*handler)(void*),
12662306a36Sopenharmony_ci		       void *data)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	if ((dmairq > 9) || !handler)
12962306a36Sopenharmony_ci		return -ENOENT;
13062306a36Sopenharmony_ci	if (dmabrg_handlers[dmairq].handler)
13162306a36Sopenharmony_ci		return -EBUSY;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	dmabrg_handlers[dmairq].handler = handler;
13462306a36Sopenharmony_ci	dmabrg_handlers[dmairq].data = data;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	dmabrg_enable_irq(dmairq);
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dmabrg_request_irq);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_civoid dmabrg_free_irq(unsigned int dmairq)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	if (likely(dmairq < 10)) {
14462306a36Sopenharmony_ci		dmabrg_disable_irq(dmairq);
14562306a36Sopenharmony_ci		dmabrg_handlers[dmairq].handler = NULL;
14662306a36Sopenharmony_ci		dmabrg_handlers[dmairq].data = NULL;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dmabrg_free_irq);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic int __init dmabrg_init(void)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	unsigned long or;
15462306a36Sopenharmony_ci	int ret;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	dmabrg_handlers = kcalloc(10, sizeof(struct dmabrg_handler),
15762306a36Sopenharmony_ci				  GFP_KERNEL);
15862306a36Sopenharmony_ci	if (!dmabrg_handlers)
15962306a36Sopenharmony_ci		return -ENOMEM;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci#ifdef CONFIG_SH_DMA
16262306a36Sopenharmony_ci	/* request DMAC channel 0 before anyone else can get it */
16362306a36Sopenharmony_ci	ret = request_dma(0, "DMAC 0 (DMABRG)");
16462306a36Sopenharmony_ci	if (ret < 0)
16562306a36Sopenharmony_ci		printk(KERN_INFO "DMABRG: DMAC ch0 not reserved!\n");
16662306a36Sopenharmony_ci#endif
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	__raw_writel(0, DMABRGCR);
16962306a36Sopenharmony_ci	__raw_writel(0, DMACHCR0);
17062306a36Sopenharmony_ci	__raw_writel(0x94000000, DMARSRA);	/* enable DMABRG in DMAC 0 */
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* enable DMABRG mode, enable the DMAC */
17362306a36Sopenharmony_ci	or = __raw_readl(DMAOR);
17462306a36Sopenharmony_ci	__raw_writel(or | DMAOR_BRG | DMAOR_DMEN, DMAOR);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	ret = request_irq(DMABRGI0, dmabrg_irq, 0,
17762306a36Sopenharmony_ci			"DMABRG USB address error", NULL);
17862306a36Sopenharmony_ci	if (ret)
17962306a36Sopenharmony_ci		goto out0;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	ret = request_irq(DMABRGI1, dmabrg_irq, 0,
18262306a36Sopenharmony_ci			"DMABRG Transfer End", NULL);
18362306a36Sopenharmony_ci	if (ret)
18462306a36Sopenharmony_ci		goto out1;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ret = request_irq(DMABRGI2, dmabrg_irq, 0,
18762306a36Sopenharmony_ci			"DMABRG Transfer Half", NULL);
18862306a36Sopenharmony_ci	if (ret == 0)
18962306a36Sopenharmony_ci		return ret;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	free_irq(DMABRGI1, NULL);
19262306a36Sopenharmony_ciout1:	free_irq(DMABRGI0, NULL);
19362306a36Sopenharmony_ciout0:	kfree(dmabrg_handlers);
19462306a36Sopenharmony_ci	return ret;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_cisubsys_initcall(dmabrg_init);
197