sun8i_h3_codec.c revision 1.3
1/* $NetBSD: sun8i_h3_codec.c,v 1.3 2017/12/21 11:52:00 nat Exp $ */ 2 3/*- 4 * Copyright (c) 2014-2017 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__KERNEL_RCSID(0, "$NetBSD: sun8i_h3_codec.c,v 1.3 2017/12/21 11:52:00 nat Exp $"); 31 32#include <sys/param.h> 33#include <sys/bus.h> 34#include <sys/cpu.h> 35#include <sys/device.h> 36#include <sys/kmem.h> 37#include <sys/bitops.h> 38 39#include <sys/audioio.h> 40#include <dev/audio_if.h> 41 42#include <arm/sunxi/sunxi_codec.h> 43 44#define H3_PR_CFG 0x00 45#define H3_AC_PR_RST __BIT(28) 46#define H3_AC_PR_RW __BIT(24) 47#define H3_AC_PR_ADDR __BITS(20,16) 48#define H3_ACDA_PR_WDAT __BITS(15,8) 49#define H3_ACDA_PR_RDAT __BITS(7,0) 50 51#define H3_LOMIXSC 0x01 52#define H3_LOMIXSC_LDAC __BIT(1) 53#define H3_ROMIXSC 0x02 54#define H3_ROMIXSC_RDAC __BIT(1) 55#define H3_DAC_PA_SRC 0x03 56#define H3_DACAREN __BIT(7) 57#define H3_DACALEN __BIT(6) 58#define H3_RMIXEN __BIT(5) 59#define H3_LMIXEN __BIT(4) 60#define H3_LINEIN_GCTR 0x05 61#define H3_LINEING __BITS(6,4) 62#define H3_MIC_GCTR 0x06 63#define H3_MIC1_GAIN __BITS(6,4) 64#define H3_MIC2_GAIN __BITS(2,0) 65#define H3_PAEN_CTR 0x07 66#define H3_LINEOUTEN __BIT(7) 67#define H3_LINEOUT_VOLC 0x09 68#define H3_LINEOUTVOL __BITS(7,3) 69#define H3_MIC2G_LINEOUT_CTR 0x0a 70#define H3_LINEOUT_LSEL __BIT(3) 71#define H3_LINEOUT_RSEL __BIT(2) 72#define H3_LADCMIXSC 0x0c 73#define H3_RADCMIXSC 0x0d 74#define H3_ADCMIXSC_MIC1 __BIT(6) 75#define H3_ADCMIXSC_MIC2 __BIT(5) 76#define H3_ADCMIXSC_LINEIN __BIT(2) 77#define H3_ADCMIXSC_OMIXER __BITS(1,0) 78#define H3_ADC_AP_EN 0x0f 79#define H3_ADCREN __BIT(7) 80#define H3_ADCLEN __BIT(6) 81#define H3_ADCG __BITS(2,0) 82 83struct h3_codec_softc { 84 device_t sc_dev; 85 bus_space_tag_t sc_bst; 86 bus_space_handle_t sc_bsh; 87 int sc_phandle; 88}; 89 90enum h3_codec_mixer_ctrl { 91 H3_CODEC_OUTPUT_CLASS, 92 H3_CODEC_INPUT_CLASS, 93 H3_CODEC_RECORD_CLASS, 94 95 H3_CODEC_OUTPUT_MASTER_VOLUME, 96 H3_CODEC_INPUT_DAC_VOLUME, 97 H3_CODEC_INPUT_LINEIN_VOLUME, 98 H3_CODEC_INPUT_MIC1_VOLUME, 99 H3_CODEC_INPUT_MIC2_VOLUME, 100 H3_CODEC_RECORD_AGC_VOLUME, 101 H3_CODEC_RECORD_SOURCE, 102 103 H3_CODEC_MIXER_CTRL_LAST 104}; 105 106static const struct h3_codec_mixer { 107 const char * name; 108 enum h3_codec_mixer_ctrl mixer_class; 109 u_int reg; 110 u_int mask; 111} h3_codec_mixers[H3_CODEC_MIXER_CTRL_LAST] = { 112 [H3_CODEC_OUTPUT_MASTER_VOLUME] = { AudioNmaster, 113 H3_CODEC_OUTPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, 114 [H3_CODEC_INPUT_DAC_VOLUME] = { AudioNdac, 115 H3_CODEC_INPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, 116 [H3_CODEC_INPUT_LINEIN_VOLUME] = { AudioNline, 117 H3_CODEC_INPUT_CLASS, H3_LINEIN_GCTR, H3_LINEING }, 118 [H3_CODEC_INPUT_MIC1_VOLUME] = { "mic1", 119 H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC1_GAIN }, 120 [H3_CODEC_INPUT_MIC2_VOLUME] = { "mic2", 121 H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC2_GAIN }, 122 [H3_CODEC_RECORD_AGC_VOLUME] = { AudioNagc, 123 H3_CODEC_RECORD_CLASS, H3_ADC_AP_EN, H3_ADCG }, 124}; 125 126#define RD4(sc, reg) \ 127 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 128#define WR4(sc, reg, val) \ 129 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 130 131static struct h3_codec_softc * 132h3_codec_find(int phandle) 133{ 134 struct h3_codec_softc *csc; 135 device_t dev; 136 137 dev = device_find_by_driver_unit("h3codec", 0); 138 if (dev == NULL) 139 return NULL; 140 csc = device_private(dev); 141 if (csc->sc_phandle != phandle) 142 return NULL; 143 144 return csc; 145} 146 147static u_int 148h3_codec_pr_read(struct h3_codec_softc *csc, u_int addr) 149{ 150 uint32_t val; 151 152 /* Read current value */ 153 val = RD4(csc, H3_PR_CFG); 154 155 /* De-assert reset */ 156 val |= H3_AC_PR_RST; 157 WR4(csc, H3_PR_CFG, val); 158 159 /* Read mode */ 160 val &= ~H3_AC_PR_RW; 161 WR4(csc, H3_PR_CFG, val); 162 163 /* Set address */ 164 val &= ~H3_AC_PR_ADDR; 165 val |= __SHIFTIN(addr, H3_AC_PR_ADDR); 166 WR4(csc, H3_PR_CFG, val); 167 168 /* Read data */ 169 return __SHIFTOUT(RD4(csc, H3_PR_CFG), H3_ACDA_PR_RDAT); 170} 171 172static void 173h3_codec_pr_write(struct h3_codec_softc *csc, u_int addr, u_int data) 174{ 175 uint32_t val; 176 177 /* Read current value */ 178 val = RD4(csc, H3_PR_CFG); 179 180 /* De-assert reset */ 181 val |= H3_AC_PR_RST; 182 WR4(csc, H3_PR_CFG, val); 183 184 /* Set address */ 185 val &= ~H3_AC_PR_ADDR; 186 val |= __SHIFTIN(addr, H3_AC_PR_ADDR); 187 WR4(csc, H3_PR_CFG, val); 188 189 /* Write data */ 190 val &= ~H3_ACDA_PR_WDAT; 191 val |= __SHIFTIN(data, H3_ACDA_PR_WDAT); 192 WR4(csc, H3_PR_CFG, val); 193 194 /* Write mode */ 195 val |= H3_AC_PR_RW; 196 WR4(csc, H3_PR_CFG, val); 197 198 /* Clear write mode */ 199 val &= ~H3_AC_PR_RW; 200 WR4(csc, H3_PR_CFG, val); 201} 202 203static void 204h3_codec_pr_set_clear(struct h3_codec_softc *csc, u_int addr, u_int set, u_int clr) 205{ 206 u_int old, new; 207 208 old = h3_codec_pr_read(csc, addr); 209 new = set | (old & ~clr); 210 h3_codec_pr_write(csc, addr, new); 211} 212 213static int 214h3_codec_init(struct sunxi_codec_softc *sc) 215{ 216 struct h3_codec_softc *csc; 217 int phandle; 218 219 /* Lookup the codec analog controls phandle */ 220 phandle = fdtbus_get_phandle(sc->sc_phandle, 221 "allwinner,codec-analog-controls"); 222 if (phandle < 0) { 223 aprint_error_dev(sc->sc_dev, 224 "missing allwinner,codec-analog-controls property\n"); 225 return ENXIO; 226 } 227 228 /* Find a matching h3codec instance */ 229 sc->sc_codec_priv = h3_codec_find(phandle); 230 if (sc->sc_codec_priv == NULL) { 231 aprint_error_dev(sc->sc_dev, "couldn't find codec analog controls\n"); 232 return ENOENT; 233 } 234 csc = sc->sc_codec_priv; 235 236 /* Right & Left LINEOUT enable */ 237 h3_codec_pr_set_clear(csc, H3_PAEN_CTR, H3_LINEOUTEN, 0); 238 h3_codec_pr_set_clear(csc, H3_MIC2G_LINEOUT_CTR, 239 H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0); 240 241 return 0; 242} 243 244static void 245h3_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode) 246{ 247 struct h3_codec_softc * const csc = sc->sc_codec_priv; 248 249 if (mode == AUMODE_PLAY) { 250 if (mute) { 251 /* Mute DAC l/r channels to output mixer */ 252 h3_codec_pr_set_clear(csc, H3_LOMIXSC, 253 0, H3_LOMIXSC_LDAC); 254 h3_codec_pr_set_clear(csc, H3_ROMIXSC, 255 0, H3_ROMIXSC_RDAC); 256 /* Disable DAC analog l/r channels and output mixer */ 257 h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, 258 0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN); 259 } else { 260 /* Enable DAC analog l/r channels and output mixer */ 261 h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, 262 H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0); 263 /* Unmute DAC l/r channels to output mixer */ 264 h3_codec_pr_set_clear(csc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0); 265 h3_codec_pr_set_clear(csc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0); 266 } 267 } else { 268 if (mute) { 269 /* Disable ADC analog l/r channels */ 270 h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, 271 0, H3_ADCREN | H3_ADCLEN); 272 } else { 273 /* Enable ADC analog l/r channels */ 274 h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, 275 H3_ADCREN | H3_ADCLEN, 0); 276 } 277 } 278} 279 280static int 281h3_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) 282{ 283 struct h3_codec_softc * const csc = sc->sc_codec_priv; 284 const struct h3_codec_mixer *mix; 285 u_int val, shift; 286 int nvol; 287 288 switch (mc->dev) { 289 case H3_CODEC_OUTPUT_MASTER_VOLUME: 290 case H3_CODEC_INPUT_DAC_VOLUME: 291 case H3_CODEC_INPUT_LINEIN_VOLUME: 292 case H3_CODEC_INPUT_MIC1_VOLUME: 293 case H3_CODEC_INPUT_MIC2_VOLUME: 294 case H3_CODEC_RECORD_AGC_VOLUME: 295 mix = &h3_codec_mixers[mc->dev]; 296 val = h3_codec_pr_read(csc, mix->reg); 297 shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); 298 nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift; 299 val &= ~mix->mask; 300 val |= __SHIFTIN(nvol, mix->mask); 301 h3_codec_pr_write(csc, mix->reg, val); 302 return 0; 303 304 case H3_CODEC_RECORD_SOURCE: 305 h3_codec_pr_write(csc, H3_LADCMIXSC, mc->un.mask); 306 h3_codec_pr_write(csc, H3_RADCMIXSC, mc->un.mask); 307 return 0; 308 } 309 310 return ENXIO; 311} 312 313static int 314h3_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) 315{ 316 struct h3_codec_softc * const csc = sc->sc_codec_priv; 317 const struct h3_codec_mixer *mix; 318 u_int val, shift; 319 int nvol; 320 321 switch (mc->dev) { 322 case H3_CODEC_OUTPUT_MASTER_VOLUME: 323 case H3_CODEC_INPUT_DAC_VOLUME: 324 case H3_CODEC_INPUT_LINEIN_VOLUME: 325 case H3_CODEC_INPUT_MIC1_VOLUME: 326 case H3_CODEC_INPUT_MIC2_VOLUME: 327 case H3_CODEC_RECORD_AGC_VOLUME: 328 mix = &h3_codec_mixers[mc->dev]; 329 val = h3_codec_pr_read(csc, mix->reg); 330 shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); 331 nvol = __SHIFTOUT(val, mix->mask) << shift; 332 mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol; 333 mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol; 334 return 0; 335 336 case H3_CODEC_RECORD_SOURCE: 337 mc->un.mask = 338 h3_codec_pr_read(csc, H3_LADCMIXSC) | 339 h3_codec_pr_read(csc, H3_RADCMIXSC); 340 return 0; 341 } 342 343 return ENXIO; 344} 345 346static int 347h3_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di) 348{ 349 const struct h3_codec_mixer *mix; 350 351 switch (di->index) { 352 case H3_CODEC_OUTPUT_CLASS: 353 di->mixer_class = di->index; 354 strcpy(di->label.name, AudioCoutputs); 355 di->type = AUDIO_MIXER_CLASS; 356 di->next = di->prev = AUDIO_MIXER_LAST; 357 return 0; 358 359 case H3_CODEC_INPUT_CLASS: 360 di->mixer_class = di->index; 361 strcpy(di->label.name, AudioCinputs); 362 di->type = AUDIO_MIXER_CLASS; 363 di->next = di->prev = AUDIO_MIXER_LAST; 364 return 0; 365 366 case H3_CODEC_RECORD_CLASS: 367 di->mixer_class = di->index; 368 strcpy(di->label.name, AudioCrecord); 369 di->type = AUDIO_MIXER_CLASS; 370 di->next = di->prev = AUDIO_MIXER_LAST; 371 return 0; 372 373 case H3_CODEC_OUTPUT_MASTER_VOLUME: 374 case H3_CODEC_INPUT_DAC_VOLUME: 375 case H3_CODEC_INPUT_LINEIN_VOLUME: 376 case H3_CODEC_INPUT_MIC1_VOLUME: 377 case H3_CODEC_INPUT_MIC2_VOLUME: 378 case H3_CODEC_RECORD_AGC_VOLUME: 379 mix = &h3_codec_mixers[di->index]; 380 di->mixer_class = mix->mixer_class; 381 strcpy(di->label.name, mix->name); 382 di->un.v.delta = 383 256 / (__SHIFTOUT_MASK(mix->mask) + 1); 384 di->type = AUDIO_MIXER_VALUE; 385 di->next = di->prev = AUDIO_MIXER_LAST; 386 di->un.v.num_channels = 2; 387 strcpy(di->un.v.units.name, AudioNvolume); 388 return 0; 389 390 case H3_CODEC_RECORD_SOURCE: 391 di->mixer_class = H3_CODEC_RECORD_CLASS; 392 strcpy(di->label.name, AudioNsource); 393 di->type = AUDIO_MIXER_SET; 394 di->next = di->prev = AUDIO_MIXER_LAST; 395 di->un.s.num_mem = 4; 396 strcpy(di->un.s.member[0].label.name, AudioNline); 397 di->un.s.member[0].mask = H3_ADCMIXSC_LINEIN; 398 strcpy(di->un.s.member[1].label.name, "mic1"); 399 di->un.s.member[1].mask = H3_ADCMIXSC_MIC1; 400 strcpy(di->un.s.member[2].label.name, "mic2"); 401 di->un.s.member[2].mask = H3_ADCMIXSC_MIC2; 402 strcpy(di->un.s.member[3].label.name, AudioNdac); 403 di->un.s.member[3].mask = H3_ADCMIXSC_OMIXER; 404 return 0; 405 406 } 407 408 return ENXIO; 409} 410 411const struct sunxi_codec_conf sun8i_h3_codecconf = { 412 .name = "H3 Audio Codec", 413 414 .init = h3_codec_init, 415 .mute = h3_codec_mute, 416 .set_port = h3_codec_set_port, 417 .get_port = h3_codec_get_port, 418 .query_devinfo = h3_codec_query_devinfo, 419 420 .DPC = 0x00, 421 .DAC_FIFOC = 0x04, 422 .DAC_FIFOS = 0x08, 423 .DAC_TXDATA = 0x20, 424 .ADC_FIFOC = 0x10, 425 .ADC_FIFOS = 0x14, 426 .ADC_RXDATA = 0x18, 427 .DAC_CNT = 0x40, 428 .ADC_CNT = 0x44, 429}; 430 431/* 432 * Device glue, only here to claim resources on behalf of the sunxi_codec driver. 433 */ 434 435static const char * compatible[] = { 436 "allwinner,sun8i-h3-codec-analog", 437 NULL 438}; 439 440static int 441h3_codec_match(device_t parent, cfdata_t cf, void *aux) 442{ 443 struct fdt_attach_args * const faa = aux; 444 445 return of_match_compatible(faa->faa_phandle, compatible); 446} 447 448static void 449h3_codec_attach(device_t parent, device_t self, void *aux) 450{ 451 struct h3_codec_softc * const sc = device_private(self); 452 struct fdt_attach_args * const faa = aux; 453 const int phandle = faa->faa_phandle; 454 bus_addr_t addr; 455 bus_size_t size; 456 457 sc->sc_dev = self; 458 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 459 aprint_error(": couldn't get registers\n"); 460 return; 461 } 462 sc->sc_bst = faa->faa_bst; 463 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 464 aprint_error(": couldn't map registers\n"); 465 return; 466 } 467 468 sc->sc_phandle = phandle; 469 470 aprint_naive("\n"); 471 aprint_normal(": H3 Audio Codec (analog part)\n"); 472} 473 474CFATTACH_DECL_NEW(h3_codec, sizeof(struct h3_codec_softc), 475 h3_codec_match, h3_codec_attach, NULL, NULL); 476