From patchwork Fri Jan 18 06:23:28 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dante X-Patchwork-Id: 213478 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id C41792C0087 for ; Fri, 18 Jan 2013 17:23:57 +1100 (EST) Received: from localhost ([::1]:45399 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5Mp-0005AT-Tk for incoming@patchwork.ozlabs.org; Fri, 18 Jan 2013 01:23:55 -0500 Received: from eggs.gnu.org ([208.118.235.92]:37279) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5Me-0005AM-RA for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:23:48 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Tw5Ma-000369-VU for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:23:44 -0500 Received: from ftcsun4.faraday-tech.com ([60.248.181.187]:56129 helo=ftcsun4.faraday.com.tw) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5Ma-00035j-AO for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:23:40 -0500 Received: from ftcpcs100.faraday.com.tw ([192.168.31.10]) by ftcsun4.faraday.com.tw (8.11.7p1+Sun/8.11.6) with ESMTP id r0I6NZm07099 for ; Fri, 18 Jan 2013 14:23:36 +0800 (CST) Received: from FTCPCS63.faraday.com.tw (unverified [192.168.32.63]) by ftcpcs100.faraday.com.tw (Clearswift SMTPRS 5.2.9) with ESMTP id for ; Fri, 18 Jan 2013 14:23:36 +0800 Received: from vmware.faraday.com.tw (192.168.65.96) by FTCPCS63.faraday.com.tw (192.168.32.65) with Microsoft SMTP Server (TLS) id 14.2.328.9; Fri, 18 Jan 2013 14:23:36 +0800 From: Dante To: Date: Fri, 18 Jan 2013 14:23:28 +0800 Message-ID: <1358490208-18136-1-git-send-email-dantesu@faraday-tech.com> X-Mailer: git-send-email 1.7.9.5 MIME-Version: 1.0 X-Originating-IP: [192.168.65.96] X-KSE-Antivirus-Interceptor-Info: scan successful X-KSE-Antivirus-Info: Clean X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 60.248.181.187 Cc: dantesu@faraday-tech.com Subject: [Qemu-devel] [PATCH] hw/wm8731: add WM8731 codec support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 --- 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 + * + * 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)