qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

lm4549.c (9205B)


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