18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * SH7760 DMABRG IRQ handling
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) 2007 MSC Vertriebsges.m.b.H, Manuel Lauss <mlau@msc-ge.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <asm/dma.h>
128c2ecf20Sopenharmony_ci#include <asm/dmabrg.h>
138c2ecf20Sopenharmony_ci#include <asm/io.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/*
168c2ecf20Sopenharmony_ci * The DMABRG is a special DMA unit within the SH7760. It does transfers
178c2ecf20Sopenharmony_ci * from USB-SRAM/Audio units to main memory (and also the LCDC; but that
188c2ecf20Sopenharmony_ci * part is sensibly placed  in the LCDC  registers and requires no irqs)
198c2ecf20Sopenharmony_ci * It has 3 IRQ lines which trigger 10 events, and works independently
208c2ecf20Sopenharmony_ci * from the traditional SH DMAC (although it blocks usage of DMAC 0)
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * BRGIRQID   | component | dir | meaning      | source
238c2ecf20Sopenharmony_ci * -----------------------------------------------------
248c2ecf20Sopenharmony_ci *     0      | USB-DMA   | ... | xfer done    | DMABRGI1
258c2ecf20Sopenharmony_ci *     1      | USB-UAE   | ... | USB addr err.| DMABRGI0
268c2ecf20Sopenharmony_ci *     2      | HAC0/SSI0 | play| all done     | DMABRGI1
278c2ecf20Sopenharmony_ci *     3      | HAC0/SSI0 | play| half done    | DMABRGI2
288c2ecf20Sopenharmony_ci *     4      | HAC0/SSI0 | rec | all done     | DMABRGI1
298c2ecf20Sopenharmony_ci *     5      | HAC0/SSI0 | rec | half done    | DMABRGI2
308c2ecf20Sopenharmony_ci *     6      | HAC1/SSI1 | play| all done     | DMABRGI1
318c2ecf20Sopenharmony_ci *     7      | HAC1/SSI1 | play| half done    | DMABRGI2
328c2ecf20Sopenharmony_ci *     8      | HAC1/SSI1 | rec | all done     | DMABRGI1
338c2ecf20Sopenharmony_ci *     9      | HAC1/SSI1 | rec | half done    | DMABRGI2
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * all can be enabled/disabled in the DMABRGCR register,
368c2ecf20Sopenharmony_ci * as well as checked if they occurred.
378c2ecf20Sopenharmony_ci *
388c2ecf20Sopenharmony_ci * DMABRGI0 services  USB  DMA  Address  errors,  but it still must be
398c2ecf20Sopenharmony_ci * enabled/acked in the DMABRGCR register.  USB-DMA complete indicator
408c2ecf20Sopenharmony_ci * is grouped together with the audio buffer end indicators, too bad...
418c2ecf20Sopenharmony_ci *
428c2ecf20Sopenharmony_ci * DMABRGCR:	Bits 31-24: audio-dma ENABLE flags,
438c2ecf20Sopenharmony_ci *		Bits 23-16: audio-dma STATUS flags,
448c2ecf20Sopenharmony_ci *		Bits  9-8:  USB error/xfer ENABLE,
458c2ecf20Sopenharmony_ci *		Bits  1-0:  USB error/xfer STATUS.
468c2ecf20Sopenharmony_ci *	Ack an IRQ by writing 0 to the STATUS flag.
478c2ecf20Sopenharmony_ci *	Mask IRQ by writing 0 to ENABLE flag.
488c2ecf20Sopenharmony_ci *
498c2ecf20Sopenharmony_ci * Usage is almost like with any other IRQ:
508c2ecf20Sopenharmony_ci *  dmabrg_request_irq(BRGIRQID, handler, data)
518c2ecf20Sopenharmony_ci *  dmabrg_free_irq(BRGIRQID)
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * handler prototype:  void brgirqhandler(void *data)
548c2ecf20Sopenharmony_ci */
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci#define DMARSRA		0xfe090000
578c2ecf20Sopenharmony_ci#define DMAOR		0xffa00040
588c2ecf20Sopenharmony_ci#define DMACHCR0	0xffa0000c
598c2ecf20Sopenharmony_ci#define DMABRGCR	0xfe3c0000
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define DMAOR_BRG	0x0000c000
628c2ecf20Sopenharmony_ci#define DMAOR_DMEN	0x00000001
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#define DMABRGI0	68
658c2ecf20Sopenharmony_ci#define DMABRGI1	69
668c2ecf20Sopenharmony_ci#define DMABRGI2	70
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistruct dmabrg_handler {
698c2ecf20Sopenharmony_ci	void (*handler)(void *);
708c2ecf20Sopenharmony_ci	void *data;
718c2ecf20Sopenharmony_ci} *dmabrg_handlers;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic inline void dmabrg_call_handler(int i)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	dmabrg_handlers[i].handler(dmabrg_handlers[i].data);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/*
798c2ecf20Sopenharmony_ci * main DMABRG irq handler. It acks irqs and then
808c2ecf20Sopenharmony_ci * handles every set and unmasked bit sequentially.
818c2ecf20Sopenharmony_ci * No locking and no validity checks; it should be
828c2ecf20Sopenharmony_ci * as fast as possible (audio!)
838c2ecf20Sopenharmony_ci */
848c2ecf20Sopenharmony_cistatic irqreturn_t dmabrg_irq(int irq, void *data)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	unsigned long dcr;
878c2ecf20Sopenharmony_ci	unsigned int i;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
908c2ecf20Sopenharmony_ci	__raw_writel(dcr & ~0x00ff0003, DMABRGCR);	/* ack all */
918c2ecf20Sopenharmony_ci	dcr &= dcr >> 8;	/* ignore masked */
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	/* USB stuff, get it out of the way first */
948c2ecf20Sopenharmony_ci	if (dcr & 1)
958c2ecf20Sopenharmony_ci		dmabrg_call_handler(DMABRGIRQ_USBDMA);
968c2ecf20Sopenharmony_ci	if (dcr & 2)
978c2ecf20Sopenharmony_ci		dmabrg_call_handler(DMABRGIRQ_USBDMAERR);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	/* Audio */
1008c2ecf20Sopenharmony_ci	dcr >>= 16;
1018c2ecf20Sopenharmony_ci	while (dcr) {
1028c2ecf20Sopenharmony_ci		i = __ffs(dcr);
1038c2ecf20Sopenharmony_ci		dcr &= dcr - 1;
1048c2ecf20Sopenharmony_ci		dmabrg_call_handler(i + DMABRGIRQ_A0TXF);
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void dmabrg_disable_irq(unsigned int dmairq)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	unsigned long dcr;
1128c2ecf20Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
1138c2ecf20Sopenharmony_ci	dcr &= ~(1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8));
1148c2ecf20Sopenharmony_ci	__raw_writel(dcr, DMABRGCR);
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic void dmabrg_enable_irq(unsigned int dmairq)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	unsigned long dcr;
1208c2ecf20Sopenharmony_ci	dcr = __raw_readl(DMABRGCR);
1218c2ecf20Sopenharmony_ci	dcr |= (1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8));
1228c2ecf20Sopenharmony_ci	__raw_writel(dcr, DMABRGCR);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ciint dmabrg_request_irq(unsigned int dmairq, void(*handler)(void*),
1268c2ecf20Sopenharmony_ci		       void *data)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	if ((dmairq > 9) || !handler)
1298c2ecf20Sopenharmony_ci		return -ENOENT;
1308c2ecf20Sopenharmony_ci	if (dmabrg_handlers[dmairq].handler)
1318c2ecf20Sopenharmony_ci		return -EBUSY;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	dmabrg_handlers[dmairq].handler = handler;
1348c2ecf20Sopenharmony_ci	dmabrg_handlers[dmairq].data = data;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	dmabrg_enable_irq(dmairq);
1378c2ecf20Sopenharmony_ci	return 0;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dmabrg_request_irq);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_civoid dmabrg_free_irq(unsigned int dmairq)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	if (likely(dmairq < 10)) {
1448c2ecf20Sopenharmony_ci		dmabrg_disable_irq(dmairq);
1458c2ecf20Sopenharmony_ci		dmabrg_handlers[dmairq].handler = NULL;
1468c2ecf20Sopenharmony_ci		dmabrg_handlers[dmairq].data = NULL;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dmabrg_free_irq);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic int __init dmabrg_init(void)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	unsigned long or;
1548c2ecf20Sopenharmony_ci	int ret;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	dmabrg_handlers = kcalloc(10, sizeof(struct dmabrg_handler),
1578c2ecf20Sopenharmony_ci				  GFP_KERNEL);
1588c2ecf20Sopenharmony_ci	if (!dmabrg_handlers)
1598c2ecf20Sopenharmony_ci		return -ENOMEM;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci#ifdef CONFIG_SH_DMA
1628c2ecf20Sopenharmony_ci	/* request DMAC channel 0 before anyone else can get it */
1638c2ecf20Sopenharmony_ci	ret = request_dma(0, "DMAC 0 (DMABRG)");
1648c2ecf20Sopenharmony_ci	if (ret < 0)
1658c2ecf20Sopenharmony_ci		printk(KERN_INFO "DMABRG: DMAC ch0 not reserved!\n");
1668c2ecf20Sopenharmony_ci#endif
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	__raw_writel(0, DMABRGCR);
1698c2ecf20Sopenharmony_ci	__raw_writel(0, DMACHCR0);
1708c2ecf20Sopenharmony_ci	__raw_writel(0x94000000, DMARSRA);	/* enable DMABRG in DMAC 0 */
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	/* enable DMABRG mode, enable the DMAC */
1738c2ecf20Sopenharmony_ci	or = __raw_readl(DMAOR);
1748c2ecf20Sopenharmony_ci	__raw_writel(or | DMAOR_BRG | DMAOR_DMEN, DMAOR);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	ret = request_irq(DMABRGI0, dmabrg_irq, 0,
1778c2ecf20Sopenharmony_ci			"DMABRG USB address error", NULL);
1788c2ecf20Sopenharmony_ci	if (ret)
1798c2ecf20Sopenharmony_ci		goto out0;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	ret = request_irq(DMABRGI1, dmabrg_irq, 0,
1828c2ecf20Sopenharmony_ci			"DMABRG Transfer End", NULL);
1838c2ecf20Sopenharmony_ci	if (ret)
1848c2ecf20Sopenharmony_ci		goto out1;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	ret = request_irq(DMABRGI2, dmabrg_irq, 0,
1878c2ecf20Sopenharmony_ci			"DMABRG Transfer Half", NULL);
1888c2ecf20Sopenharmony_ci	if (ret == 0)
1898c2ecf20Sopenharmony_ci		return ret;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	free_irq(DMABRGI1, NULL);
1928c2ecf20Sopenharmony_ciout1:	free_irq(DMABRGI0, NULL);
1938c2ecf20Sopenharmony_ciout0:	kfree(dmabrg_handlers);
1948c2ecf20Sopenharmony_ci	return ret;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_cisubsys_initcall(dmabrg_init);
197