Annotation of qemu/hw/lm4549.c, revision 1.1

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: };

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.