| Manuel Lauss | fc467a2 | 2007-05-09 17:36:15 +0900 | [diff] [blame] | 1 | /* | 
|  | 2 | * SH7760 DMABRG IRQ handling | 
|  | 3 | * | 
|  | 4 | * (c) 2007 MSC Vertriebsges.m.b.H, Manuel Lauss <mlau@msc-ge.com> | 
|  | 5 | *  licensed under the GPLv2. | 
|  | 6 | * | 
|  | 7 | */ | 
|  | 8 |  | 
|  | 9 | #include <linux/interrupt.h> | 
|  | 10 | #include <linux/kernel.h> | 
|  | 11 | #include <asm/dma.h> | 
|  | 12 | #include <asm/dmabrg.h> | 
|  | 13 | #include <asm/io.h> | 
|  | 14 |  | 
|  | 15 | /* | 
|  | 16 | * The DMABRG is a special DMA unit within the SH7760. It does transfers | 
|  | 17 | * from USB-SRAM/Audio units to main memory (and also the LCDC; but that | 
|  | 18 | * part is sensibly placed  in the LCDC  registers and requires no irqs) | 
|  | 19 | * It has 3 IRQ lines which trigger 10 events, and works independently | 
|  | 20 | * from the traditional SH DMAC (although it blocks usage of DMAC 0) | 
|  | 21 | * | 
|  | 22 | * BRGIRQID   | component | dir | meaning      | source | 
|  | 23 | * ----------------------------------------------------- | 
|  | 24 | *     0      | USB-DMA   | ... | xfer done    | DMABRGI1 | 
|  | 25 | *     1      | USB-UAE   | ... | USB addr err.| DMABRGI0 | 
|  | 26 | *     2      | HAC0/SSI0 | play| all done     | DMABRGI1 | 
|  | 27 | *     3      | HAC0/SSI0 | play| half done    | DMABRGI2 | 
|  | 28 | *     4      | HAC0/SSI0 | rec | all done     | DMABRGI1 | 
|  | 29 | *     5      | HAC0/SSI0 | rec | half done    | DMABRGI2 | 
|  | 30 | *     6      | HAC1/SSI1 | play| all done     | DMABRGI1 | 
|  | 31 | *     7      | HAC1/SSI1 | play| half done    | DMABRGI2 | 
|  | 32 | *     8      | HAC1/SSI1 | rec | all done     | DMABRGI1 | 
|  | 33 | *     9      | HAC1/SSI1 | rec | half done    | DMABRGI2 | 
|  | 34 | * | 
|  | 35 | * all can be enabled/disabled in the DMABRGCR register, | 
| Simon Arlott | e868d61 | 2007-05-14 08:15:10 +0900 | [diff] [blame] | 36 | * as well as checked if they occurred. | 
| Manuel Lauss | fc467a2 | 2007-05-09 17:36:15 +0900 | [diff] [blame] | 37 | * | 
|  | 38 | * DMABRGI0 services  USB  DMA  Address  errors,  but it still must be | 
|  | 39 | * enabled/acked in the DMABRGCR register.  USB-DMA complete indicator | 
|  | 40 | * is grouped together with the audio buffer end indicators, too bad... | 
|  | 41 | * | 
|  | 42 | * DMABRGCR:	Bits 31-24: audio-dma ENABLE flags, | 
|  | 43 | *		Bits 23-16: audio-dma STATUS flags, | 
|  | 44 | *		Bits  9-8:  USB error/xfer ENABLE, | 
|  | 45 | *		Bits  1-0:  USB error/xfer STATUS. | 
|  | 46 | *	Ack an IRQ by writing 0 to the STATUS flag. | 
|  | 47 | *	Mask IRQ by writing 0 to ENABLE flag. | 
|  | 48 | * | 
|  | 49 | * Usage is almost like with any other IRQ: | 
|  | 50 | *  dmabrg_request_irq(BRGIRQID, handler, data) | 
|  | 51 | *  dmabrg_free_irq(BRGIRQID) | 
|  | 52 | * | 
|  | 53 | * handler prototype:  void brgirqhandler(void *data) | 
|  | 54 | */ | 
|  | 55 |  | 
|  | 56 | #define DMARSRA		0xfe090000 | 
|  | 57 | #define DMAOR		0xffa00040 | 
|  | 58 | #define DMACHCR0	0xffa0000c | 
|  | 59 | #define DMABRGCR	0xfe3c0000 | 
|  | 60 |  | 
|  | 61 | #define DMAOR_BRG	0x0000c000 | 
|  | 62 | #define DMAOR_DMEN	0x00000001 | 
|  | 63 |  | 
|  | 64 | #define DMABRGI0	68 | 
|  | 65 | #define DMABRGI1	69 | 
|  | 66 | #define DMABRGI2	70 | 
|  | 67 |  | 
|  | 68 | struct dmabrg_handler { | 
|  | 69 | void (*handler)(void *); | 
|  | 70 | void *data; | 
|  | 71 | } *dmabrg_handlers; | 
|  | 72 |  | 
|  | 73 | static inline void dmabrg_call_handler(int i) | 
|  | 74 | { | 
|  | 75 | dmabrg_handlers[i].handler(dmabrg_handlers[i].data); | 
|  | 76 | } | 
|  | 77 |  | 
|  | 78 | /* | 
|  | 79 | * main DMABRG irq handler. It acks irqs and then | 
|  | 80 | * handles every set and unmasked bit sequentially. | 
|  | 81 | * No locking and no validity checks; it should be | 
|  | 82 | * as fast as possible (audio!) | 
|  | 83 | */ | 
|  | 84 | static irqreturn_t dmabrg_irq(int irq, void *data) | 
|  | 85 | { | 
|  | 86 | unsigned long dcr; | 
|  | 87 | unsigned int i; | 
|  | 88 |  | 
|  | 89 | dcr = ctrl_inl(DMABRGCR); | 
|  | 90 | ctrl_outl(dcr & ~0x00ff0003, DMABRGCR);	/* ack all */ | 
|  | 91 | dcr &= dcr >> 8;	/* ignore masked */ | 
|  | 92 |  | 
|  | 93 | /* USB stuff, get it out of the way first */ | 
|  | 94 | if (dcr & 1) | 
|  | 95 | dmabrg_call_handler(DMABRGIRQ_USBDMA); | 
|  | 96 | if (dcr & 2) | 
|  | 97 | dmabrg_call_handler(DMABRGIRQ_USBDMAERR); | 
|  | 98 |  | 
|  | 99 | /* Audio */ | 
|  | 100 | dcr >>= 16; | 
|  | 101 | while (dcr) { | 
|  | 102 | i = __ffs(dcr); | 
|  | 103 | dcr &= dcr - 1; | 
|  | 104 | dmabrg_call_handler(i + DMABRGIRQ_A0TXF); | 
|  | 105 | } | 
|  | 106 | return IRQ_HANDLED; | 
|  | 107 | } | 
|  | 108 |  | 
|  | 109 | static void dmabrg_disable_irq(unsigned int dmairq) | 
|  | 110 | { | 
|  | 111 | unsigned long dcr; | 
|  | 112 | dcr = ctrl_inl(DMABRGCR); | 
|  | 113 | dcr &= ~(1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8)); | 
|  | 114 | ctrl_outl(dcr, DMABRGCR); | 
|  | 115 | } | 
|  | 116 |  | 
|  | 117 | static void dmabrg_enable_irq(unsigned int dmairq) | 
|  | 118 | { | 
|  | 119 | unsigned long dcr; | 
|  | 120 | dcr = ctrl_inl(DMABRGCR); | 
|  | 121 | dcr |= (1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8)); | 
|  | 122 | ctrl_outl(dcr, DMABRGCR); | 
|  | 123 | } | 
|  | 124 |  | 
|  | 125 | int dmabrg_request_irq(unsigned int dmairq, void(*handler)(void*), | 
|  | 126 | void *data) | 
|  | 127 | { | 
|  | 128 | if ((dmairq > 9) || !handler) | 
|  | 129 | return -ENOENT; | 
|  | 130 | if (dmabrg_handlers[dmairq].handler) | 
|  | 131 | return -EBUSY; | 
|  | 132 |  | 
|  | 133 | dmabrg_handlers[dmairq].handler = handler; | 
|  | 134 | dmabrg_handlers[dmairq].data = data; | 
|  | 135 |  | 
|  | 136 | dmabrg_enable_irq(dmairq); | 
|  | 137 | return 0; | 
|  | 138 | } | 
|  | 139 | EXPORT_SYMBOL_GPL(dmabrg_request_irq); | 
|  | 140 |  | 
|  | 141 | void dmabrg_free_irq(unsigned int dmairq) | 
|  | 142 | { | 
|  | 143 | if (likely(dmairq < 10)) { | 
|  | 144 | dmabrg_disable_irq(dmairq); | 
|  | 145 | dmabrg_handlers[dmairq].handler = NULL; | 
|  | 146 | dmabrg_handlers[dmairq].data = NULL; | 
|  | 147 | } | 
|  | 148 | } | 
|  | 149 | EXPORT_SYMBOL_GPL(dmabrg_free_irq); | 
|  | 150 |  | 
|  | 151 | static int __init dmabrg_init(void) | 
|  | 152 | { | 
|  | 153 | unsigned long or; | 
|  | 154 | int ret; | 
|  | 155 |  | 
|  | 156 | dmabrg_handlers = kzalloc(10 * sizeof(struct dmabrg_handler), | 
|  | 157 | GFP_KERNEL); | 
|  | 158 | if (!dmabrg_handlers) | 
|  | 159 | return -ENOMEM; | 
|  | 160 |  | 
|  | 161 | #ifdef CONFIG_SH_DMA | 
|  | 162 | /* request DMAC channel 0 before anyone else can get it */ | 
|  | 163 | ret = request_dma(0, "DMAC 0 (DMABRG)"); | 
|  | 164 | if (ret < 0) | 
|  | 165 | printk(KERN_INFO "DMABRG: DMAC ch0 not reserved!\n"); | 
|  | 166 | #endif | 
|  | 167 |  | 
|  | 168 | ctrl_outl(0, DMABRGCR); | 
|  | 169 | ctrl_outl(0, DMACHCR0); | 
|  | 170 | ctrl_outl(0x94000000, DMARSRA);	/* enable DMABRG in DMAC 0 */ | 
|  | 171 |  | 
|  | 172 | /* enable DMABRG mode, enable the DMAC */ | 
|  | 173 | or = ctrl_inl(DMAOR); | 
|  | 174 | ctrl_outl(or | DMAOR_BRG | DMAOR_DMEN, DMAOR); | 
|  | 175 |  | 
|  | 176 | ret = request_irq(DMABRGI0, dmabrg_irq, IRQF_DISABLED, | 
|  | 177 | "DMABRG USB address error", NULL); | 
|  | 178 | if (ret) | 
|  | 179 | goto out0; | 
|  | 180 |  | 
|  | 181 | ret = request_irq(DMABRGI1, dmabrg_irq, IRQF_DISABLED, | 
|  | 182 | "DMABRG Transfer End", NULL); | 
|  | 183 | if (ret) | 
|  | 184 | goto out1; | 
|  | 185 |  | 
|  | 186 | ret = request_irq(DMABRGI2, dmabrg_irq, IRQF_DISABLED, | 
|  | 187 | "DMABRG Transfer Half", NULL); | 
|  | 188 | if (ret == 0) | 
|  | 189 | return ret; | 
|  | 190 |  | 
|  | 191 | free_irq(DMABRGI1, 0); | 
|  | 192 | out1:	free_irq(DMABRGI0, 0); | 
|  | 193 | out0:	kfree(dmabrg_handlers); | 
|  | 194 | return ret; | 
|  | 195 | } | 
|  | 196 | subsys_initcall(dmabrg_init); |