xref: /netbsd-src/sys/dev/ic/msm6258.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: msm6258.c,v 1.24 2017/09/02 12:57:35 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.24 2017/09/02 12:57:35 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 int16_t buzzer;	/* sound for debug */
79 
80 static stream_filter_t *
81 msm6258_factory(struct audio_softc *asc,
82     int (*fetch_to)(struct audio_softc *, stream_fetcher_t *, audio_stream_t *, int))
83 {
84 	struct msm6258_codecvar *this;
85 
86 	this = kmem_alloc(sizeof(struct msm6258_codecvar), KM_SLEEP);
87 	this->base.base.fetch_to = fetch_to;
88 	this->base.dtor = msm6258_dtor;
89 	this->base.set_fetcher = stream_filter_set_fetcher;
90 	this->base.set_inputbuffer = stream_filter_set_inputbuffer;
91 	return &this->base;
92 }
93 
94 static void
95 msm6258_dtor(struct stream_filter *this)
96 {
97 	if (this != NULL)
98 		kmem_free(this, sizeof(struct msm6258_codecvar));
99 }
100 
101 /*
102  * signed 16bit linear PCM -> OkiADPCM
103  */
104 static inline uint8_t
105 pcm2adpcm_step(struct msm6258_codecvar *mc, int16_t a)
106 {
107 	int estim = (int)mc->mc_estim;
108 	int df;
109 	short dl, c;
110 	uint8_t b;
111 	uint8_t s;
112 
113 	df = a - mc->mc_amp;
114 	dl = adpcm_estim[estim];
115 	c = (df / 16) * 8 / dl;
116 	if (df < 0) {
117 		b = (unsigned char)(-c) / 2;
118 		s = 0x08;
119 	} else {
120 		b = (unsigned char)(c) / 2;
121 		s = 0;
122 	}
123 	if (b > 7)
124 		b = 7;
125 	s |= b;
126 	mc->mc_amp += (short)(adpcm_estimindex[(int)s] * dl);
127 	estim += adpcm_estimstep[b];
128 	if (estim < 0)
129 		estim = 0;
130 	else if (estim > 48)
131 		estim = 48;
132 
133 	mc->mc_estim = estim;
134 	return s;
135 }
136 
137 #define DEFINE_FILTER(name)	\
138 static int \
139 name##_fetch_to(struct audio_softc *, stream_fetcher_t *, audio_stream_t *, int); \
140 stream_filter_t * \
141 name(struct audio_softc *sc, const audio_params_t *from, \
142      const audio_params_t *to) \
143 { \
144 	return msm6258_factory(sc, name##_fetch_to); \
145 } \
146 static int \
147 name##_fetch_to(struct audio_softc *asc, stream_fetcher_t *self, audio_stream_t *dst, int max_used)
148 
149 DEFINE_FILTER(msm6258_slinear16_to_adpcm)
150 {
151 	stream_filter_t *this;
152 	struct msm6258_codecvar *mc;
153 	uint8_t *d;
154 	const uint8_t *s;
155 	int m, err, enc_src;
156 
157 	this = (stream_filter_t *)self;
158 	mc = (struct msm6258_codecvar *)self;
159 	if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used * 4)))
160 		return err;
161 	m = dst->end - dst->start;
162 	m = min(m, max_used);
163 	d = dst->inp;
164 	s = this->src->outp;
165 	enc_src = this->src->param.encoding;
166 	if (enc_src == AUDIO_ENCODING_SLINEAR_LE) {
167 		while (dst->used < m && this->src->used >= 4) {
168 			uint8_t f;
169 			int16_t ss;
170 			ss = le16toh(*(const int16_t*)s);
171 			f  = pcm2adpcm_step(mc, ss);
172 			s = audio_stream_add_outp(this->src, s, 2);
173 			ss = le16toh(*(const int16_t*)s);
174 			f |= pcm2adpcm_step(mc, ss) << 4;
175 			s = audio_stream_add_outp(this->src, s, 2);
176 			*d = f;
177 			d = audio_stream_add_inp(dst, d, 1);
178 		}
179 	} else if (enc_src == AUDIO_ENCODING_SLINEAR_BE) {
180 		while (dst->used < m && this->src->used >= 4) {
181 			uint8_t f;
182 			int16_t ss;
183 			ss = be16toh(*(const int16_t*)s);
184 			s = audio_stream_add_outp(this->src, s, 2);
185 			f  = pcm2adpcm_step(mc, ss);
186 			ss = be16toh(*(const int16_t*)s);
187 			s = audio_stream_add_outp(this->src, s, 2);
188 			f |= pcm2adpcm_step(mc, ss) << 4;
189 			*d = f;
190 			d = audio_stream_add_inp(dst, d, 1);
191 		}
192 	} else {
193 #if defined(DIAGNOSTIC)
194 		panic("msm6258_slinear16_to_adpcm: unsupported enc_src(%d)", enc_src);
195 #endif
196 		/* dummy run */
197 		while (dst->used < m && this->src->used >= 4) {
198 			s = audio_stream_add_outp(this->src, s, 2);
199 			s = audio_stream_add_outp(this->src, s, 2);
200 			*d = buzzer++;
201 			d = audio_stream_add_inp(dst, d, 1);
202 		}
203 	}
204 	dst->inp = d;
205 	this->src->outp = s;
206 	return 0;
207 }
208 
209 DEFINE_FILTER(msm6258_linear8_to_adpcm)
210 {
211 	stream_filter_t *this;
212 	struct msm6258_codecvar *mc;
213 	uint8_t *d;
214 	const uint8_t *s;
215 	int m, err, enc_src;
216 
217 	this = (stream_filter_t *)self;
218 	mc = (struct msm6258_codecvar *)self;
219 	if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used * 2)))
220 		return err;
221 	m = dst->end - dst->start;
222 	m = min(m, max_used);
223 	d = dst->inp;
224 	s = this->src->outp;
225 	enc_src = this->src->param.encoding;
226 	if (enc_src == AUDIO_ENCODING_SLINEAR_LE
227 	 || enc_src == AUDIO_ENCODING_SLINEAR_BE) {
228 		while (dst->used < m && this->src->used >= 4) {
229 			uint8_t f;
230 			int16_t ss;
231 			ss = ((int16_t)s[0]) * 256;
232 			s = audio_stream_add_outp(this->src, s, 1);
233 			f  = pcm2adpcm_step(mc, ss);
234 			ss = ((int16_t)s[0]) * 256;
235 			s = audio_stream_add_outp(this->src, s, 1);
236 			f |= pcm2adpcm_step(mc, ss) << 4;
237 			*d = f;
238 			d = audio_stream_add_inp(dst, d, 1);
239 		}
240 	} else if (enc_src == AUDIO_ENCODING_ULINEAR_LE
241 	        || enc_src == AUDIO_ENCODING_ULINEAR_BE) {
242 		while (dst->used < m && this->src->used >= 4) {
243 			uint8_t f;
244 			int16_t ss;
245 			ss = ((int16_t)(s[0] ^ 0x80)) * 256;
246 			s = audio_stream_add_outp(this->src, s, 1);
247 			f  = pcm2adpcm_step(mc, ss);
248 			ss = ((int16_t)(s[0] ^ 0x80)) * 256;
249 			s = audio_stream_add_outp(this->src, s, 1);
250 			f |= pcm2adpcm_step(mc, ss) << 4;
251 			*d = f;
252 			d = audio_stream_add_inp(dst, d, 1);
253 		}
254 	} else {
255 #if defined(DIAGNOSTIC)
256 		panic("msm6258_linear8_to_adpcm: unsupported enc_src(%d)", enc_src);
257 #endif
258 		/* dummy run */
259 		while (dst->used < m && this->src->used >= 4) {
260 			s = audio_stream_add_outp(this->src, s, 1);
261 			s = audio_stream_add_outp(this->src, s, 1);
262 			*d = buzzer++;
263 			d = audio_stream_add_inp(dst, d, 1);
264 		}
265 	}
266 	dst->inp = d;
267 	this->src->outp = s;
268 	return 0;
269 }
270 
271 /*
272  * OkiADPCM -> signed 16bit linear PCM
273  */
274 static inline int16_t
275 adpcm2pcm_step(struct msm6258_codecvar *mc, uint8_t b)
276 {
277 	int estim = (int)mc->mc_estim;
278 
279 	mc->mc_amp += adpcm_estim[estim] * adpcm_estimindex[b];
280 	estim += adpcm_estimstep[b];
281 
282 	if (estim < 0)
283 		estim = 0;
284 	else if (estim > 48)
285 		estim = 48;
286 
287 	mc->mc_estim = estim;
288 
289 	return mc->mc_amp;
290 }
291 
292 DEFINE_FILTER(msm6258_adpcm_to_slinear16)
293 {
294 	stream_filter_t *this;
295 	struct msm6258_codecvar *mc;
296 	uint8_t *d;
297 	const uint8_t *s;
298 	int m, err, enc_dst;
299 
300 	this = (stream_filter_t *)self;
301 	mc = (struct msm6258_codecvar *)self;
302 	max_used = (max_used + 3) & ~3; /* round up multiple of 4 */
303 	if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used / 4)))
304 		return err;
305 	m = (dst->end - dst->start) & ~3;
306 	m = min(m, max_used);
307 	d = dst->inp;
308 	s = this->src->outp;
309 	enc_dst = dst->param.encoding;
310 	if (enc_dst == AUDIO_ENCODING_SLINEAR_LE) {
311 		while (dst->used < m && this->src->used >= 1) {
312 			uint8_t a;
313 			int16_t s1, s2;
314 			a = s[0];
315 			s1 = adpcm2pcm_step(mc, a & 0x0f);
316 			s2 = adpcm2pcm_step(mc, a >> 4);
317 			*(int16_t*)d = htole16(s1);
318 			d = audio_stream_add_inp(dst, d, 2);
319 			*(int16_t*)d = htole16(s2);
320 			d = audio_stream_add_inp(dst, d, 2);
321 			s = audio_stream_add_outp(this->src, s, 1);
322 		}
323 	} else if (enc_dst == AUDIO_ENCODING_SLINEAR_BE) {
324 		while (dst->used < m && this->src->used >= 1) {
325 			uint8_t a;
326 			int16_t s1, s2;
327 			a = s[0];
328 			s1 = adpcm2pcm_step(mc, a & 0x0f);
329 			s2 = adpcm2pcm_step(mc, a >> 4);
330 			*(int16_t*)d = htobe16(s1);
331 			d = audio_stream_add_inp(dst, d, 2);
332 			*(int16_t*)d = htobe16(s2);
333 			d = audio_stream_add_inp(dst, d, 2);
334 			s = audio_stream_add_outp(this->src, s, 1);
335 		}
336 	} else {
337 #if defined(DIAGNOSTIC)
338 		panic("msm6258_adpcm_to_slinear16: unsupported enc_dst(%d)", enc_dst);
339 #endif
340 		/* dummy run */
341 		while (dst->used < m && this->src->used >= 1) {
342 			*d = buzzer++;
343 			d = audio_stream_add_inp(dst, d, 2);
344 			*d = buzzer++;
345 			d = audio_stream_add_inp(dst, d, 2);
346 			s = audio_stream_add_outp(this->src, s, 1);
347 		}
348 	}
349 	dst->inp = d;
350 	this->src->outp = s;
351 	return 0;
352 }
353 
354 DEFINE_FILTER(msm6258_adpcm_to_linear8)
355 {
356 	stream_filter_t *this;
357 	struct msm6258_codecvar *mc;
358 	uint8_t *d;
359 	const uint8_t *s;
360 	int m, err, enc_dst;
361 
362 	this = (stream_filter_t *)self;
363 	mc = (struct msm6258_codecvar *)self;
364 	max_used = (max_used + 1) & ~1; /* round up multiple of 4 */
365 	if ((err = this->prev->fetch_to(asc, this->prev, this->src, max_used / 2)))
366 		return err;
367 	m = (dst->end - dst->start) & ~1;
368 	m = min(m, max_used);
369 	d = dst->inp;
370 	s = this->src->outp;
371 	enc_dst = dst->param.encoding;
372 	if (enc_dst == AUDIO_ENCODING_SLINEAR_LE) {
373 		while (dst->used < m && this->src->used >= 1) {
374 			uint8_t a;
375 			int16_t s1, s2;
376 			a = s[0];
377 			s = audio_stream_add_outp(this->src, s, 1);
378 			s1 = adpcm2pcm_step(mc, a & 0x0f);
379 			s2 = adpcm2pcm_step(mc, a >> 4);
380 			d[0] = s1 / 256;
381 			d = audio_stream_add_inp(dst, d, 1);
382 			d[0] = s2 / 256;
383 			d = audio_stream_add_inp(dst, d, 1);
384 		}
385 	} else if (enc_dst == AUDIO_ENCODING_ULINEAR_LE) {
386 		while (dst->used < m && this->src->used >= 1) {
387 			uint8_t a;
388 			int16_t s1, s2;
389 			a = s[0];
390 			s = audio_stream_add_outp(this->src, s, 1);
391 			s1 = adpcm2pcm_step(mc, a & 0x0f);
392 			s2 = adpcm2pcm_step(mc, a >> 4);
393 			d[0] = (s1 / 256) ^ 0x80;
394 			d = audio_stream_add_inp(dst, d, 1);
395 			d[0] = (s2 / 256) ^ 0x80;
396 			d = audio_stream_add_inp(dst, d, 1);
397 		}
398 	} else {
399 #if defined(DIAGNOSTIC)
400 		panic("msm6258_adpcm_to_linear8: unsupported enc_dst(%d)", enc_dst);
401 #endif
402 		/* dummy run */
403 		while (dst->used < m && this->src->used >= 1) {
404 			*d = buzzer++;
405 			d = audio_stream_add_inp(dst, d, 1);
406 			*d = buzzer++;
407 			d = audio_stream_add_inp(dst, d, 1);
408 			s = audio_stream_add_outp(this->src, s, 1);
409 		}
410 	}
411 	dst->inp = d;
412 	this->src->outp = s;
413 	return 0;
414 }
415