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