msm6258.c revision 1.18 1 /* $NetBSD: msm6258.c,v 1.18 2017/07/15 10:17:09 isaki Exp $ */
2
3 /*
4 * Copyright (c) 2001 Tetsuya Isaki. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 /*
29 * OKI MSM6258 ADPCM voice synthesizer codec.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: msm6258.c,v 1.18 2017/07/15 10:17:09 isaki Exp $");
34
35 #include <sys/systm.h>
36 #include <sys/device.h>
37 #include <sys/kmem.h>
38 #include <sys/select.h>
39 #include <sys/audioio.h>
40
41 #include <dev/audio_if.h>
42 #include <dev/auconv.h>
43 #include <dev/audiovar.h>
44 #include <dev/mulaw.h>
45 #include <dev/ic/msm6258var.h>
46
47 struct msm6258_codecvar {
48 stream_filter_t base;
49 short mc_amp;
50 char mc_estim;
51 };
52
53 static stream_filter_t *msm6258_factory
54 (struct audio_softc *,
55 int (*)(struct audio_softc *, stream_fetcher_t *, audio_stream_t *, int));
56 static void msm6258_dtor(struct stream_filter *);
57 static inline uint8_t pcm2adpcm_step(struct msm6258_codecvar *, int16_t);
58 static inline int16_t adpcm2pcm_step(struct msm6258_codecvar *, uint8_t);
59
60 static const int adpcm_estimindex[16] = {
61 2, 6, 10, 14, 18, 22, 26, 30,
62 -2, -6, -10, -14, -18, -22, -26, -30
63 };
64
65 static const int adpcm_estim[49] = {
66 16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
67 41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
68 107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
69 279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
70 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
71 };
72
73 static const int adpcm_estimstep[16] = {
74 -1, -1, -1, -1, 2, 4, 6, 8,
75 -1, -1, -1, -1, 2, 4, 6, 8
76 };
77
78 static stream_filter_t *
79 msm6258_factory(struct audio_softc *asc,
80 int (*fetch_to)(struct audio_softc *, stream_fetcher_t *, audio_stream_t *, int))
81 {
82 struct msm6258_codecvar *this;
83
84 this = kmem_alloc(sizeof(struct msm6258_codecvar), KM_SLEEP);
85 this->base.base.fetch_to = fetch_to;
86 this->base.dtor = msm6258_dtor;
87 this->base.set_fetcher = stream_filter_set_fetcher;
88 this->base.set_inputbuffer = stream_filter_set_inputbuffer;
89 return &this->base;
90 }
91
92 static void
93 msm6258_dtor(struct stream_filter *this)
94 {
95 if (this != NULL)
96 kmem_free(this, sizeof(struct msm6258_codecvar));
97 }
98
99 /*
100 * signed 16bit linear PCM -> OkiADPCM
101 */
102 static inline uint8_t
103 pcm2adpcm_step(struct msm6258_codecvar *mc, int16_t a)
104 {
105 int estim = (int)mc->mc_estim;
106 int df;
107 short dl, c;
108 uint8_t b;
109 uint8_t s;
110
111 df = a - mc->mc_amp;
112 dl = adpcm_estim[estim];
113 c = (df / 16) * 8 / dl;
114 if (df < 0) {
115 b = (unsigned char)(-c) / 2;
116 s = 0x08;
117 } else {
118 b = (unsigned char)(c) / 2;
119 s = 0;
120 }
121 if (b > 7)
122 b = 7;
123 s |= b;
124 mc->mc_amp += (short)(adpcm_estimindex[(int)s] * dl);
125 estim += adpcm_estimstep[b];
126 if (estim < 0)
127 estim = 0;
128 else if (estim > 48)
129 estim = 48;
130
131 mc->mc_estim = estim;
132 return s;
133 }
134
135 #define DEFINE_FILTER(name) \
136 static int \
137 name##_fetch_to(struct audio_softc *, stream_fetcher_t *, audio_stream_t *, int); \
138 stream_filter_t * \
139 name(struct audio_softc *sc, const audio_params_t *from, \
140 const audio_params_t *to) \
141 { \
142 return msm6258_factory(sc, name##_fetch_to); \
143 } \
144 static int \
145 name##_fetch_to(struct audio_softc *asc, stream_fetcher_t *self, audio_stream_t *dst, int max_used)
146
147 DEFINE_FILTER(msm6258_slinear16_to_adpcm)
148 {
149 stream_filter_t *this;
150 struct msm6258_codecvar *mc;
151 uint8_t *d;
152 const uint8_t *s;
153 int m, err, enc_src;
154
155 this = (stream_filter_t *)self;
156 mc = (struct msm6258_codecvar *)self;
157 if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used * 4)))
158 return err;
159 m = dst->end - dst->start;
160 m = min(m, max_used);
161 d = dst->inp;
162 s = this->src->outp;
163 enc_src = this->src->param.encoding;
164 if (enc_src == AUDIO_ENCODING_SLINEAR_LE) {
165 while (dst->used < m && this->src->used >= 4) {
166 uint8_t f;
167 int16_t ss;
168 #if BYTE_ORDER == LITTLE_ENDIAN
169 ss = *(const int16_t*)s;
170 s = audio_stream_add_outp(this->src, s, 2);
171 f = pcm2adpcm_step(mc, ss);
172 ss = *(const int16_t*)s;
173 #else
174 ss = (s[1] << 8) | s[0];
175 s = audio_stream_add_outp(this->src, s, 2);
176 f = pcm2adpcm_step(mc, ss);
177 ss = (s[1] << 8) | s[0];
178 #endif
179 f |= pcm2adpcm_step(mc, ss) << 4;
180 *d = f;
181 d = audio_stream_add_inp(dst, d, 1);
182 s = audio_stream_add_outp(this->src, s, 2);
183 }
184 #if defined(DIAGNOSTIC)
185 } else if (enc_src == AUDIO_ENCODING_SLINEAR_BE) {
186 #else
187 } else {
188 #endif
189 while (dst->used < m && this->src->used >= 4) {
190 uint8_t f;
191 int16_t ss;
192 #if BYTE_ORDER == BIG_ENDIAN
193 ss = *(const int16_t*)s;
194 s = audio_stream_add_outp(this->src, s, 2);
195 f = pcm2adpcm_step(mc, ss);
196 ss = *(const int16_t*)s;
197 #else
198 ss = (s[0] << 8) | s[1];
199 s = audio_stream_add_outp(this->src, s, 2);
200 f = pcm2adpcm_step(mc, ss);
201 ss = (s[0] << 8) | s[1];
202 #endif
203 f |= pcm2adpcm_step(mc, ss) << 4;
204 *d = f;
205 d = audio_stream_add_inp(dst, d, 1);
206 s = audio_stream_add_outp(this->src, s, 2);
207 }
208 }
209 #if defined(DIAGNOSTIC)
210 else {
211 panic("msm6258_slinear16_to_adpcm: unsupported enc_src(%d)", enc_src);
212 }
213 #endif
214 dst->inp = d;
215 this->src->outp = s;
216 return 0;
217 }
218
219 DEFINE_FILTER(msm6258_linear8_to_adpcm)
220 {
221 stream_filter_t *this;
222 struct msm6258_codecvar *mc;
223 uint8_t *d;
224 const uint8_t *s;
225 int m, err, enc_src;
226
227 this = (stream_filter_t *)self;
228 mc = (struct msm6258_codecvar *)self;
229 if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used * 2)))
230 return err;
231 m = dst->end - dst->start;
232 m = min(m, max_used);
233 d = dst->inp;
234 s = this->src->outp;
235 enc_src = this->src->param.encoding;
236 if (enc_src == AUDIO_ENCODING_SLINEAR_LE) {
237 while (dst->used < m && this->src->used >= 4) {
238 uint8_t f;
239 int16_t ss;
240 ss = ((int16_t)s[0]) * 256;
241 s = audio_stream_add_outp(this->src, s, 1);
242 f = pcm2adpcm_step(mc, ss);
243 ss = ((int16_t)s[0]) * 256;
244 f |= pcm2adpcm_step(mc, ss) << 4;
245 *d = f;
246 d = audio_stream_add_inp(dst, d, 1);
247 s = audio_stream_add_outp(this->src, s, 1);
248 }
249 #if defined(DIAGNOSTIC)
250 } else if (enc_src == AUDIO_ENCODING_ULINEAR_LE) {
251 #else
252 } else {
253 #endif
254 while (dst->used < m && this->src->used >= 4) {
255 uint8_t f;
256 int16_t ss;
257 ss = ((int16_t)(s[0] ^ 0x80)) * 256;
258 s = audio_stream_add_outp(this->src, s, 1);
259 f = pcm2adpcm_step(mc, ss);
260 ss = ((int16_t)(s[0] ^ 0x80)) * 256;
261 f |= pcm2adpcm_step(mc, ss) << 4;
262 *d = f;
263 d = audio_stream_add_inp(dst, d, 1);
264 s = audio_stream_add_outp(this->src, s, 1);
265 }
266 }
267 #if defined(DIAGNOSTIC)
268 else {
269 panic("msm6258_linear8_to_adpcm: unsupported enc_src(%d)", enc_src);
270 }
271 #endif
272 dst->inp = d;
273 this->src->outp = s;
274 return 0;
275 }
276
277 /*
278 * OkiADPCM -> signed 16bit linear PCM
279 */
280 static inline int16_t
281 adpcm2pcm_step(struct msm6258_codecvar *mc, uint8_t b)
282 {
283 int estim = (int)mc->mc_estim;
284
285 mc->mc_amp += adpcm_estim[estim] * adpcm_estimindex[b];
286 estim += adpcm_estimstep[b];
287
288 if (estim < 0)
289 estim = 0;
290 else if (estim > 48)
291 estim = 48;
292
293 mc->mc_estim = estim;
294
295 return mc->mc_amp;
296 }
297
298 DEFINE_FILTER(msm6258_adpcm_to_slinear16)
299 {
300 stream_filter_t *this;
301 struct msm6258_codecvar *mc;
302 uint8_t *d;
303 const uint8_t *s;
304 int m, err, enc_dst;
305
306 this = (stream_filter_t *)self;
307 mc = (struct msm6258_codecvar *)self;
308 max_used = (max_used + 3) & ~3; /* round up multiple of 4 */
309 if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used / 4)))
310 return err;
311 m = (dst->end - dst->start) & ~3;
312 m = min(m, max_used);
313 d = dst->inp;
314 s = this->src->outp;
315 enc_dst = dst->param.encoding;
316 if (enc_dst == AUDIO_ENCODING_SLINEAR_LE) {
317 while (dst->used < m && this->src->used >= 1) {
318 uint8_t a;
319 int16_t s1, s2;
320 a = s[0];
321 s1 = adpcm2pcm_step(mc, a & 0x0f);
322 s2 = adpcm2pcm_step(mc, a >> 4);
323 #if BYTE_ORDER == LITTLE_ENDIAN
324 *(int16_t*)d = s1;
325 d = audio_stream_add_inp(dst, d, 2);
326 *(int16_t*)d = s2;
327 #else
328 d[0] = s1;
329 d[1] = s1 >> 8;
330 d = audio_stream_add_inp(dst, d, 2);
331 d[0] = s2;
332 d[1] = s2 >> 8;
333 #endif
334 d = audio_stream_add_inp(dst, d, 2);
335 s = audio_stream_add_outp(this->src, s, 1);
336 }
337 #if defined(DIAGNOSTIC)
338 } else if (enc_dst == AUDIO_ENCODING_SLINEAR_BE) {
339 #else
340 } else {
341 #endif
342 while (dst->used < m && this->src->used >= 1) {
343 uint8_t a;
344 int16_t s1, s2;
345 a = s[0];
346 s1 = adpcm2pcm_step(mc, a & 0x0f);
347 s2 = adpcm2pcm_step(mc, a >> 4);
348 #if BYTE_ORDER == BIG_ENDIAN
349 *(int16_t*)d = s1;
350 d = audio_stream_add_inp(dst, d, 2);
351 *(int16_t*)d = s2;
352 #else
353 d[1] = s1;
354 d[0] = s1 >> 8;
355 d = audio_stream_add_inp(dst, d, 2);
356 d[1] = s2;
357 d[0] = s2 >> 8;
358 #endif
359 d = audio_stream_add_inp(dst, d, 2);
360 s = audio_stream_add_outp(this->src, s, 1);
361 }
362 }
363 #if defined(DIAGNOSTIC)
364 else {
365 panic("msm6258_adpcm_to_slinear16: unsupported enc_dst(%d)", enc_dst);
366 }
367 #endif
368 dst->inp = d;
369 this->src->outp = s;
370 return 0;
371 }
372
373 DEFINE_FILTER(msm6258_adpcm_to_linear8)
374 {
375 stream_filter_t *this;
376 struct msm6258_codecvar *mc;
377 uint8_t *d;
378 const uint8_t *s;
379 int m, err, enc_dst;
380
381 this = (stream_filter_t *)self;
382 mc = (struct msm6258_codecvar *)self;
383 max_used = (max_used + 1) & ~1; /* round up multiple of 4 */
384 if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used / 2)))
385 return err;
386 m = (dst->end - dst->start) & ~1;
387 m = min(m, max_used);
388 d = dst->inp;
389 s = this->src->outp;
390 enc_dst = dst->param.encoding;
391 if (enc_dst == AUDIO_ENCODING_SLINEAR_LE) {
392 while (dst->used < m && this->src->used >= 1) {
393 uint8_t a;
394 int16_t s1, s2;
395 a = s[0];
396 s1 = adpcm2pcm_step(mc, a & 0x0f);
397 s2 = adpcm2pcm_step(mc, a >> 4);
398 d[0] = s1 / 266;
399 d = audio_stream_add_inp(dst, d, 1);
400 d[0] = s2 / 266;
401 d = audio_stream_add_inp(dst, d, 1);
402 s = audio_stream_add_outp(this->src, s, 1);
403 }
404 #if defined(DIAGNOSTIC)
405 } else if (enc_dst == AUDIO_ENCODING_ULINEAR_LE) {
406 #else
407 } else {
408 #endif
409 while (dst->used < m && this->src->used >= 1) {
410 uint8_t a;
411 int16_t s1, s2;
412 a = s[0];
413 s1 = adpcm2pcm_step(mc, a & 0x0f);
414 s2 = adpcm2pcm_step(mc, a >> 4);
415 d[0] = (s1 / 266) ^ 0x80;
416 d = audio_stream_add_inp(dst, d, 1);
417 d[0] = (s2 / 266) ^ 0x80;
418 d = audio_stream_add_inp(dst, d, 1);
419 s = audio_stream_add_outp(this->src, s, 1);
420 }
421 }
422 #if defined(DIAGNOSTIC)
423 else {
424 panic("msm6258_adpcm_to_linear8: unsupported enc_dst(%d)", enc_dst);
425 }
426 #endif
427 dst->inp = d;
428 this->src->outp = s;
429 return 0;
430 }
431