qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v2] hw/wm8731: add WM8731 codec support


From: Andreas Färber
Subject: Re: [Qemu-devel] [PATCH v2] hw/wm8731: add WM8731 codec support
Date: Fri, 25 Jan 2013 10:18:43 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130105 Thunderbird/17.0.2

Am 25.01.2013 08:16, schrieb Kuo-Jung Su:
> From: Kuo-Jung Su <address@hidden>
> 
> Wolfson WM8731 is a simple audio codec for embedded systems.
> It has 2 input and 1 output ports:
> 
> ** Input **
>     1. Linue-In
>     2. Microphone
> 
> ** Output **
>     1. Headphone out
> 
> BTW it's based on hw/wm8750.c with 16bit I2S support by default.
> 
> Signed-off-by: Kuo-Jung Su <address@hidden>
> ---
> 
> Changes for v2:
>    - introduce QOM coding style
>    - coding style fixes, howeve there are still some false alarms.
>      for examples:
>      
>      ERROR: need consistent spacing around '*' (ctx:WxV)
>      #9997: FILE: hw/wm8731.c:33:
>      +    SWVoiceIn *adc_voice[IN_PORT_N];
> 
> ---
>  default-configs/arm-softmmu.mak |    1 +
>  hw/Makefile.objs                |    1 +
>  hw/i2c.h                        |    6 +
>  hw/wm8731.c                     |  488 
> +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 496 insertions(+)
>  create mode 100644 hw/wm8731.c
> 
> diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
> index 2f1a5c9..c86d0f2 100644
> --- a/default-configs/arm-softmmu.mak
> +++ b/default-configs/arm-softmmu.mak
> @@ -10,6 +10,7 @@ CONFIG_SERIAL=y
>  CONFIG_PTIMER=y
>  CONFIG_SD=y
>  CONFIG_MAX7310=y
> +CONFIG_WM8731=y
>  CONFIG_WM8750=y
>  CONFIG_TWL92230=y
>  CONFIG_TSC2005=y
> diff --git a/hw/Makefile.objs b/hw/Makefile.objs
> index 23ac249..1da2a34 100644
> --- a/hw/Makefile.objs
> +++ b/hw/Makefile.objs
> @@ -166,6 +166,7 @@ common-obj-$(CONFIG_REALLY_VIRTFS) += 9pfs/
>  common-obj-y += usb/
>  common-obj-$(CONFIG_PTIMER) += ptimer.o
>  common-obj-$(CONFIG_MAX7310) += max7310.o
> +common-obj-$(CONFIG_WM8731) += wm8731.o
>  common-obj-$(CONFIG_WM8750) += wm8750.o
>  common-obj-$(CONFIG_TWL92230) += twl92230.o
>  common-obj-$(CONFIG_TSC2005) += tsc2005.o
> diff --git a/hw/i2c.h b/hw/i2c.h
> index 883b5c5..89d63cd 100644
> --- a/hw/i2c.h
> +++ b/hw/i2c.h
> @@ -64,6 +64,12 @@ int i2c_recv(i2c_bus *bus);
>  
>  DeviceState *i2c_create_slave(i2c_bus *bus, const char *name, uint8_t addr);
>  
> +/* wm8731.c */
> +void wm8731_data_req_set(DeviceState *dev,
> +                void (*data_req)(void *, int, int), void *opaque);
> +void wm8731_dac_dat(void *opaque, uint32_t sample);
> +uint32_t wm8731_adc_dat(void *opaque);
> +
>  /* wm8750.c */
>  void wm8750_data_req_set(DeviceState *dev,
>                  void (*data_req)(void *, int, int), void *opaque);
> diff --git a/hw/wm8731.c b/hw/wm8731.c
> new file mode 100644
> index 0000000..328cb7f
> --- /dev/null
> +++ b/hw/wm8731.c
> @@ -0,0 +1,488 @@
> +/*
> + * WM8731 audio CODEC.
> + *
> + * base is wm8750.c
> + *
> + * Copyright (c) 2013 Faraday Technology
> + * Written by Dante Su <address@hidden>
> + *
> + * This file is licensed under GNU GPL v2.
> + */
> +
> +#include "hw.h"
> +#include "i2c.h"
> +#include "audio/audio.h"
> +
> +#define IN_PORT_N   2
> +#define OUT_PORT_N  1
> +
> +#define TYPE_WM8731 "wm8731"
> +
> +typedef struct WMRate {
> +    int adc;
> +    int adc_hz;
> +    int dac;
> +    int dac_hz;
> +} wmrate;
> +
> +typedef struct WM8731State {
> +    I2CSlave i2c;
> +    uint8_t i2c_data[2];
> +    int i2c_len;
> +    QEMUSoundCard card;
> +    SWVoiceIn *adc_voice[IN_PORT_N];
> +    SWVoiceOut *dac_voice[OUT_PORT_N];
> +    void (*data_req)(void *, int, int);
> +    void *opaque;
> +    uint8_t data_in[4096];
> +    uint8_t data_out[4096];
> +    int idx_in, req_in;
> +    int idx_out, req_out;
> +
> +    SWVoiceOut **out[2];
> +    uint8_t outvol[2];
> +    SWVoiceIn **in[2];
> +    uint8_t invol[2], inmute[2], mutemic;
> +
> +    uint8_t mute;
> +    uint8_t power, format, active;
> +    const wmrate *rate;
> +    uint8_t rate_vmstate;
> +    int adc_hz, dac_hz, ext_adc_hz, ext_dac_hz, master;
> +} wm8731_state;
> +
> +#define WM8731(obj) \
> +    OBJECT_CHECK(wm8731_state, obj, TYPE_WM8731)

While I appreciate you adding the QOM macro here, the CamelCase type
name WM8731State in v1 was totally correct, please change it back. Same
for WMRate and any other type names.

> +
> +#define WM8731_OUTVOL_TRANSFORM(x)  (x << 1)
> +#define WM8731_INVOL_TRANSFORM(x)   (x << 3)
> +
> +static inline void wm8731_in_load(wm8731_state *s)
> +{
> +    if (s->idx_in + s->req_in <= sizeof(s->data_in)) {
> +        return;
> +    }
> +    s->idx_in = audio_MAX(0, (int) sizeof(s->data_in) - s->req_in);
> +    AUD_read(*s->in[0], s->data_in + s->idx_in,
> +             sizeof(s->data_in) - s->idx_in);
> +}
> +
> +static inline void wm8731_out_flush(wm8731_state *s)
> +{
> +    int sent = 0;
> +    while (sent < s->idx_out) {
> +        sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent)
> +              ? 0 : s->idx_out;
> +    }
> +    s->idx_out = 0;
> +}
> +
> +static void wm8731_audio_in_cb(void *opaque, int avail_b)
> +{
> +    wm8731_state *s = WM8731(opaque);
> +    s->req_in = avail_b;
> +    /* 16 bit samples */
> +    s->data_req(s->opaque, s->req_out >> 1, avail_b >> 1);
> +}
> +
> +static void wm8731_audio_out_cb(void *opaque, int free_b)
> +{
> +    wm8731_state *s = WM8731(opaque);
> +
> +    if (s->idx_out >= free_b) {
> +        s->idx_out = free_b;
> +        s->req_out = 0;
> +        wm8731_out_flush(s);
> +    } else {
> +        s->req_out = free_b - s->idx_out;
> +    }
> +    /* 16 bit samples */
> +    s->data_req(s->opaque, s->req_out >> 1, s->req_in >> 1);
> +}
> +
> +static const wmrate wm_rate_table[] = {
> +    {  256, 48000,  256, 48000 },    /* SR: 0000, BOSR: 0 */
> +    {  384, 48000,  384, 48000 },    /* SR: 0000, BOSR: 1 */
> +    {  256, 48000,  256,  8000 },    /* SR: 0001, BOSR: 0 */
> +    {  384, 48000,  384,  8000 },    /* SR: 0001, BOSR: 1 */
> +    {  256,  8000,  256, 48000 },    /* SR: 0010, BOSR: 0 */
> +    {  384,  8000,  384, 48000 },    /* SR: 0010, BOSR: 1 */
> +    {  256,  8000,  256,  8000 },    /* SR: 0011, BOSR: 0 */
> +    {  384,  8000,  384,  8000 },    /* SR: 0011, BOSR: 1 */
> +    {  256, 32000,  256, 32000 },    /* SR: 0110, BOSR: 0 */
> +    {  384, 32000,  384, 32000 },    /* SR: 0110, BOSR: 1 */
> +    {  128, 96000,  128, 96000 },    /* SR: 0111, BOSR: 0 */
> +    {  192, 96000,  192, 96000 },    /* SR: 0111, BOSR: 1 */
> +    {  256, 44100,  256, 44100 },    /* SR: 1000, BOSR: 0 */
> +    {  384, 44100,  384, 44100 },    /* SR: 1000, BOSR: 1 */
> +    {  256, 44100,  256,  8000 },    /* SR: 1001, BOSR: 0 */
> +    {  384, 44100,  384,  8000 },    /* SR: 1001, BOSR: 1 */
> +    {  256,  8000,  256, 44100 },    /* SR: 1010, BOSR: 0 */
> +    {  384,  8000,  384, 44100 },    /* SR: 1010, BOSR: 1 */
> +    {  256,  8000,  256,  8000 },    /* SR: 1011, BOSR: 0 */
> +    {  384,  8000,  384,  8000 },    /* SR: 1011, BOSR: 1 */
> +    {  128, 88200,  128, 88200 },    /* SR: 1011, BOSR: 0 */
> +    {  192, 88200,  192, 88200 },    /* SR: 1011, BOSR: 1 */
> +};
> +
> +static void wm8731_vol_update(wm8731_state *s)
> +{
> +    AUD_set_volume_in(s->adc_voice[0], s->mute,
> +                      s->inmute[0] ? 0 : WM8731_INVOL_TRANSFORM(s->invol[0]),
> +                      s->inmute[1] ? 0 : 
> WM8731_INVOL_TRANSFORM(s->invol[1]));
> +    AUD_set_volume_in(s->adc_voice[1], s->mute,
> +                      s->mutemic ? 0 : WM8731_INVOL_TRANSFORM(s->invol[0]),
> +                      s->mutemic ? 0 : WM8731_INVOL_TRANSFORM(s->invol[1]));
> +
> +    /* Headphone: LOUT1VOL ROUT1VOL */
> +    AUD_set_volume_out(s->dac_voice[0], s->mute,
> +                       WM8731_OUTVOL_TRANSFORM(s->outvol[0]),
> +                       WM8731_OUTVOL_TRANSFORM(s->outvol[1]));
> +}
> +
> +static void wm8731_set_format(wm8731_state *s)
> +{
> +    int i;
> +    struct audsettings in_fmt;
> +    struct audsettings out_fmt;
> +
> +    wm8731_out_flush(s);
> +
> +    if (s->in[0] && *s->in[0]) {
> +        AUD_set_active_in(*s->in[0], 0);
> +    }
> +    if (s->out[0] && *s->out[0]) {
> +        AUD_set_active_out(*s->out[0], 0);
> +    }
> +
> +    for (i = 0; i < IN_PORT_N; i++) {
> +        if (s->adc_voice[i]) {
> +            AUD_close_in(&s->card, s->adc_voice[i]);
> +            s->adc_voice[i] = NULL;
> +        }
> +    }
> +    for (i = 0; i < OUT_PORT_N; i++) {
> +        if (s->dac_voice[i]) {
> +            AUD_close_out(&s->card, s->dac_voice[i]);
> +            s->dac_voice[i] = NULL;
> +        }
> +    }
> +
> +    /* Setup input */
> +    in_fmt.endianness = 0;
> +    in_fmt.nchannels = 2;
> +    in_fmt.freq = s->adc_hz;
> +    in_fmt.fmt = AUD_FMT_S16;
> +
> +    s->adc_voice[0] = AUD_open_in(&s->card, s->adc_voice[0],
> +                TYPE_WM8731 ".line-in", s, wm8731_audio_in_cb, &in_fmt);
> +    s->adc_voice[1] = AUD_open_in(&s->card, s->adc_voice[1],
> +                TYPE_WM8731 ".microphone", s, wm8731_audio_in_cb, &in_fmt);
> +
> +    /* Setup output */
> +    out_fmt.endianness = 0;
> +    out_fmt.nchannels = 2;
> +    out_fmt.freq = s->dac_hz;
> +    out_fmt.fmt = AUD_FMT_S16;
> +
> +    s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0],
> +                TYPE_WM8731 ".headphone", s, wm8731_audio_out_cb, &out_fmt);
> +
> +    wm8731_vol_update(s);
> +
> +    /* We should connect the left and right channels to their
> +     * respective inputs/outputs but we have completely no need
> +     * for mixing or combining paths to different ports, so we
> +     * connect both channels to where the left channel is routed.  */
> +    if (s->in[0] && *s->in[0]) {
> +        AUD_set_active_in(*s->in[0], 1);
> +    }
> +    if (s->out[0] && *s->out[0]) {
> +        AUD_set_active_out(*s->out[0], 1);
> +    }
> +}
> +
> +static void wm8731_clk_update(wm8731_state *s, int ext)
> +{
> +    if (s->master || !s->ext_dac_hz) {
> +        s->dac_hz = s->rate->dac_hz;
> +    } else {
> +        s->dac_hz = s->ext_dac_hz;
> +    }
> +    if (s->master || !s->ext_adc_hz) {
> +        s->adc_hz = s->rate->adc_hz;
> +    } else {
> +        s->adc_hz = s->ext_adc_hz;
> +    }
> +    if (s->master || (!s->ext_dac_hz && !s->ext_adc_hz)) {
> +        if (!ext) {
> +            wm8731_set_format(s);
> +        }
> +    } else {
> +        if (ext) {
> +            wm8731_set_format(s);
> +        }
> +    }
> +}
> +
> +static void wm8731_reset(I2CSlave *i2c)
> +{
> +    wm8731_state *s = WM8731(i2c);
> +    s->rate = &wm_rate_table[0];
> +    wm8731_clk_update(s, 1);
> +    s->in[0] = &s->adc_voice[0];
> +    s->invol[0] = 0x17;
> +    s->invol[1] = 0x17;
> +    s->out[0] = &s->dac_voice[0];
> +    s->outvol[0] = 0x39;
> +    s->outvol[1] = 0x39;
> +    s->inmute[0] = 0;
> +    s->inmute[1] = 0;
> +    s->mutemic = 1;
> +    s->mute = 1;
> +    s->power = 0x9f;
> +    s->format = 0x02;    /* I2S, 16-bits */
> +    s->active = 0;
> +    s->idx_in = sizeof(s->data_in);
> +    s->req_in = 0;
> +    s->idx_out = 0;
> +    s->req_out = 0;
> +    wm8731_vol_update(s);
> +    s->i2c_len = 0;
> +}
> +
> +static void wm8731_event(I2CSlave *i2c, enum i2c_event event)
> +{
> +    wm8731_state *s = WM8731(i2c);
> +
> +    switch (event) {
> +    case I2C_START_SEND:
> +        s->i2c_len = 0;
> +        break;
> +    case I2C_FINISH:
> +#ifdef VERBOSE
> +        if (s->i2c_len < 2) {
> +            printf("wm8731_event: message too short (%i bytes)\n",
> +                   s->i2c_len);
> +        }
> +#endif
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +#define WM8731_LINVOL   0x00
> +#define WM8731_RINVOL   0x01
> +#define WM8731_LOUT1V   0x02
> +#define WM8731_ROUT1V   0x03
> +#define WM8731_APANA    0x04
> +#define WM8731_APDIGI   0x05
> +#define WM8731_PWR      0x06
> +#define WM8731_IFACE    0x07
> +#define WM8731_SRATE    0x08
> +#define WM8731_ACTIVE   0x09
> +#define WM8731_RESET    0x0f
> +
> +static int wm8731_tx(I2CSlave *i2c, uint8_t data)
> +{
> +    wm8731_state *s = WM8731(i2c);
> +    uint8_t cmd;
> +    uint16_t value;
> +
> +    if (s->i2c_len >= 2) {
> +#ifdef VERBOSE
> +        printf("%s: long message (%i bytes)\n", __func__, s->i2c_len);
> +#endif
> +        return 1;
> +    }
> +    s->i2c_data[s->i2c_len++] = data;
> +    if (s->i2c_len != 2) {
> +        return 0;
> +    }
> +
> +    cmd = s->i2c_data[0] >> 1;
> +    value = ((s->i2c_data[0] << 8) | s->i2c_data[1]) & 0x1ff;
> +
> +    switch (cmd) {
> +    case WM8731_LINVOL:
> +        s->invol[0] = value & 0x1f;         /* LINVOL */
> +        s->inmute[0] = (value >> 7) & 1;    /* LINMUTE */
> +        wm8731_vol_update(s);
> +        break;
> +    case WM8731_RINVOL:
> +        s->invol[1] = value & 0x1f;         /* RINVOL */
> +        s->inmute[1] = (value >> 7) & 1;    /* RINMUTE */
> +        wm8731_vol_update(s);
> +        break;
> +    case WM8731_LOUT1V:
> +        s->outvol[0] = value & 0x7f;        /* LHPVOL */
> +        wm8731_vol_update(s);
> +        break;
> +    case WM8731_ROUT1V:
> +        s->outvol[1] = value & 0x7f;        /* RHPVOL */
> +        wm8731_vol_update(s);
> +        break;
> +    case WM8731_APANA:
> +        s->mutemic = (value >> 1) & 1;      /* MUTEMIC */
> +        if (value & 0x04) {
> +            s->in[0] = &s->adc_voice[1];    /* MIC */
> +        } else {
> +            s->in[0] = &s->adc_voice[0];    /* LINE-IN */
> +        }
> +        break;
> +    case WM8731_APDIGI:
> +        if (s->mute != ((value >> 3) & 1)) {
> +            s->mute = (value >> 3) & 1;            /* DACMU */
> +            wm8731_vol_update(s);
> +        }
> +        break;
> +    case WM8731_PWR:
> +        s->power = (uint8_t)(value & 0xff);
> +        wm8731_set_format(s);
> +        break;
> +    case WM8731_IFACE:
> +        s->format = value;
> +        s->master = (value >> 6) & 1;            /* MS */
> +        wm8731_clk_update(s, s->master);
> +        break;
> +    case WM8731_SRATE:
> +        s->rate = &wm_rate_table[(value >> 1) & 0x1f];
> +        wm8731_clk_update(s, 0);
> +        break;
> +    case WM8731_ACTIVE:
> +        s->active = (uint8_t)(value & 1);
> +        break;
> +    case WM8731_RESET:    /* Reset */
> +        if (value == 0) {
> +            wm8731_reset(&s->i2c);
> +        }
> +        break;
> +
> +#ifdef VERBOSE
> +    default:
> +        printf("wm8731_tx: unknown register %02x\n", cmd);
> +#endif
> +    }
> +
> +    return 0;
> +}
> +
> +static int wm8731_rx(I2CSlave *i2c)
> +{
> +    return 0x00;
> +}
> +
> +static void wm8731_pre_save(void *opaque)
> +{
> +    wm8731_state *s = WM8731(opaque);
> +
> +    s->rate_vmstate = s->rate - wm_rate_table;
> +}
> +
> +static int wm8731_post_load(void *opaque, int version_id)
> +{
> +    wm8731_state *s = WM8731(opaque);
> +
> +    s->rate = &wm_rate_table[s->rate_vmstate & 0x1f];
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_wm8731 = {
> +    .name = TYPE_WM8731,

This VMState name does not need to match the type name, I'm not aware of
such uses today... Juan, should we avoid this coupling or is it okay?

> +    .version_id = 0,
> +    .minimum_version_id = 0,
> +    .minimum_version_id_old = 0,
> +    .pre_save  = wm8731_pre_save,
> +    .post_load = wm8731_post_load,
> +    .fields    = (VMStateField[]) {
> +        VMSTATE_UINT8_ARRAY(i2c_data, wm8731_state, 2),
> +        VMSTATE_INT32(i2c_len, wm8731_state),
> +        VMSTATE_INT32(idx_in, wm8731_state),
> +        VMSTATE_INT32(req_in, wm8731_state),
> +        VMSTATE_INT32(idx_out, wm8731_state),
> +        VMSTATE_INT32(req_out, wm8731_state),
> +        VMSTATE_UINT8_ARRAY(outvol, wm8731_state, 2),
> +        VMSTATE_UINT8_ARRAY(invol, wm8731_state, 2),
> +        VMSTATE_UINT8_ARRAY(inmute, wm8731_state, 2),
> +        VMSTATE_UINT8(mutemic, wm8731_state),
> +        VMSTATE_UINT8(mute, wm8731_state),
> +        VMSTATE_UINT8(format, wm8731_state),
> +        VMSTATE_UINT8(power, wm8731_state),
> +        VMSTATE_UINT8(active, wm8731_state),
> +        VMSTATE_UINT8(rate_vmstate, wm8731_state),
> +        VMSTATE_I2C_SLAVE(i2c, wm8731_state),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static int wm8731_init(I2CSlave *i2c)
> +{
> +    wm8731_state *s = WM8731(FROM_I2C_SLAVE(wm8731_state, i2c));

Just do WM8731(i2c) here please.

> +
> +    AUD_register_card(TYPE_WM8731, &s->card);
> +    wm8731_reset(&s->i2c);
> +
> +    return 0;
> +}
> +
> +void wm8731_data_req_set(DeviceState *dev,
> +                         void (*data_req)(void *, int, int), void *opaque)
> +{
> +    wm8731_state *s = FROM_I2C_SLAVE(wm8731_state, I2C_SLAVE_FROM_QDEV(dev));

Same here.

> +    s->data_req = data_req;
> +    s->opaque = opaque;
> +}
> +
> +void wm8731_dac_dat(void *opaque, uint32_t sample)
> +{
> +    wm8731_state *s = WM8731(opaque);

Note if dac / adc were performance-sensitive, you may choose to just do:
WM8731State *s = opaque;

I assume you have tested it, so you can leave it as is of course.

> +    /* 16-bit samples */
> +    *(uint16_t *) &s->data_out[s->idx_out] = (uint16_t)sample;
> +    s->req_out -= 2;
> +    s->idx_out += 2;
> +    if (s->idx_out >= sizeof(s->data_out) || s->req_out <= 0) {
> +        wm8731_out_flush(s);
> +    }
> +}
> +
> +uint32_t wm8731_adc_dat(void *opaque)
> +{
> +    wm8731_state *s = WM8731(opaque);
> +    uint16_t sample;
> +
> +    if (s->idx_in >= sizeof(s->data_in)) {
> +        wm8731_in_load(s);
> +    }
> +
> +    sample = *(uint16_t *) &s->data_in[s->idx_in];
> +    s->req_in -= 2;
> +    s->idx_in += 2;
> +    return sample;
> +}
> +
> +static void wm8731_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
> +
> +    sc->init  = wm8731_init;
> +    sc->event = wm8731_event;
> +    sc->recv  = wm8731_rx;
> +    sc->send  = wm8731_tx;
> +    dc->vmsd  = &vmstate_wm8731;
> +}
> +
> +static TypeInfo wm8731_info = {

static const

> +    .name          = TYPE_WM8731,
> +    .parent        = TYPE_I2C_SLAVE,
> +    .instance_size = sizeof(wm8731_state),
> +    .class_init    = wm8731_class_init,
> +};
> +
> +static void wm8731_register_types(void)
> +{
> +    type_register_static(&wm8731_info);
> +}
> +
> +type_init(wm8731_register_types)

Regards,
Andreas

-- 
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg



reply via email to

[Prev in Thread] Current Thread [Next in Thread]