Am 25.01.2013 08:16, schrieb Kuo-Jung Su: > From: Kuo-Jung Su <dant...@faraday-tech.com> > > 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 <dant...@faraday-tech.com> > --- > > 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 <dant...@faraday-tech.com> > + * > + * 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