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