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