|
|
1.1 ! root 1: /* ! 2: * LM4549 Audio Codec Interface ! 3: * ! 4: * Copyright (c) 2011 ! 5: * Written by Mathieu Sonet - www.elasticsheep.com ! 6: * ! 7: * This code is licenced under the GPL. ! 8: * ! 9: * ***************************************************************** ! 10: * ! 11: * This driver emulates the LM4549 codec. ! 12: * ! 13: * It supports only one playback voice and no record voice. ! 14: */ ! 15: ! 16: #include "hw.h" ! 17: #include "audio/audio.h" ! 18: #include "lm4549.h" ! 19: ! 20: #if 0 ! 21: #define LM4549_DEBUG 1 ! 22: #endif ! 23: ! 24: #if 0 ! 25: #define LM4549_DUMP_DAC_INPUT 1 ! 26: #endif ! 27: ! 28: #ifdef LM4549_DEBUG ! 29: #define DPRINTF(fmt, ...) \ ! 30: do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0) ! 31: #else ! 32: #define DPRINTF(fmt, ...) do {} while (0) ! 33: #endif ! 34: ! 35: #if defined(LM4549_DUMP_DAC_INPUT) ! 36: #include <stdio.h> ! 37: static FILE *fp_dac_input; ! 38: #endif ! 39: ! 40: /* LM4549 register list */ ! 41: enum { ! 42: LM4549_Reset = 0x00, ! 43: LM4549_Master_Volume = 0x02, ! 44: LM4549_Line_Out_Volume = 0x04, ! 45: LM4549_Master_Volume_Mono = 0x06, ! 46: LM4549_PC_Beep_Volume = 0x0A, ! 47: LM4549_Phone_Volume = 0x0C, ! 48: LM4549_Mic_Volume = 0x0E, ! 49: LM4549_Line_In_Volume = 0x10, ! 50: LM4549_CD_Volume = 0x12, ! 51: LM4549_Video_Volume = 0x14, ! 52: LM4549_Aux_Volume = 0x16, ! 53: LM4549_PCM_Out_Volume = 0x18, ! 54: LM4549_Record_Select = 0x1A, ! 55: LM4549_Record_Gain = 0x1C, ! 56: LM4549_General_Purpose = 0x20, ! 57: LM4549_3D_Control = 0x22, ! 58: LM4549_Powerdown_Ctrl_Stat = 0x26, ! 59: LM4549_Ext_Audio_ID = 0x28, ! 60: LM4549_Ext_Audio_Stat_Ctrl = 0x2A, ! 61: LM4549_PCM_Front_DAC_Rate = 0x2C, ! 62: LM4549_PCM_ADC_Rate = 0x32, ! 63: LM4549_Vendor_ID1 = 0x7C, ! 64: LM4549_Vendor_ID2 = 0x7E ! 65: }; ! 66: ! 67: static void lm4549_reset(lm4549_state *s) ! 68: { ! 69: uint16_t *regfile = s->regfile; ! 70: ! 71: regfile[LM4549_Reset] = 0x0d50; ! 72: regfile[LM4549_Master_Volume] = 0x8008; ! 73: regfile[LM4549_Line_Out_Volume] = 0x8000; ! 74: regfile[LM4549_Master_Volume_Mono] = 0x8000; ! 75: regfile[LM4549_PC_Beep_Volume] = 0x0000; ! 76: regfile[LM4549_Phone_Volume] = 0x8008; ! 77: regfile[LM4549_Mic_Volume] = 0x8008; ! 78: regfile[LM4549_Line_In_Volume] = 0x8808; ! 79: regfile[LM4549_CD_Volume] = 0x8808; ! 80: regfile[LM4549_Video_Volume] = 0x8808; ! 81: regfile[LM4549_Aux_Volume] = 0x8808; ! 82: regfile[LM4549_PCM_Out_Volume] = 0x8808; ! 83: regfile[LM4549_Record_Select] = 0x0000; ! 84: regfile[LM4549_Record_Gain] = 0x8000; ! 85: regfile[LM4549_General_Purpose] = 0x0000; ! 86: regfile[LM4549_3D_Control] = 0x0101; ! 87: regfile[LM4549_Powerdown_Ctrl_Stat] = 0x000f; ! 88: regfile[LM4549_Ext_Audio_ID] = 0x0001; ! 89: regfile[LM4549_Ext_Audio_Stat_Ctrl] = 0x0000; ! 90: regfile[LM4549_PCM_Front_DAC_Rate] = 0xbb80; ! 91: regfile[LM4549_PCM_ADC_Rate] = 0xbb80; ! 92: regfile[LM4549_Vendor_ID1] = 0x4e53; ! 93: regfile[LM4549_Vendor_ID2] = 0x4331; ! 94: } ! 95: ! 96: static void lm4549_audio_transfer(lm4549_state *s) ! 97: { ! 98: uint32_t written_bytes, written_samples; ! 99: uint32_t i; ! 100: ! 101: /* Activate the voice */ ! 102: AUD_set_active_out(s->voice, 1); ! 103: s->voice_is_active = 1; ! 104: ! 105: /* Try to write the buffer content */ ! 106: written_bytes = AUD_write(s->voice, s->buffer, ! 107: s->buffer_level * sizeof(uint16_t)); ! 108: written_samples = written_bytes >> 1; ! 109: ! 110: #if defined(LM4549_DUMP_DAC_INPUT) ! 111: fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input); ! 112: #endif ! 113: ! 114: s->buffer_level -= written_samples; ! 115: ! 116: if (s->buffer_level > 0) { ! 117: /* Move the data back to the start of the buffer */ ! 118: for (i = 0; i < s->buffer_level; i++) { ! 119: s->buffer[i] = s->buffer[i + written_samples]; ! 120: } ! 121: } ! 122: } ! 123: ! 124: static void lm4549_audio_out_callback(void *opaque, int free) ! 125: { ! 126: lm4549_state *s = (lm4549_state *)opaque; ! 127: static uint32_t prev_buffer_level; ! 128: ! 129: #ifdef LM4549_DEBUG ! 130: int size = AUD_get_buffer_size_out(s->voice); ! 131: DPRINTF("audio_out_callback size = %i free = %i\n", size, free); ! 132: #endif ! 133: ! 134: /* Detect that no data are consumed ! 135: => disable the voice */ ! 136: if (s->buffer_level == prev_buffer_level) { ! 137: AUD_set_active_out(s->voice, 0); ! 138: s->voice_is_active = 0; ! 139: } ! 140: prev_buffer_level = s->buffer_level; ! 141: ! 142: /* Check if a buffer transfer is pending */ ! 143: if (s->buffer_level == LM4549_BUFFER_SIZE) { ! 144: lm4549_audio_transfer(s); ! 145: ! 146: /* Request more data */ ! 147: if (s->data_req_cb != NULL) { ! 148: (s->data_req_cb)(s->opaque); ! 149: } ! 150: } ! 151: } ! 152: ! 153: uint32_t lm4549_read(lm4549_state *s, target_phys_addr_t offset) ! 154: { ! 155: uint16_t *regfile = s->regfile; ! 156: uint32_t value = 0; ! 157: ! 158: /* Read the stored value */ ! 159: assert(offset < 128); ! 160: value = regfile[offset]; ! 161: ! 162: DPRINTF("read [0x%02x] = 0x%04x\n", offset, value); ! 163: ! 164: return value; ! 165: } ! 166: ! 167: void lm4549_write(lm4549_state *s, ! 168: target_phys_addr_t offset, uint32_t value) ! 169: { ! 170: uint16_t *regfile = s->regfile; ! 171: ! 172: assert(offset < 128); ! 173: DPRINTF("write [0x%02x] = 0x%04x\n", offset, value); ! 174: ! 175: switch (offset) { ! 176: case LM4549_Reset: ! 177: lm4549_reset(s); ! 178: break; ! 179: ! 180: case LM4549_PCM_Front_DAC_Rate: ! 181: regfile[LM4549_PCM_Front_DAC_Rate] = value; ! 182: DPRINTF("DAC rate change = %i\n", value); ! 183: ! 184: /* Re-open a voice with the new sample rate */ ! 185: struct audsettings as; ! 186: as.freq = value; ! 187: as.nchannels = 2; ! 188: as.fmt = AUD_FMT_S16; ! 189: as.endianness = 0; ! 190: ! 191: s->voice = AUD_open_out( ! 192: &s->card, ! 193: s->voice, ! 194: "lm4549.out", ! 195: s, ! 196: lm4549_audio_out_callback, ! 197: &as ! 198: ); ! 199: break; ! 200: ! 201: case LM4549_Powerdown_Ctrl_Stat: ! 202: value &= ~0xf; ! 203: value |= regfile[LM4549_Powerdown_Ctrl_Stat] & 0xf; ! 204: regfile[LM4549_Powerdown_Ctrl_Stat] = value; ! 205: break; ! 206: ! 207: case LM4549_Ext_Audio_ID: ! 208: case LM4549_Vendor_ID1: ! 209: case LM4549_Vendor_ID2: ! 210: DPRINTF("Write to read-only register 0x%x\n", (int)offset); ! 211: break; ! 212: ! 213: default: ! 214: /* Store the new value */ ! 215: regfile[offset] = value; ! 216: break; ! 217: } ! 218: } ! 219: ! 220: uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right) ! 221: { ! 222: /* The left and right samples are in 20-bit resolution. ! 223: The LM4549 has 18-bit resolution and only uses the bits [19:2]. ! 224: This model supports 16-bit playback. ! 225: */ ! 226: ! 227: if (s->buffer_level >= LM4549_BUFFER_SIZE) { ! 228: DPRINTF("write_sample Buffer full\n"); ! 229: return 0; ! 230: } ! 231: ! 232: /* Store 16-bit samples in the buffer */ ! 233: s->buffer[s->buffer_level++] = (left >> 4); ! 234: s->buffer[s->buffer_level++] = (right >> 4); ! 235: ! 236: if (s->buffer_level == LM4549_BUFFER_SIZE) { ! 237: /* Trigger the transfer of the buffer to the audio host */ ! 238: lm4549_audio_transfer(s); ! 239: } ! 240: ! 241: return 1; ! 242: } ! 243: ! 244: static int lm4549_post_load(void *opaque, int version_id) ! 245: { ! 246: lm4549_state *s = (lm4549_state *)opaque; ! 247: uint16_t *regfile = s->regfile; ! 248: ! 249: /* Re-open a voice with the current sample rate */ ! 250: uint32_t freq = regfile[LM4549_PCM_Front_DAC_Rate]; ! 251: ! 252: DPRINTF("post_load freq = %i\n", freq); ! 253: DPRINTF("post_load voice_is_active = %i\n", s->voice_is_active); ! 254: ! 255: struct audsettings as; ! 256: as.freq = freq; ! 257: as.nchannels = 2; ! 258: as.fmt = AUD_FMT_S16; ! 259: as.endianness = 0; ! 260: ! 261: s->voice = AUD_open_out( ! 262: &s->card, ! 263: s->voice, ! 264: "lm4549.out", ! 265: s, ! 266: lm4549_audio_out_callback, ! 267: &as ! 268: ); ! 269: ! 270: /* Request data */ ! 271: if (s->voice_is_active == 1) { ! 272: lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice)); ! 273: } ! 274: ! 275: return 0; ! 276: } ! 277: ! 278: void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque) ! 279: { ! 280: struct audsettings as; ! 281: ! 282: /* Store the callback and opaque pointer */ ! 283: s->data_req_cb = data_req_cb; ! 284: s->opaque = opaque; ! 285: ! 286: /* Init the registers */ ! 287: lm4549_reset(s); ! 288: ! 289: /* Register an audio card */ ! 290: AUD_register_card("lm4549", &s->card); ! 291: ! 292: /* Open a default voice */ ! 293: as.freq = 48000; ! 294: as.nchannels = 2; ! 295: as.fmt = AUD_FMT_S16; ! 296: as.endianness = 0; ! 297: ! 298: s->voice = AUD_open_out( ! 299: &s->card, ! 300: s->voice, ! 301: "lm4549.out", ! 302: s, ! 303: lm4549_audio_out_callback, ! 304: &as ! 305: ); ! 306: ! 307: AUD_set_volume_out(s->voice, 0, 255, 255); ! 308: ! 309: s->voice_is_active = 0; ! 310: ! 311: /* Reset the input buffer */ ! 312: memset(s->buffer, 0x00, sizeof(s->buffer)); ! 313: s->buffer_level = 0; ! 314: ! 315: #if defined(LM4549_DUMP_DAC_INPUT) ! 316: fp_dac_input = fopen("lm4549_dac_input.pcm", "wb"); ! 317: if (!fp_dac_input) { ! 318: hw_error("Unable to open lm4549_dac_input.pcm for writing\n"); ! 319: } ! 320: #endif ! 321: } ! 322: ! 323: const VMStateDescription vmstate_lm4549_state = { ! 324: .name = "lm4549_state", ! 325: .version_id = 1, ! 326: .minimum_version_id = 1, ! 327: .minimum_version_id_old = 1, ! 328: .post_load = &lm4549_post_load, ! 329: .fields = (VMStateField[]) { ! 330: VMSTATE_UINT32(voice_is_active, lm4549_state), ! 331: VMSTATE_UINT16_ARRAY(regfile, lm4549_state, 128), ! 332: VMSTATE_UINT16_ARRAY(buffer, lm4549_state, LM4549_BUFFER_SIZE), ! 333: VMSTATE_UINT32(buffer_level, lm4549_state), ! 334: VMSTATE_END_OF_LIST() ! 335: } ! 336: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.