qemu-devel
[Top][All Lists]
Advanced

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

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


From: Dante
Subject: [Qemu-devel] [PATCH] hw/wm8731: add WM8731 codec support
Date: Fri, 18 Jan 2013 14:23:28 +0800

Wolfson WM8731 is a simple audio codec for embedded systems.
It contains 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>
---
 default-configs/arm-softmmu.mak |    1 +
 hw/Makefile.objs                |    1 +
 hw/i2c.h                        |    6 +
 hw/wm8731.c                     |  478 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 486 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 74b07a7..21c9f6b 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -165,6 +165,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..1b11fcd
--- /dev/null
+++ b/hw/wm8731.c
@@ -0,0 +1,478 @@
+/*
+ * 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.
+ */
+
+#include "hw.h"
+#include "i2c.h"
+#include "audio/audio.h"
+
+#define IN_PORT_N    2
+#define OUT_PORT_N    1
+
+#define CODEC        "wm8731"
+
+typedef struct {
+    int adc;
+    int adc_hz;
+    int dac;
+    int dac_hz;
+} WMRate;
+
+typedef struct {
+    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;
+} WM8731State;
+
+#define WM8731_OUTVOL_TRANSFORM(x)    (x << 1)
+#define WM8731_INVOL_TRANSFORM(x)    (x << 3)
+
+static inline void wm8731_in_load(WM8731State *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(WM8731State *s)
+{
+    int sent = 0;
+    while (sent < s->idx_out)
+        sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent)
+                ?: s->idx_out;
+    s->idx_out = 0;
+}
+
+static void wm8731_audio_in_cb(void *opaque, int avail_b)
+{
+    WM8731State *s = (WM8731State *) 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)
+{
+    WM8731State *s = (WM8731State *) 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(WM8731State *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(WM8731State *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],
+                    CODEC ".line-in", s, wm8731_audio_in_cb, &in_fmt);
+    s->adc_voice[1] = AUD_open_in(&s->card, s->adc_voice[1],
+                    CODEC ".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],
+                    CODEC ".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(WM8731State *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)
+{
+    WM8731State *s = (WM8731State *) 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)
+{
+    WM8731State *s = (WM8731State *) i2c;
+
+    switch (event) {
+    case I2C_START_SEND:
+        s->i2c_len = 0;
+        break;
+    case I2C_FINISH:
+#ifdef VERBOSE
+        if (s->i2c_len < 2)
+            printf("%s: message too short (%i bytes)\n",
+                            __FUNCTION__, 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)
+{
+    WM8731State *s = (WM8731State *) 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("%s: unknown register %02x\n", __FUNCTION__, cmd);
+#endif
+    }
+
+    return 0;
+}
+
+static int wm8731_rx(I2CSlave *i2c)
+{
+    return 0x00;
+}
+
+static void wm8731_pre_save(void *opaque)
+{
+    WM8731State *s = opaque;
+
+    s->rate_vmstate = s->rate - wm_rate_table;
+}
+
+static int wm8731_post_load(void *opaque, int version_id)
+{
+    WM8731State *s = opaque;
+
+    s->rate = &wm_rate_table[s->rate_vmstate & 0x1f];
+    return 0;
+}
+
+static const VMStateDescription vmstate_wm8731 = {
+    .name = CODEC,
+    .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, WM8731State, 2),
+        VMSTATE_INT32(i2c_len, WM8731State),
+        VMSTATE_INT32(idx_in, WM8731State),
+        VMSTATE_INT32(req_in, WM8731State),
+        VMSTATE_INT32(idx_out, WM8731State),
+        VMSTATE_INT32(req_out, WM8731State),
+        VMSTATE_UINT8_ARRAY(outvol, WM8731State, 2),
+        VMSTATE_UINT8_ARRAY(invol, WM8731State, 2),
+        VMSTATE_UINT8_ARRAY(inmute, WM8731State, 2),
+        VMSTATE_UINT8(mutemic, WM8731State),
+        VMSTATE_UINT8(mute, WM8731State),
+        VMSTATE_UINT8(format, WM8731State),
+        VMSTATE_UINT8(power, WM8731State),
+        VMSTATE_UINT8(active, WM8731State),
+        VMSTATE_UINT8(rate_vmstate, WM8731State),
+        VMSTATE_I2C_SLAVE(i2c, WM8731State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int wm8731_init(I2CSlave *i2c)
+{
+    WM8731State *s = FROM_I2C_SLAVE(WM8731State, i2c);
+
+    AUD_register_card(CODEC, &s->card);
+    wm8731_reset(&s->i2c);
+
+    return 0;
+}
+
+#if 0
+static void wm8731_fini(I2CSlave *i2c)
+{
+    WM8731State *s = (WM8731State *) i2c;
+    wm8731_reset(&s->i2c);
+    AUD_remove_card(&s->card);
+    g_free(s);
+}
+#endif
+
+void wm8731_data_req_set(DeviceState *dev,
+                void (*data_req)(void *, int, int), void *opaque)
+{
+    WM8731State *s = FROM_I2C_SLAVE(WM8731State, I2C_SLAVE_FROM_QDEV(dev));
+    s->data_req = data_req;
+    s->opaque = opaque;
+}
+
+void wm8731_dac_dat(void *opaque, uint32_t sample)
+{
+    WM8731State *s = (WM8731State *) opaque;
+    /* 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)
+{
+    WM8731State *s = (WM8731State *) 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 = {
+    .name          = "wm8731",
+    .parent        = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(WM8731State),
+    .class_init    = wm8731_class_init,
+};
+
+static void wm8731_register_types(void)
+{
+    type_register_static(&wm8731_info);
+}
+
+type_init(wm8731_register_types)
-- 
1.7.9.5


********************* Confidentiality Notice ************************
This electronic message and any attachments may contain
confidential and legally privileged information or
information which is otherwise protected from disclosure.
If you are not the intended recipient,please do not disclose
the contents, either in whole or in part, to anyone,and
immediately delete the message and any attachments from
your computer system and destroy all hard copies.
Thank you for your cooperation.
***********************************************************************




reply via email to

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