diff options
Diffstat (limited to 'firmware/target/arm/s3c2440/gigabeat-fx')
-rw-r--r-- | firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c | 374 |
1 files changed, 150 insertions, 224 deletions
diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c index 57873faaff..a38b4e424e 100644 --- a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c +++ b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c | |||
@@ -25,31 +25,42 @@ | |||
25 | #include "file.h" | 25 | #include "file.h" |
26 | #include "mmu-meg-fx.h" | 26 | #include "mmu-meg-fx.h" |
27 | 27 | ||
28 | /* All exact rates for 16.9344MHz clock */ | ||
28 | #define GIGABEAT_11025HZ (0x19 << 1) | 29 | #define GIGABEAT_11025HZ (0x19 << 1) |
29 | #define GIGABEAT_22050HZ (0x1b << 1) | 30 | #define GIGABEAT_22050HZ (0x1b << 1) |
30 | #define GIGABEAT_44100HZ (0x11 << 1) | 31 | #define GIGABEAT_44100HZ (0x11 << 1) |
31 | #define GIGABEAT_88200HZ (0x1f << 1) | 32 | #define GIGABEAT_88200HZ (0x1f << 1) |
32 | 33 | ||
33 | static int pcm_freq = 0; /* 44.1 is default */ | 34 | /* PCM interrupt routine lockout */ |
35 | static struct | ||
36 | { | ||
37 | int locked; | ||
38 | unsigned long state; | ||
39 | } dma_play_lock = | ||
40 | { | ||
41 | .locked = 0, | ||
42 | .state = (0<<19) | ||
43 | }; | ||
44 | |||
45 | /* Last samplerate set by pcm_set_frequency */ | ||
46 | static unsigned long pcm_freq = 0; /* 44.1 is default */ | ||
47 | /* Samplerate control for audio codec */ | ||
34 | static int sr_ctrl = 0; | 48 | static int sr_ctrl = 0; |
35 | #define FIFO_COUNT ((IISFCON >> 6) & 0x01F) | 49 | |
50 | #define FIFO_COUNT ((IISFCON >> 6) & 0x3F) | ||
36 | 51 | ||
37 | /* Setup for the DMA controller */ | 52 | /* Setup for the DMA controller */ |
38 | #define DMA_CONTROL_SETUP ((1<<31) | (1<<29) | (1<<23) | (1<<22) | (1<<20)) | 53 | #define DMA_CONTROL_SETUP ((1<<31) | (1<<29) | (1<<23) | (1<<22) | (1<<20)) |
39 | 54 | ||
40 | /* DMA count has hit zero - no more data */ | 55 | /* DMA count has hit zero - no more data */ |
41 | /* Get more data from the callback and top off the FIFO */ | 56 | /* Get more data from the callback and top off the FIFO */ |
42 | /* Uses explicitly coded prologue/epilogue code to get around complier bugs | 57 | void fiq_handler(void) __attribute__((interrupt ("FIQ"))); |
43 | in order to be able to use the stack */ | ||
44 | void fiq_handler(void) __attribute__((naked)); | ||
45 | 58 | ||
46 | static void _pcm_apply_settings(void) | 59 | static void _pcm_apply_settings(void) |
47 | { | 60 | { |
48 | static int last_freqency = 0; | 61 | if (pcm_freq != pcm_curr_sampr) |
49 | |||
50 | if (pcm_freq != last_freqency) | ||
51 | { | 62 | { |
52 | last_freqency = pcm_freq; | 63 | pcm_curr_sampr = pcm_freq; |
53 | audiohw_set_frequency(sr_ctrl); | 64 | audiohw_set_frequency(sr_ctrl); |
54 | } | 65 | } |
55 | } | 66 | } |
@@ -61,29 +72,50 @@ void pcm_apply_settings(void) | |||
61 | set_fiq_status(oldstatus); | 72 | set_fiq_status(oldstatus); |
62 | } | 73 | } |
63 | 74 | ||
64 | void pcm_init(void) | 75 | /* For the locks, DMA interrupt must be disabled because the handler |
76 | manipulates INTMSK and the operation is not atomic */ | ||
77 | void pcm_play_lock(void) | ||
78 | { | ||
79 | int status = set_fiq_status(FIQ_DISABLED); | ||
80 | if (++dma_play_lock.locked == 1) | ||
81 | INTMSK |= (1<<19); /* Mask the DMA interrupt */ | ||
82 | set_fiq_status(status); | ||
83 | } | ||
84 | |||
85 | void pcm_play_unlock(void) | ||
65 | { | 86 | { |
66 | pcm_playing = false; | 87 | int status = set_fiq_status(FIQ_DISABLED); |
67 | pcm_paused = false; | 88 | if (--dma_play_lock.locked == 0) |
68 | pcm_callback_for_more = NULL; | 89 | INTMSK &= ~dma_play_lock.state; /* Unmask the DMA interrupt if enabled */ |
90 | set_fiq_status(status); | ||
91 | } | ||
69 | 92 | ||
93 | void pcm_play_dma_init(void) | ||
94 | { | ||
70 | pcm_set_frequency(SAMPR_44); | 95 | pcm_set_frequency(SAMPR_44); |
71 | 96 | ||
72 | /* slave */ | 97 | /* slave */ |
73 | IISMOD |= (1<<8); | 98 | IISMOD |= (1<<8); |
74 | 99 | ||
100 | /* RX,TX off,idle */ | ||
101 | IISCON |= (1<<3) | (1<<2); | ||
102 | |||
75 | audiohw_init(); | 103 | audiohw_init(); |
76 | 104 | ||
77 | /* init GPIO */ | 105 | /* init GPIO */ |
78 | GPCCON = (GPCCON & ~(3<<14)) | (1<<14); | 106 | GPCCON = (GPCCON & ~(3<<14)) | (1<<14); |
79 | GPCDAT |= 1<<7; | 107 | GPCDAT |= (1<<7); |
80 | GPECON |= 0x2aa; | 108 | /* GPE4=I2SDO, GPE3=I2SDI, GPE2=CDCLK, GPE1=I2SSCLK, GPE0=I2SLRCK */ |
109 | GPECON = (GPECON & ~0x3ff) | 0x2aa; | ||
81 | 110 | ||
82 | /* Do not service DMA requests, yet */ | 111 | /* Do not service DMA requests, yet */ |
112 | |||
83 | /* clear any pending int and mask it */ | 113 | /* clear any pending int and mask it */ |
84 | INTMSK |= (1<<19); /* mask the interrupt */ | 114 | INTMSK |= (1<<19); |
85 | SRCPND = (1<<19); /* clear any pending interrupts */ | 115 | SRCPND = (1<<19); |
86 | INTMOD |= (1<<19); /* connect to FIQ */ | 116 | |
117 | /* connect to FIQ */ | ||
118 | INTMOD |= (1<<19); | ||
87 | } | 119 | } |
88 | 120 | ||
89 | void pcm_postinit(void) | 121 | void pcm_postinit(void) |
@@ -92,21 +124,69 @@ void pcm_postinit(void) | |||
92 | pcm_apply_settings(); | 124 | pcm_apply_settings(); |
93 | } | 125 | } |
94 | 126 | ||
95 | void pcm_play_dma_start(const void *addr, size_t size) | 127 | /* Connect the DMA and start filling the FIFO */ |
128 | static void play_start_pcm(void) | ||
129 | { | ||
130 | /* clear pending DMA interrupt */ | ||
131 | SRCPND = (1<<19); | ||
132 | |||
133 | _pcm_apply_settings(); | ||
134 | |||
135 | /* Flush any pending writes */ | ||
136 | clean_dcache_range((void*)DISRC2, (DCON2 & 0xFFFFF) * 2); | ||
137 | |||
138 | /* unmask DMA interrupt when unlocking */ | ||
139 | dma_play_lock.state = (1<<19); | ||
140 | |||
141 | /* turn on the request */ | ||
142 | IISCON |= (1<<5); | ||
143 | |||
144 | /* Activate the channel */ | ||
145 | DMASKTRIG2 = 0x2; | ||
146 | |||
147 | /* turn off the idle */ | ||
148 | IISCON &= ~(1<<3); | ||
149 | |||
150 | /* start the IIS */ | ||
151 | IISCON |= (1<<0); | ||
152 | } | ||
153 | |||
154 | /* Disconnect the DMA and wait for the FIFO to clear */ | ||
155 | static void play_stop_pcm(void) | ||
96 | { | 156 | { |
97 | addr = (void *)((unsigned long)addr & ~3); /* Align data */ | 157 | /* Mask DMA interrupt */ |
98 | size &= ~3; /* Size must be multiple of 4 */ | 158 | INTMSK |= (1<<19); |
159 | |||
160 | /* De-Activate the DMA channel */ | ||
161 | DMASKTRIG2 = 0x4; | ||
162 | |||
163 | /* are we playing? wait for the chunk to finish */ | ||
164 | if (dma_play_lock.state != 0) | ||
165 | { | ||
166 | /* wait for the FIFO to empty and DMA to stop */ | ||
167 | while ((IISCON & (1<<7)) || (DMASKTRIG2 & 0x2)); | ||
168 | } | ||
169 | |||
170 | /* Keep interrupt masked when unlocking */ | ||
171 | dma_play_lock.state = 0; | ||
172 | |||
173 | /* turn off the request */ | ||
174 | IISCON &= ~(1<<5); | ||
99 | 175 | ||
100 | /* sanity check: bad pointer or too small file */ | 176 | /* turn on the idle */ |
101 | if (NULL == addr || size == 0) return; | 177 | IISCON |= (1<<3); |
102 | 178 | ||
103 | disable_fiq(); | 179 | /* stop the IIS */ |
180 | IISCON &= ~(1<<0); | ||
181 | } | ||
104 | 182 | ||
183 | void pcm_play_dma_start(const void *addr, size_t size) | ||
184 | { | ||
105 | /* Enable the IIS clock */ | 185 | /* Enable the IIS clock */ |
106 | CLKCON |= (1<<17); | 186 | CLKCON |= (1<<17); |
107 | 187 | ||
108 | /* IIS interface setup and set to idle */ | 188 | /* stop any DMA in progress - idle IIS */ |
109 | IISCON = (1<<5) | (1<<3); | 189 | play_stop_pcm(); |
110 | 190 | ||
111 | /* slave, transmit mode, 16 bit samples - MCLK 384fs - use 16.9344Mhz - | 191 | /* slave, transmit mode, 16 bit samples - MCLK 384fs - use 16.9344Mhz - |
112 | BCLK 32fs */ | 192 | BCLK 32fs */ |
@@ -116,155 +196,80 @@ void pcm_play_dma_start(const void *addr, size_t size) | |||
116 | IISFCON = (1<<15) | (1<<13); | 196 | IISFCON = (1<<15) | (1<<13); |
117 | 197 | ||
118 | /* set DMA dest */ | 198 | /* set DMA dest */ |
119 | DIDST2 = (int)&IISFIFO; | 199 | DIDST2 = (unsigned int)&IISFIFO; |
120 | 200 | ||
121 | /* IIS is on the APB bus, INT when TC reaches 0, fixed dest addr */ | 201 | /* IIS is on the APB bus, INT when TC reaches 0, fixed dest addr */ |
122 | DIDSTC2 = 0x03; | 202 | DIDSTC2 = 0x03; |
123 | 203 | ||
204 | /* set DMA source and options */ | ||
205 | DISRC2 = (unsigned int)addr + 0x30000000; | ||
124 | /* How many transfers to make - we transfer half-word at a time = 2 bytes */ | 206 | /* How many transfers to make - we transfer half-word at a time = 2 bytes */ |
125 | /* DMA control: CURR_TC int, single service mode, I2SSDO int, HW trig */ | 207 | /* DMA control: CURR_TC int, single service mode, I2SSDO int, HW trig */ |
126 | /* no auto-reload, half-word (16bit) */ | 208 | /* no auto-reload, half-word (16bit) */ |
127 | DCON2 = DMA_CONTROL_SETUP | (size / 2); | 209 | DCON2 = DMA_CONTROL_SETUP | (size / 2); |
128 | |||
129 | /* set DMA source and options */ | ||
130 | DISRC2 = (unsigned long)addr + 0x30000000; | ||
131 | DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */ | 210 | DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */ |
132 | 211 | ||
133 | /* clear pending DMA interrupt */ | 212 | play_start_pcm(); |
134 | SRCPND = 1<<19; | 213 | } |
135 | |||
136 | pcm_playing = true; | ||
137 | |||
138 | _pcm_apply_settings(); | ||
139 | |||
140 | /* unmask the DMA interrupt */ | ||
141 | INTMSK &= ~(1<<19); | ||
142 | |||
143 | /* Flush any pending writes */ | ||
144 | clean_dcache_range(addr, size); | ||
145 | |||
146 | /* Activate the channel */ | ||
147 | DMASKTRIG2 = 0x2; | ||
148 | |||
149 | /* turn off the idle */ | ||
150 | IISCON &= ~(1<<3); | ||
151 | 214 | ||
152 | /* start the IIS */ | 215 | /* Promptly stop DMA transfers and stop IIS */ |
153 | IISCON |= (1<<0); | 216 | void pcm_play_dma_stop(void) |
217 | { | ||
218 | play_stop_pcm(); | ||
154 | 219 | ||
155 | enable_fiq(); | 220 | /* Disconnect the IIS clock */ |
221 | CLKCON &= ~(1<<17); | ||
156 | } | 222 | } |
157 | 223 | ||
158 | static void pcm_play_dma_stop_fiq(void) | 224 | void pcm_play_dma_pause(bool pause) |
159 | { | 225 | { |
160 | INTMSK |= (1<<19); /* mask the DMA interrupt */ | 226 | if (pause) |
161 | IISCON &= ~(1<<5); /* disable fifo request */ | ||
162 | DMASKTRIG2 = 0x4; /* De-Activate the DMA channel */ | ||
163 | |||
164 | /* are we playing? wait for the chunk to finish */ | ||
165 | if (pcm_playing) | ||
166 | { | 227 | { |
167 | /* wait for the FIFO to empty before turning things off */ | 228 | /* pause playback on current buffer */ |
168 | while (IISCON & (1<<7)) ; | 229 | play_stop_pcm(); |
169 | 230 | } | |
170 | pcm_playing = false; | 231 | else |
171 | if (!audio_status()) | 232 | { |
172 | pcm_paused = false; | 233 | /* restart playback on current buffer */ |
234 | /* make sure we're aligned on left channel - skip any right | ||
235 | channel sample left waiting */ | ||
236 | DISRC2 = (DCSRC2 + 2) & ~0x3; | ||
237 | DCON2 = DMA_CONTROL_SETUP | (DSTAT2 & 0xFFFFE); | ||
238 | play_start_pcm(); | ||
173 | } | 239 | } |
174 | |||
175 | /* Disconnect the IIS clock */ | ||
176 | CLKCON &= ~(1<<17); | ||
177 | } | 240 | } |
178 | 241 | ||
179 | void fiq_handler(void) | 242 | void fiq_handler(void) |
180 | { | 243 | { |
181 | /* r0-r7 are probably not all used by GCC but there's no way to know | 244 | static unsigned char *start; |
182 | otherwise this whole thing must be assembly */ | 245 | static size_t size; |
183 | asm volatile ("stmfd sp!, {r0-r7, ip, lr} \n" /* Store context */ | ||
184 | "sub sp, sp, #8 \n"); /* Reserve stack */ | ||
185 | register pcm_more_callback_type get_more; /* No stack for this */ | 246 | register pcm_more_callback_type get_more; /* No stack for this */ |
186 | unsigned char *next_start; /* sp + #0 */ | ||
187 | size_t next_size; /* sp + #4 */ | ||
188 | 247 | ||
189 | /* clear any pending interrupt */ | 248 | /* clear any pending interrupt */ |
190 | SRCPND = (1<<19); | 249 | SRCPND = (1<<19); |
191 | 250 | ||
192 | /* Buffer empty. Try to get more. */ | 251 | /* Buffer empty. Try to get more. */ |
193 | get_more = pcm_callback_for_more; | 252 | get_more = pcm_callback_for_more; |
194 | if (get_more == NULL) | 253 | size = 0; |
195 | { | ||
196 | /* Callback missing */ | ||
197 | pcm_play_dma_stop_fiq(); | ||
198 | goto fiq_exit; | ||
199 | } | ||
200 | |||
201 | next_size = 0; | ||
202 | get_more(&next_start, &next_size); | ||
203 | 254 | ||
204 | if (next_size == 0) | 255 | if (get_more == NULL || (get_more(&start, &size), size == 0)) |
205 | { | 256 | { |
206 | /* No more DMA to do */ | 257 | /* Callback missing or no more DMA to do */ |
207 | pcm_play_dma_stop_fiq(); | 258 | pcm_play_dma_stop(); |
208 | goto fiq_exit; | 259 | pcm_play_dma_stopped_callback(); |
209 | } | 260 | } |
210 | 261 | else | |
211 | /* Flush any pending cache writes */ | ||
212 | clean_dcache_range(next_start, next_size); | ||
213 | |||
214 | /* set the new DMA values */ | ||
215 | DCON2 = DMA_CONTROL_SETUP | (next_size >> 1); | ||
216 | DISRC2 = (unsigned long)next_start + 0x30000000; | ||
217 | |||
218 | /* Re-Activate the channel */ | ||
219 | DMASKTRIG2 = 0x2; | ||
220 | |||
221 | fiq_exit: | ||
222 | asm volatile("add sp, sp, #8 \n" /* Cleanup stack */ | ||
223 | "ldmfd sp!, {r0-r7, ip, lr} \n" /* Restore context */ | ||
224 | "subs pc, lr, #4 \n"); /* Return from FIQ */ | ||
225 | } | ||
226 | |||
227 | /* Disconnect the DMA and wait for the FIFO to clear */ | ||
228 | void pcm_play_dma_stop(void) | ||
229 | { | ||
230 | disable_fiq(); | ||
231 | pcm_play_dma_stop_fiq(); | ||
232 | } | ||
233 | |||
234 | void pcm_play_pause_pause(void) | ||
235 | { | ||
236 | /* stop servicing refills */ | ||
237 | int oldstatus = set_fiq_status(FIQ_DISABLED); | ||
238 | INTMSK |= (1<<19); /* mask interrupt request */ | ||
239 | IISCON &= ~(1<<5); /* turn off FIFO request */ | ||
240 | DMASKTRIG2 = 0x4; /* stop DMA at end of atomic transfer */ | ||
241 | |||
242 | if (pcm_playing) | ||
243 | { | 262 | { |
244 | /* playing - wait for the FIFO to empty */ | 263 | /* Flush any pending cache writes */ |
245 | while (IISCON & (1<<7)) ; | 264 | clean_dcache_range(start, size); |
246 | } | ||
247 | 265 | ||
248 | set_fiq_status(oldstatus); | 266 | /* set the new DMA values */ |
249 | } | 267 | DCON2 = DMA_CONTROL_SETUP | (size >> 1); |
268 | DISRC2 = (unsigned int)start + 0x30000000; | ||
250 | 269 | ||
251 | void pcm_play_pause_unpause(void) | 270 | /* Re-Activate the channel */ |
252 | { | 271 | DMASKTRIG2 = 0x2; |
253 | /* refill buffer and keep going */ | ||
254 | int oldstatus = set_fiq_status(FIQ_DISABLED); | ||
255 | _pcm_apply_settings(); | ||
256 | if (pcm_playing) | ||
257 | { | ||
258 | /* make sure we're aligned on left channel - skip any right channel | ||
259 | sample left waiting */ | ||
260 | DISRC2 = (DCSRC2 + 2) & ~0x3; | ||
261 | DCON2 = (DSTAT2 & 0xFFFFE); | ||
262 | |||
263 | SRCPND = (1<<19); /* clear pending DMA interrupt */ | ||
264 | INTMSK &= ~(1<<19); /* unmask interrupt request */ | ||
265 | IISCON |= (1<<5); /* enable FIFO request */ | ||
266 | } | 272 | } |
267 | set_fiq_status(oldstatus); | ||
268 | } | 273 | } |
269 | 274 | ||
270 | void pcm_set_frequency(unsigned int frequency) | 275 | void pcm_set_frequency(unsigned int frequency) |
@@ -296,89 +301,10 @@ size_t pcm_get_bytes_waiting(void) | |||
296 | return (DSTAT2 & 0xFFFFE) * 2; | 301 | return (DSTAT2 & 0xFFFFE) * 2; |
297 | } | 302 | } |
298 | 303 | ||
299 | /** **/ | 304 | const void * pcm_play_dma_get_peak_buffer(int *count) |
300 | |||
301 | void pcm_mute(bool mute) | ||
302 | { | 305 | { |
303 | audiohw_mute(mute); | 306 | unsigned long addr = DCSRC2; |
304 | if (mute) | 307 | int cnt = DSTAT2; |
305 | sleep(HZ/16); | 308 | *count = (cnt & 0xFFFFF) >> 1; |
309 | return (void *)((addr + 2) & ~3); | ||
306 | } | 310 | } |
307 | |||
308 | /** | ||
309 | * Return playback peaks - Peaks ahead in the DMA buffer based upon the | ||
310 | * calling period to attempt to compensate for | ||
311 | * delay. | ||
312 | */ | ||
313 | void pcm_calculate_peaks(int *left, int *right) | ||
314 | { | ||
315 | static unsigned long last_peak_tick = 0; | ||
316 | static unsigned long frame_period = 0; | ||
317 | static int peaks_l = 0, peaks_r = 0; | ||
318 | |||
319 | /* Throttled peak ahead based on calling period */ | ||
320 | unsigned long period = current_tick - last_peak_tick; | ||
321 | |||
322 | /* Keep reasonable limits on period */ | ||
323 | if (period < 1) | ||
324 | period = 1; | ||
325 | else if (period > HZ/5) | ||
326 | period = HZ/5; | ||
327 | |||
328 | frame_period = (3*frame_period + period) >> 2; | ||
329 | |||
330 | last_peak_tick = current_tick; | ||
331 | |||
332 | if (pcm_playing && !pcm_paused) | ||
333 | { | ||
334 | unsigned long *addr = (unsigned long *)DCSRC2; | ||
335 | long samples = DSTAT2; | ||
336 | long samp_frames; | ||
337 | |||
338 | addr = (unsigned long *)((unsigned long)addr & ~3); | ||
339 | samples &= 0xFFFFE; | ||
340 | samp_frames = frame_period*pcm_freq/(HZ/2); | ||
341 | samples = MIN(samp_frames, samples) >> 1; | ||
342 | |||
343 | if (samples > 0) | ||
344 | { | ||
345 | long peak_l = 0, peak_r = 0; | ||
346 | long peaksq_l = 0, peaksq_r = 0; | ||
347 | |||
348 | addr -= 0x30000000 >> 2; | ||
349 | addr = (long *)((long)addr & ~3); | ||
350 | |||
351 | do | ||
352 | { | ||
353 | long value = *addr; | ||
354 | long ch, chsq; | ||
355 | |||
356 | ch = (int16_t)value; | ||
357 | chsq = ch*ch; | ||
358 | if (chsq > peaksq_l) | ||
359 | peak_l = ch, peaksq_l = chsq; | ||
360 | |||
361 | ch = value >> 16; | ||
362 | chsq = ch*ch; | ||
363 | if (chsq > peaksq_r) | ||
364 | peak_r = ch, peaksq_r = chsq; | ||
365 | |||
366 | addr += 4; | ||
367 | } | ||
368 | while ((samples -= 4) > 0); | ||
369 | |||
370 | peaks_l = abs(peak_l); | ||
371 | peaks_r = abs(peak_r); | ||
372 | } | ||
373 | } | ||
374 | else | ||
375 | { | ||
376 | peaks_l = peaks_r = 0; | ||
377 | } | ||
378 | |||
379 | if (left) | ||
380 | *left = peaks_l; | ||
381 | |||
382 | if (right) | ||
383 | *right = peaks_r; | ||
384 | } /* pcm_calculate_peaks */ | ||