diff options
Diffstat (limited to 'firmware/target/arm/s5l8702/pcm-s5l8702.c')
-rw-r--r-- | firmware/target/arm/s5l8702/pcm-s5l8702.c | 180 |
1 files changed, 101 insertions, 79 deletions
diff --git a/firmware/target/arm/s5l8702/pcm-s5l8702.c b/firmware/target/arm/s5l8702/pcm-s5l8702.c index 1048354ec5..a4ea56edce 100644 --- a/firmware/target/arm/s5l8702/pcm-s5l8702.c +++ b/firmware/target/arm/s5l8702/pcm-s5l8702.c | |||
@@ -31,104 +31,129 @@ | |||
31 | #include "pcm_sampr.h" | 31 | #include "pcm_sampr.h" |
32 | #include "mmu-arm.h" | 32 | #include "mmu-arm.h" |
33 | #include "pcm-target.h" | 33 | #include "pcm-target.h" |
34 | #include "dma-s5l8702.h" | ||
35 | |||
36 | /* DMA configuration */ | ||
37 | |||
38 | /* 3 DMA tasks needed, one chunk task and two dblbuf tasks */ | ||
39 | #define DMA_PLAY_TSKBUF_SZ 4 /* N tasks, MUST be pow2 */ | ||
40 | #define DMA_PLAY_LLIBUF_SZ 4 /* N LLIs, MUST be pow2 */ | ||
41 | |||
42 | static struct dmac_tsk dma_play_tskbuf[DMA_PLAY_TSKBUF_SZ]; | ||
43 | static struct dmac_lli volatile \ | ||
44 | dma_play_llibuf[DMA_PLAY_LLIBUF_SZ] CACHEALIGN_ATTR; | ||
45 | |||
46 | static void dma_play_callback(void *data) ICODE_ATTR; | ||
47 | |||
48 | static struct dmac_ch dma_play_ch = { | ||
49 | .dmac = &s5l8702_dmac0, | ||
50 | .prio = DMAC_CH_PRIO(2), | ||
51 | .cb_fn = dma_play_callback, | ||
52 | |||
53 | .tskbuf = dma_play_tskbuf, | ||
54 | .tskbuf_mask = DMA_PLAY_TSKBUF_SZ - 1, | ||
55 | .queue_mode = QUEUE_LINK, | ||
56 | |||
57 | .llibuf = dma_play_llibuf, | ||
58 | .llibuf_mask = DMA_PLAY_LLIBUF_SZ - 1, | ||
59 | .llibuf_bus = DMAC_MASTER_AHB1, | ||
60 | }; | ||
61 | |||
62 | static struct dmac_ch_cfg dma_play_ch_cfg = { | ||
63 | .srcperi = S5L8702_DMAC0_PERI_MEM, | ||
64 | .dstperi = S5L8702_DMAC0_PERI_IIS0_TX, | ||
65 | .sbsize = DMACCxCONTROL_BSIZE_8, | ||
66 | .dbsize = DMACCxCONTROL_BSIZE_4, | ||
67 | .swidth = DMACCxCONTROL_WIDTH_16, | ||
68 | .dwidth = DMACCxCONTROL_WIDTH_16, | ||
69 | .sbus = DMAC_MASTER_AHB1, | ||
70 | .dbus = DMAC_MASTER_AHB1, | ||
71 | .sinc = DMACCxCONTROL_INC_ENABLE, | ||
72 | .dinc = DMACCxCONTROL_INC_DISABLE, | ||
73 | .prot = DMAC_PROT_CACH | DMAC_PROT_BUFF | DMAC_PROT_PRIV, | ||
74 | /* align LLI transfers to L-R pairs (samples) */ | ||
75 | .lli_xfer_max_count = DMAC_LLI_MAX_COUNT & ~1, | ||
76 | }; | ||
77 | #define LLI_MAX_BYTES 8188 /* lli_xfer_max_count << swidth */ | ||
78 | |||
79 | /* Use all available LLIs for chunk */ | ||
80 | /*#define CHUNK_MAX_BYTES (LLI_MAX_BYTES * (DMA_PLAY_LLIBUF_SZ - 2))*/ | ||
81 | #define CHUNK_MAX_BYTES (LLI_MAX_BYTES * 1) | ||
82 | #define WATERMARK_BYTES (PCM_WATERMARK * 4) | ||
34 | 83 | ||
35 | static volatile int locked = 0; | 84 | static volatile int locked = 0; |
36 | static const int zerosample = 0; | 85 | static unsigned char dblbuf[2][WATERMARK_BYTES] CACHEALIGN_ATTR; |
37 | static unsigned char dblbuf[2][PCM_WATERMARK * 4]; | ||
38 | static int active_dblbuf; | 86 | static int active_dblbuf; |
39 | struct dma_lli pcm_lli[PCM_LLICOUNT] __attribute__((aligned(16))); | ||
40 | static struct dma_lli* lastlli; | ||
41 | static const void* dataptr; | ||
42 | size_t pcm_remaining; | 87 | size_t pcm_remaining; |
43 | size_t pcm_chunksize; | ||
44 | 88 | ||
45 | /* Mask the DMA interrupt */ | 89 | /* Mask the DMA interrupt */ |
46 | void pcm_play_lock(void) | 90 | void pcm_play_lock(void) |
47 | { | 91 | { |
48 | if (locked++ == 0) { | 92 | if (locked++ == 0) |
49 | //TODO: Urgh, I don't like that at all... | 93 | dmac_ch_lock_int(&dma_play_ch); |
50 | VIC0INTENCLEAR = 1 << IRQ_DMAC0; | ||
51 | } | ||
52 | } | 94 | } |
53 | 95 | ||
54 | /* Unmask the DMA interrupt if enabled */ | 96 | /* Unmask the DMA interrupt if enabled */ |
55 | void pcm_play_unlock(void) | 97 | void pcm_play_unlock(void) |
56 | { | 98 | { |
57 | if (--locked == 0) { | 99 | if (--locked == 0) |
58 | VIC0INTENABLE = 1 << IRQ_DMAC0; | 100 | dmac_ch_unlock_int(&dma_play_ch); |
59 | } | ||
60 | } | 101 | } |
61 | 102 | ||
62 | void INT_DMAC0C0(void) ICODE_ATTR; | 103 | static inline void play_queue_dma(void *addr, size_t size, void *cb_data) |
63 | void INT_DMAC0C0(void) | ||
64 | { | 104 | { |
65 | DMAC0INTTCCLR = 1; | 105 | commit_dcache_range(addr, size); |
66 | if (!pcm_remaining) | 106 | dmac_ch_queue(&dma_play_ch, addr, |
67 | { | 107 | (void*)S5L8702_DADDR_PERI_IIS0_TX, size, cb_data); |
68 | pcm_play_dma_complete_callback(PCM_DMAST_OK, &dataptr, &pcm_remaining); | 108 | } |
69 | pcm_chunksize = pcm_remaining; | 109 | |
70 | } | 110 | static void dma_play_callback(void *cb_data) |
111 | { | ||
112 | if (!cb_data) | ||
113 | return; /* dblbuf callback entered, nothing to do */ | ||
114 | |||
115 | const void *dataptr = cb_data; | ||
116 | |||
71 | if (!pcm_remaining) | 117 | if (!pcm_remaining) |
72 | { | 118 | if (!pcm_play_dma_complete_callback( |
73 | pcm_lli->nextlli = NULL; | 119 | PCM_DMAST_OK, &dataptr, &pcm_remaining)) |
74 | pcm_lli->control = 0x7524a000; | 120 | return; |
75 | commit_dcache(); | 121 | |
76 | return; | 122 | uint32_t lastsize = MIN(WATERMARK_BYTES, pcm_remaining >> 1); |
77 | } | ||
78 | uint32_t lastsize = MIN(PCM_WATERMARK * 4, pcm_remaining / 2 + 1) & ~1; | ||
79 | pcm_remaining -= lastsize; | 123 | pcm_remaining -= lastsize; |
80 | if (pcm_remaining) lastlli = &pcm_lli[ARRAYLEN(pcm_lli) - 1]; | 124 | uint32_t chunksize = MIN(CHUNK_MAX_BYTES, pcm_remaining); |
81 | else lastlli = pcm_lli; | 125 | |
82 | uint32_t chunksize = MIN(PCM_CHUNKSIZE * 4 - lastsize, pcm_remaining); | 126 | /* last chunk should be at least 2*WATERMARK_BYTES in size */ |
83 | if (pcm_remaining > chunksize && chunksize > pcm_remaining - PCM_WATERMARK * 8) | 127 | if ((pcm_remaining > chunksize) && |
84 | chunksize = pcm_remaining - PCM_WATERMARK * 8; | 128 | (pcm_remaining < chunksize + WATERMARK_BYTES * 2)) |
129 | chunksize = pcm_remaining - WATERMARK_BYTES * 2; | ||
130 | |||
85 | pcm_remaining -= chunksize; | 131 | pcm_remaining -= chunksize; |
86 | bool last = !chunksize; | 132 | |
87 | int i = 0; | 133 | /* first part */ |
88 | while (chunksize) | 134 | play_queue_dma((void*)dataptr, chunksize, |
89 | { | 135 | (void*)dataptr + chunksize + lastsize); /* cb_data */ |
90 | uint32_t thislli = MIN(PCM_LLIMAX * 4, chunksize); | 136 | |
91 | chunksize -= thislli; | 137 | /* second part */ |
92 | pcm_lli[i].srcaddr = (void*)dataptr; | 138 | memcpy(dblbuf[active_dblbuf], dataptr + chunksize, lastsize); |
93 | pcm_lli[i].dstaddr = (void*)((int)&I2STXDB0); | 139 | play_queue_dma(dblbuf[active_dblbuf], lastsize, NULL); |
94 | pcm_lli[i].nextlli = chunksize ? &pcm_lli[i + 1] : lastlli; | 140 | active_dblbuf ^= 1; |
95 | pcm_lli[i].control = (chunksize ? 0x7524a000 : 0xf524a000) | (thislli / 2); | ||
96 | dataptr += thislli; | ||
97 | i++; | ||
98 | } | ||
99 | if (!pcm_remaining) | ||
100 | { | ||
101 | memcpy(dblbuf[active_dblbuf], dataptr, lastsize); | ||
102 | lastlli->srcaddr = dblbuf[active_dblbuf]; | ||
103 | active_dblbuf ^= 1; | ||
104 | } | ||
105 | else lastlli->srcaddr = dataptr; | ||
106 | lastlli->dstaddr = (void*)((int)&I2STXDB0); | ||
107 | lastlli->nextlli = last ? NULL : pcm_lli; | ||
108 | lastlli->control = (last ? 0xf524a000 : 0x7524a000) | (lastsize / 2); | ||
109 | dataptr += lastsize; | ||
110 | commit_dcache(); | ||
111 | if (!(DMAC0C0CONFIG & 1) && (pcm_lli[0].control & 0xfff)) | ||
112 | { | ||
113 | DMAC0C0LLI = pcm_lli[0]; | ||
114 | DMAC0C0CONFIG = 0x8a81; | ||
115 | } | ||
116 | else DMAC0C0NEXTLLI = pcm_lli; | ||
117 | 141 | ||
118 | pcm_play_dma_status_callback(PCM_DMAST_STARTED); | 142 | pcm_play_dma_status_callback(PCM_DMAST_STARTED); |
119 | } | 143 | } |
120 | 144 | ||
121 | void pcm_play_dma_start(const void* addr, size_t size) | 145 | void pcm_play_dma_start(const void* addr, size_t size) |
122 | { | 146 | { |
123 | dataptr = addr; | 147 | pcm_play_dma_stop(); |
148 | |||
124 | pcm_remaining = size; | 149 | pcm_remaining = size; |
125 | I2STXCOM = 0xe; | 150 | I2STXCOM = 0xe; |
126 | INT_DMAC0C0(); | 151 | dma_play_callback((void*)addr); |
127 | } | 152 | } |
128 | 153 | ||
129 | void pcm_play_dma_stop(void) | 154 | void pcm_play_dma_stop(void) |
130 | { | 155 | { |
131 | DMAC0C0CONFIG = 0x8a80; | 156 | dmac_ch_stop(&dma_play_ch); |
132 | I2STXCOM = 0xa; | 157 | I2STXCOM = 0xa; |
133 | } | 158 | } |
134 | 159 | ||
@@ -184,9 +209,11 @@ void pcm_play_dma_init(void) | |||
184 | { | 209 | { |
185 | PWRCON(0) &= ~(1 << 4); | 210 | PWRCON(0) &= ~(1 << 4); |
186 | PWRCON(1) &= ~(1 << 7); | 211 | PWRCON(1) &= ~(1 << 7); |
212 | |||
213 | dmac_ch_init(&dma_play_ch, &dma_play_ch_cfg); | ||
214 | |||
187 | I2STXCON = 0xb100019; | 215 | I2STXCON = 0xb100019; |
188 | I2SCLKCON = 1; | 216 | I2SCLKCON = 1; |
189 | VIC0INTENABLE = 1 << IRQ_DMAC0; | ||
190 | 217 | ||
191 | audiohw_preinit(); | 218 | audiohw_preinit(); |
192 | pcm_dma_apply_settings(); | 219 | pcm_dma_apply_settings(); |
@@ -199,21 +226,16 @@ void pcm_play_dma_postinit(void) | |||
199 | 226 | ||
200 | size_t pcm_get_bytes_waiting(void) | 227 | size_t pcm_get_bytes_waiting(void) |
201 | { | 228 | { |
202 | int bytes = pcm_remaining; | 229 | size_t total_bytes; |
203 | const struct dma_lli* lli = (const struct dma_lli*)((int)&DMAC0C0LLI); | 230 | dmac_ch_get_info(&dma_play_ch, NULL, &total_bytes); |
204 | while (lli) | 231 | return total_bytes; |
205 | { | ||
206 | bytes += (lli->control & 0xfff) * 2; | ||
207 | if (lli == lastlli) break; | ||
208 | lli = lli->nextlli; | ||
209 | } | ||
210 | return bytes; | ||
211 | } | 232 | } |
212 | 233 | ||
213 | const void* pcm_play_dma_get_peak_buffer(int *count) | 234 | const void* pcm_play_dma_get_peak_buffer(int *count) |
214 | { | 235 | { |
215 | *count = (DMAC0C0LLI.control & 0xfff) * 2; | 236 | void *addr = dmac_ch_get_info(&dma_play_ch, count, NULL); |
216 | return (void*)(((uint32_t)DMAC0C0LLI.srcaddr) & ~3); | 237 | *count >>= 2; /* bytes to samples */ |
238 | return addr; /* aligned to dest burst */ | ||
217 | } | 239 | } |
218 | 240 | ||
219 | #ifdef HAVE_PCM_DMA_ADDRESS | 241 | #ifdef HAVE_PCM_DMA_ADDRESS |