Commit adb86c372e1596c07437682ff7aa71c905dbc14f
1 parent
3f582262
Add WM8750 and MAX7310 chips (I2C slaves).
Wolfson Microsystems WM8750 audio chip and Maxim MAX7310 gpio expander chip are used in the Spitz. git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2854 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
5 changed files
with
805 additions
and
2 deletions
Makefile.target
| ... | ... | @@ -459,8 +459,8 @@ VL_OBJS+= versatile_pci.o sd.o ptimer.o |
| 459 | 459 | VL_OBJS+= arm_gic.o realview.o arm_sysctl.o |
| 460 | 460 | VL_OBJS+= arm-semi.o |
| 461 | 461 | VL_OBJS+= pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o |
| 462 | -VL_OBJS+= pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o max111x.o | |
| 463 | -VL_OBJS+= spitz.o ads7846.o ide.o serial.o nand.o $(AUDIODRV) | |
| 462 | +VL_OBJS+= pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o max111x.o max7310.o | |
| 463 | +VL_OBJS+= spitz.o ads7846.o ide.o serial.o nand.o $(AUDIODRV) wm8750.o | |
| 464 | 464 | CPPFLAGS += -DHAS_AUDIO |
| 465 | 465 | endif |
| 466 | 466 | ifeq ($(TARGET_BASE_ARCH), sh4) | ... | ... |
hw/i2c.h
| ... | ... | @@ -46,4 +46,18 @@ void i2c_nack(i2c_bus *bus); |
| 46 | 46 | int i2c_send(i2c_bus *bus, uint8_t data); |
| 47 | 47 | int i2c_recv(i2c_bus *bus); |
| 48 | 48 | |
| 49 | +/* max7310.c */ | |
| 50 | +i2c_slave *max7310_init(i2c_bus *bus); | |
| 51 | +void max7310_reset(i2c_slave *i2c); | |
| 52 | +qemu_irq *max7310_gpio_in_get(i2c_slave *i2c); | |
| 53 | +void max7310_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler); | |
| 54 | + | |
| 55 | +/* wm8750.c */ | |
| 56 | +i2c_slave *wm8750_init(i2c_bus *bus, AudioState *audio); | |
| 57 | +void wm8750_reset(i2c_slave *i2c); | |
| 58 | +void wm8750_data_req_set(i2c_slave *i2c, | |
| 59 | + void (*data_req)(void *, int, int), void *opaque); | |
| 60 | +void wm8750_dac_dat(void *opaque, uint32_t sample); | |
| 61 | +uint32_t wm8750_adc_dat(void *opaque); | |
| 62 | + | |
| 49 | 63 | #endif | ... | ... |
hw/max7310.c
0 → 100644
| 1 | +/* | |
| 2 | + * MAX7310 8-port GPIO expansion chip. | |
| 3 | + * | |
| 4 | + * Copyright (c) 2006 Openedhand Ltd. | |
| 5 | + * Written by Andrzej Zaborowski <balrog@zabor.org> | |
| 6 | + * | |
| 7 | + * This file is licensed under GNU GPL. | |
| 8 | + */ | |
| 9 | + | |
| 10 | +#include "vl.h" | |
| 11 | + | |
| 12 | +struct max7310_s { | |
| 13 | + i2c_slave i2c; | |
| 14 | + int i2c_command_byte; | |
| 15 | + int len; | |
| 16 | + | |
| 17 | + uint8_t level; | |
| 18 | + uint8_t direction; | |
| 19 | + uint8_t polarity; | |
| 20 | + uint8_t status; | |
| 21 | + uint8_t command; | |
| 22 | + qemu_irq handler[8]; | |
| 23 | + qemu_irq *gpio_in; | |
| 24 | +}; | |
| 25 | + | |
| 26 | +void max7310_reset(i2c_slave *i2c) | |
| 27 | +{ | |
| 28 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 29 | + s->level &= s->direction; | |
| 30 | + s->direction = 0xff; | |
| 31 | + s->polarity = 0xf0; | |
| 32 | + s->status = 0x01; | |
| 33 | + s->command = 0x00; | |
| 34 | +} | |
| 35 | + | |
| 36 | +static int max7310_rx(i2c_slave *i2c) | |
| 37 | +{ | |
| 38 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 39 | + | |
| 40 | + switch (s->command) { | |
| 41 | + case 0x00: /* Input port */ | |
| 42 | + return s->level ^ s->polarity; | |
| 43 | + break; | |
| 44 | + | |
| 45 | + case 0x01: /* Output port */ | |
| 46 | + return s->level & ~s->direction; | |
| 47 | + break; | |
| 48 | + | |
| 49 | + case 0x02: /* Polarity inversion */ | |
| 50 | + return s->polarity; | |
| 51 | + | |
| 52 | + case 0x03: /* Configuration */ | |
| 53 | + return s->direction; | |
| 54 | + | |
| 55 | + case 0x04: /* Timeout */ | |
| 56 | + return s->status; | |
| 57 | + break; | |
| 58 | + | |
| 59 | + case 0xff: /* Reserved */ | |
| 60 | + return 0xff; | |
| 61 | + | |
| 62 | + default: | |
| 63 | +#ifdef VERBOSE | |
| 64 | + printf("%s: unknown register %02x\n", __FUNCTION__, s->command); | |
| 65 | +#endif | |
| 66 | + break; | |
| 67 | + } | |
| 68 | + return 0xff; | |
| 69 | +} | |
| 70 | + | |
| 71 | +static int max7310_tx(i2c_slave *i2c, uint8_t data) | |
| 72 | +{ | |
| 73 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 74 | + uint8_t diff; | |
| 75 | + int line; | |
| 76 | + | |
| 77 | + if (s->len ++ > 1) { | |
| 78 | +#ifdef VERBOSE | |
| 79 | + printf("%s: message too long (%i bytes)\n", __FUNCTION__, s->len); | |
| 80 | +#endif | |
| 81 | + return 1; | |
| 82 | + } | |
| 83 | + | |
| 84 | + if (s->i2c_command_byte) { | |
| 85 | + s->command = data; | |
| 86 | + s->i2c_command_byte = 0; | |
| 87 | + return 0; | |
| 88 | + } | |
| 89 | + | |
| 90 | + switch (s->command) { | |
| 91 | + case 0x01: /* Output port */ | |
| 92 | + for (diff = (data ^ s->level) & ~s->direction; diff; | |
| 93 | + diff &= ~(1 << line)) { | |
| 94 | + line = ffs(diff) - 1; | |
| 95 | + if (s->handler[line]) | |
| 96 | + qemu_set_irq(s->handler[line], (data >> line) & 1); | |
| 97 | + } | |
| 98 | + s->level = (s->level & s->direction) | (data & ~s->direction); | |
| 99 | + break; | |
| 100 | + | |
| 101 | + case 0x02: /* Polarity inversion */ | |
| 102 | + s->polarity = data; | |
| 103 | + break; | |
| 104 | + | |
| 105 | + case 0x03: /* Configuration */ | |
| 106 | + s->level &= ~(s->direction ^ data); | |
| 107 | + s->direction = data; | |
| 108 | + break; | |
| 109 | + | |
| 110 | + case 0x04: /* Timeout */ | |
| 111 | + s->status = data; | |
| 112 | + break; | |
| 113 | + | |
| 114 | + case 0x00: /* Input port - ignore writes */ | |
| 115 | + break; | |
| 116 | + default: | |
| 117 | +#ifdef VERBOSE | |
| 118 | + printf("%s: unknown register %02x\n", __FUNCTION__, s->command); | |
| 119 | +#endif | |
| 120 | + return 1; | |
| 121 | + } | |
| 122 | + | |
| 123 | + return 0; | |
| 124 | +} | |
| 125 | + | |
| 126 | +static void max7310_event(i2c_slave *i2c, enum i2c_event event) | |
| 127 | +{ | |
| 128 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 129 | + s->len = 0; | |
| 130 | + | |
| 131 | + switch (event) { | |
| 132 | + case I2C_START_SEND: | |
| 133 | + s->i2c_command_byte = 1; | |
| 134 | + break; | |
| 135 | + case I2C_FINISH: | |
| 136 | + if (s->len == 1) | |
| 137 | +#ifdef VERBOSE | |
| 138 | + printf("%s: message too short (%i bytes)\n", __FUNCTION__, s->len); | |
| 139 | +#endif | |
| 140 | + break; | |
| 141 | + default: | |
| 142 | + break; | |
| 143 | + } | |
| 144 | +} | |
| 145 | + | |
| 146 | +static void max7310_gpio_set(void *opaque, int line, int level) | |
| 147 | +{ | |
| 148 | + struct max7310_s *s = (struct max7310_s *) opaque; | |
| 149 | + if (line >= sizeof(s->handler) / sizeof(*s->handler) || line < 0) | |
| 150 | + cpu_abort(cpu_single_env, "bad GPIO line"); | |
| 151 | + | |
| 152 | + if (level) | |
| 153 | + s->level |= s->direction & (1 << line); | |
| 154 | + else | |
| 155 | + s->level &= ~(s->direction & (1 << line)); | |
| 156 | +} | |
| 157 | + | |
| 158 | +/* MAX7310 is SMBus-compatible (can be used with only SMBus protocols), | |
| 159 | + * but also accepts sequences that are not SMBus so return an I2C device. */ | |
| 160 | +struct i2c_slave *max7310_init(i2c_bus *bus) | |
| 161 | +{ | |
| 162 | + struct max7310_s *s = (struct max7310_s *) | |
| 163 | + i2c_slave_init(bus, 0, sizeof(struct max7310_s)); | |
| 164 | + s->i2c.event = max7310_event; | |
| 165 | + s->i2c.recv = max7310_rx; | |
| 166 | + s->i2c.send = max7310_tx; | |
| 167 | + s->gpio_in = qemu_allocate_irqs(max7310_gpio_set, s, | |
| 168 | + sizeof(s->handler) / sizeof(*s->handler)); | |
| 169 | + | |
| 170 | + max7310_reset(&s->i2c); | |
| 171 | + | |
| 172 | + return &s->i2c; | |
| 173 | +} | |
| 174 | + | |
| 175 | +qemu_irq *max7310_gpio_in_get(i2c_slave *i2c) | |
| 176 | +{ | |
| 177 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 178 | + return s->gpio_in; | |
| 179 | +} | |
| 180 | + | |
| 181 | +void max7310_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler) | |
| 182 | +{ | |
| 183 | + struct max7310_s *s = (struct max7310_s *) i2c; | |
| 184 | + if (line >= sizeof(s->handler) / sizeof(*s->handler) || line < 0) | |
| 185 | + cpu_abort(cpu_single_env, "bad GPIO line"); | |
| 186 | + | |
| 187 | + s->handler[line] = handler; | |
| 188 | +} | ... | ... |
hw/spitz.c
| ... | ... | @@ -801,6 +801,57 @@ static void spitz_microdrive_attach(struct pxa2xx_state_s *cpu) |
| 801 | 801 | } |
| 802 | 802 | } |
| 803 | 803 | |
| 804 | +/* Wm8750 and Max7310 on I2C */ | |
| 805 | + | |
| 806 | +#define AKITA_MAX_ADDR 0x18 | |
| 807 | +#define SPITZ_WM_ADDRL 0x1a | |
| 808 | +#define SPITZ_WM_ADDRH 0x1b | |
| 809 | + | |
| 810 | +#define SPITZ_GPIO_WM 5 | |
| 811 | + | |
| 812 | +#ifdef HAS_AUDIO | |
| 813 | +static void spitz_wm8750_addr(int line, int level, void *opaque) | |
| 814 | +{ | |
| 815 | + i2c_slave *wm = (i2c_slave *) opaque; | |
| 816 | + if (level) | |
| 817 | + i2c_set_slave_address(wm, SPITZ_WM_ADDRH); | |
| 818 | + else | |
| 819 | + i2c_set_slave_address(wm, SPITZ_WM_ADDRL); | |
| 820 | +} | |
| 821 | +#endif | |
| 822 | + | |
| 823 | +static void spitz_i2c_setup(struct pxa2xx_state_s *cpu) | |
| 824 | +{ | |
| 825 | + /* Attach the CPU on one end of our I2C bus. */ | |
| 826 | + i2c_bus *bus = pxa2xx_i2c_bus(cpu->i2c[0]); | |
| 827 | + | |
| 828 | +#ifdef HAS_AUDIO | |
| 829 | + AudioState *audio; | |
| 830 | + i2c_slave *wm; | |
| 831 | + | |
| 832 | + audio = AUD_init(); | |
| 833 | + if (!audio) | |
| 834 | + return; | |
| 835 | + /* Attach a WM8750 to the bus */ | |
| 836 | + wm = wm8750_init(bus, audio); | |
| 837 | + | |
| 838 | + spitz_wm8750_addr(0, 0, wm); | |
| 839 | + pxa2xx_gpio_handler_set(cpu->gpio, SPITZ_GPIO_WM, spitz_wm8750_addr, wm); | |
| 840 | + /* .. and to the sound interface. */ | |
| 841 | + cpu->i2s->opaque = wm; | |
| 842 | + cpu->i2s->codec_out = wm8750_dac_dat; | |
| 843 | + cpu->i2s->codec_in = wm8750_adc_dat; | |
| 844 | + wm8750_data_req_set(wm, cpu->i2s->data_req, cpu->i2s); | |
| 845 | +#endif | |
| 846 | +} | |
| 847 | + | |
| 848 | +static void spitz_akita_i2c_setup(struct pxa2xx_state_s *cpu) | |
| 849 | +{ | |
| 850 | + /* Attach a Max7310 to Akita I2C bus. */ | |
| 851 | + i2c_set_slave_address(max7310_init(pxa2xx_i2c_bus(cpu->i2c[0])), | |
| 852 | + AKITA_MAX_ADDR); | |
| 853 | +} | |
| 854 | + | |
| 804 | 855 | /* Other peripherals */ |
| 805 | 856 | |
| 806 | 857 | static void spitz_charge_switch(int line, int level, void *opaque) |
| ... | ... | @@ -1026,6 +1077,11 @@ static void spitz_common_init(int ram_size, int vga_ram_size, |
| 1026 | 1077 | |
| 1027 | 1078 | spitz_gpio_setup(cpu, (model == akita) ? 1 : 2); |
| 1028 | 1079 | |
| 1080 | + spitz_i2c_setup(cpu); | |
| 1081 | + | |
| 1082 | + if (model == akita) | |
| 1083 | + spitz_akita_i2c_setup(cpu); | |
| 1084 | + | |
| 1029 | 1085 | if (model == terrier) |
| 1030 | 1086 | /* A 6.0 GB microdrive is permanently sitting in CF slot 0. */ |
| 1031 | 1087 | spitz_microdrive_attach(cpu); | ... | ... |
hw/wm8750.c
0 → 100644
| 1 | +/* | |
| 2 | + * WM8750 audio CODEC. | |
| 3 | + * | |
| 4 | + * Copyright (c) 2006 Openedhand Ltd. | |
| 5 | + * Written by Andrzej Zaborowski <balrog@zabor.org> | |
| 6 | + * | |
| 7 | + * This file is licensed under GNU GPL. | |
| 8 | + */ | |
| 9 | + | |
| 10 | +#include "vl.h" | |
| 11 | + | |
| 12 | +#define IN_PORT_N 3 | |
| 13 | +#define OUT_PORT_N 3 | |
| 14 | + | |
| 15 | +#define CODEC "wm8750" | |
| 16 | + | |
| 17 | +struct wm_rate_s; | |
| 18 | +struct wm8750_s { | |
| 19 | + i2c_slave i2c; | |
| 20 | + uint8_t i2c_data[2]; | |
| 21 | + int i2c_len; | |
| 22 | + QEMUSoundCard card; | |
| 23 | + SWVoiceIn *adc_voice[IN_PORT_N]; | |
| 24 | + SWVoiceOut *dac_voice[OUT_PORT_N]; | |
| 25 | + int enable; | |
| 26 | + void (*data_req)(void *, int, int); | |
| 27 | + void *opaque; | |
| 28 | + uint8_t data_in[4096]; | |
| 29 | + uint8_t data_out[4096]; | |
| 30 | + int idx_in, req_in; | |
| 31 | + int idx_out, req_out; | |
| 32 | + | |
| 33 | + SWVoiceOut **out[2]; | |
| 34 | + uint8_t outvol[7], outmute[2]; | |
| 35 | + SWVoiceIn **in[2]; | |
| 36 | + uint8_t invol[4], inmute[2]; | |
| 37 | + | |
| 38 | + uint8_t diff[2], pol, ds, monomix[2], alc, mute; | |
| 39 | + uint8_t path[4], mpath[2], power, format; | |
| 40 | + uint32_t inmask, outmask; | |
| 41 | + const struct wm_rate_s *rate; | |
| 42 | +}; | |
| 43 | + | |
| 44 | +static inline void wm8750_in_load(struct wm8750_s *s) | |
| 45 | +{ | |
| 46 | + int acquired; | |
| 47 | + if (s->idx_in + s->req_in <= sizeof(s->data_in)) | |
| 48 | + return; | |
| 49 | + s->idx_in = audio_MAX(0, (int) sizeof(s->data_in) - s->req_in); | |
| 50 | + acquired = AUD_read(*s->in[0], s->data_in + s->idx_in, | |
| 51 | + sizeof(s->data_in) - s->idx_in); | |
| 52 | +} | |
| 53 | + | |
| 54 | +static inline void wm8750_out_flush(struct wm8750_s *s) | |
| 55 | +{ | |
| 56 | + int sent; | |
| 57 | + if (!s->idx_out) | |
| 58 | + return; | |
| 59 | + sent = AUD_write(*s->out[0], s->data_out, s->idx_out); | |
| 60 | + s->idx_out = 0; | |
| 61 | +} | |
| 62 | + | |
| 63 | +static void wm8750_audio_in_cb(void *opaque, int avail_b) | |
| 64 | +{ | |
| 65 | + struct wm8750_s *s = (struct wm8750_s *) opaque; | |
| 66 | + s->req_in = avail_b; | |
| 67 | + s->data_req(s->opaque, s->req_out >> 2, avail_b >> 2); | |
| 68 | + | |
| 69 | +#if 0 | |
| 70 | + wm8750_in_load(s); | |
| 71 | +#endif | |
| 72 | +} | |
| 73 | + | |
| 74 | +static void wm8750_audio_out_cb(void *opaque, int free_b) | |
| 75 | +{ | |
| 76 | + struct wm8750_s *s = (struct wm8750_s *) opaque; | |
| 77 | + wm8750_out_flush(s); | |
| 78 | + | |
| 79 | + s->req_out = free_b; | |
| 80 | + s->data_req(s->opaque, free_b >> 2, s->req_in >> 2); | |
| 81 | +} | |
| 82 | + | |
| 83 | +struct wm_rate_s { | |
| 84 | + int adc; | |
| 85 | + int adc_hz; | |
| 86 | + int dac; | |
| 87 | + int dac_hz; | |
| 88 | +}; | |
| 89 | + | |
| 90 | +static const struct wm_rate_s wm_rate_table[] = { | |
| 91 | + { 256, 48000, 256, 48000 }, /* SR: 00000 */ | |
| 92 | + { 384, 48000, 384, 48000 }, /* SR: 00001 */ | |
| 93 | + { 256, 48000, 1536, 8000 }, /* SR: 00010 */ | |
| 94 | + { 384, 48000, 2304, 8000 }, /* SR: 00011 */ | |
| 95 | + { 1536, 8000, 256, 48000 }, /* SR: 00100 */ | |
| 96 | + { 2304, 8000, 384, 48000 }, /* SR: 00101 */ | |
| 97 | + { 1536, 8000, 1536, 8000 }, /* SR: 00110 */ | |
| 98 | + { 2304, 8000, 2304, 8000 }, /* SR: 00111 */ | |
| 99 | + { 1024, 12000, 1024, 12000 }, /* SR: 01000 */ | |
| 100 | + { 1526, 12000, 1536, 12000 }, /* SR: 01001 */ | |
| 101 | + { 768, 16000, 768, 16000 }, /* SR: 01010 */ | |
| 102 | + { 1152, 16000, 1152, 16000 }, /* SR: 01011 */ | |
| 103 | + { 384, 32000, 384, 32000 }, /* SR: 01100 */ | |
| 104 | + { 576, 32000, 576, 32000 }, /* SR: 01101 */ | |
| 105 | + { 128, 96000, 128, 96000 }, /* SR: 01110 */ | |
| 106 | + { 192, 96000, 192, 96000 }, /* SR: 01111 */ | |
| 107 | + { 256, 44100, 256, 44100 }, /* SR: 10000 */ | |
| 108 | + { 384, 44100, 384, 44100 }, /* SR: 10001 */ | |
| 109 | + { 256, 44100, 1408, 8018 }, /* SR: 10010 */ | |
| 110 | + { 384, 44100, 2112, 8018 }, /* SR: 10011 */ | |
| 111 | + { 1408, 8018, 256, 44100 }, /* SR: 10100 */ | |
| 112 | + { 2112, 8018, 384, 44100 }, /* SR: 10101 */ | |
| 113 | + { 1408, 8018, 1408, 8018 }, /* SR: 10110 */ | |
| 114 | + { 2112, 8018, 2112, 8018 }, /* SR: 10111 */ | |
| 115 | + { 1024, 11025, 1024, 11025 }, /* SR: 11000 */ | |
| 116 | + { 1536, 11025, 1536, 11025 }, /* SR: 11001 */ | |
| 117 | + { 512, 22050, 512, 22050 }, /* SR: 11010 */ | |
| 118 | + { 768, 22050, 768, 22050 }, /* SR: 11011 */ | |
| 119 | + { 512, 24000, 512, 24000 }, /* SR: 11100 */ | |
| 120 | + { 768, 24000, 768, 24000 }, /* SR: 11101 */ | |
| 121 | + { 128, 88200, 128, 88200 }, /* SR: 11110 */ | |
| 122 | + { 192, 88200, 128, 88200 }, /* SR: 11111 */ | |
| 123 | +}; | |
| 124 | + | |
| 125 | +void wm8750_set_format(struct wm8750_s *s) | |
| 126 | +{ | |
| 127 | + int i; | |
| 128 | + audsettings_t in_fmt; | |
| 129 | + audsettings_t out_fmt; | |
| 130 | + audsettings_t monoout_fmt; | |
| 131 | + | |
| 132 | + wm8750_out_flush(s); | |
| 133 | + | |
| 134 | + if (s->in[0] && *s->in[0]) | |
| 135 | + AUD_set_active_in(*s->in[0], 0); | |
| 136 | + if (s->out[0] && *s->out[0]) | |
| 137 | + AUD_set_active_out(*s->out[0], 0); | |
| 138 | + | |
| 139 | + for (i = 0; i < IN_PORT_N; i ++) | |
| 140 | + if (s->adc_voice[i]) { | |
| 141 | + AUD_close_in(&s->card, s->adc_voice[i]); | |
| 142 | + s->adc_voice[i] = 0; | |
| 143 | + } | |
| 144 | + for (i = 0; i < OUT_PORT_N; i ++) | |
| 145 | + if (s->dac_voice[i]) { | |
| 146 | + AUD_close_out(&s->card, s->dac_voice[i]); | |
| 147 | + s->dac_voice[i] = 0; | |
| 148 | + } | |
| 149 | + | |
| 150 | + if (!s->enable) | |
| 151 | + return; | |
| 152 | + | |
| 153 | + /* Setup input */ | |
| 154 | + in_fmt.endianness = 0; | |
| 155 | + in_fmt.nchannels = 2; | |
| 156 | + in_fmt.freq = s->rate->adc_hz; | |
| 157 | + in_fmt.fmt = AUD_FMT_S16; | |
| 158 | + | |
| 159 | + s->adc_voice[0] = AUD_open_in(&s->card, s->adc_voice[0], | |
| 160 | + CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt); | |
| 161 | + s->adc_voice[1] = AUD_open_in(&s->card, s->adc_voice[1], | |
| 162 | + CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt); | |
| 163 | + s->adc_voice[2] = AUD_open_in(&s->card, s->adc_voice[2], | |
| 164 | + CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt); | |
| 165 | + | |
| 166 | + /* Setup output */ | |
| 167 | + out_fmt.endianness = 0; | |
| 168 | + out_fmt.nchannels = 2; | |
| 169 | + out_fmt.freq = s->rate->dac_hz; | |
| 170 | + out_fmt.fmt = AUD_FMT_S16; | |
| 171 | + monoout_fmt.endianness = 0; | |
| 172 | + monoout_fmt.nchannels = 1; | |
| 173 | + monoout_fmt.freq = s->rate->dac_hz; | |
| 174 | + monoout_fmt.fmt = AUD_FMT_S16; | |
| 175 | + | |
| 176 | + s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0], | |
| 177 | + CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt); | |
| 178 | + s->dac_voice[1] = AUD_open_out(&s->card, s->dac_voice[1], | |
| 179 | + CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt); | |
| 180 | + /* MONOMIX is also in stereo for simplicity */ | |
| 181 | + s->dac_voice[2] = AUD_open_out(&s->card, s->dac_voice[2], | |
| 182 | + CODEC ".monomix", s, wm8750_audio_out_cb, &out_fmt); | |
| 183 | + /* no sense emulating OUT3 which is a mix of other outputs */ | |
| 184 | + | |
| 185 | + /* We should connect the left and right channels to their | |
| 186 | + * respective inputs/outputs but we have completely no need | |
| 187 | + * for mixing or combining paths to different ports, so we | |
| 188 | + * connect both channels to where the left channel is routed. */ | |
| 189 | + if (s->in[0] && *s->in[0]) | |
| 190 | + AUD_set_active_in(*s->in[0], 1); | |
| 191 | + if (s->out[0] && *s->out[0]) | |
| 192 | + AUD_set_active_out(*s->out[0], 1); | |
| 193 | +} | |
| 194 | + | |
| 195 | +void inline wm8750_mask_update(struct wm8750_s *s) | |
| 196 | +{ | |
| 197 | +#define R_ONLY 0x0000ffff | |
| 198 | +#define L_ONLY 0xffff0000 | |
| 199 | +#define BOTH (R_ONLY | L_ONLY) | |
| 200 | +#define NONE (R_ONLY & L_ONLY) | |
| 201 | + s->inmask = | |
| 202 | + (s->inmute[0] ? R_ONLY : BOTH) & | |
| 203 | + (s->inmute[1] ? L_ONLY : BOTH) & | |
| 204 | + (s->mute ? NONE : BOTH); | |
| 205 | + s->outmask = | |
| 206 | + (s->outmute[0] ? R_ONLY : BOTH) & | |
| 207 | + (s->outmute[1] ? L_ONLY : BOTH) & | |
| 208 | + (s->mute ? NONE : BOTH); | |
| 209 | +} | |
| 210 | + | |
| 211 | +void wm8750_reset(i2c_slave *i2c) | |
| 212 | +{ | |
| 213 | + struct wm8750_s *s = (struct wm8750_s *) i2c; | |
| 214 | + s->enable = 0; | |
| 215 | + wm8750_set_format(s); | |
| 216 | + s->diff[0] = 0; | |
| 217 | + s->diff[1] = 0; | |
| 218 | + s->ds = 0; | |
| 219 | + s->alc = 0; | |
| 220 | + s->in[0] = &s->adc_voice[0]; | |
| 221 | + s->invol[0] = 0x17; | |
| 222 | + s->invol[1] = 0x17; | |
| 223 | + s->invol[2] = 0xc3; | |
| 224 | + s->invol[3] = 0xc3; | |
| 225 | + s->out[0] = &s->dac_voice[0]; | |
| 226 | + s->outvol[0] = 0xff; | |
| 227 | + s->outvol[1] = 0xff; | |
| 228 | + s->outvol[2] = 0x79; | |
| 229 | + s->outvol[3] = 0x79; | |
| 230 | + s->outvol[4] = 0x79; | |
| 231 | + s->outvol[5] = 0x79; | |
| 232 | + s->inmute[0] = 0; | |
| 233 | + s->inmute[1] = 0; | |
| 234 | + s->outmute[0] = 0; | |
| 235 | + s->outmute[1] = 0; | |
| 236 | + s->mute = 1; | |
| 237 | + s->path[0] = 0; | |
| 238 | + s->path[1] = 0; | |
| 239 | + s->path[2] = 0; | |
| 240 | + s->path[3] = 0; | |
| 241 | + s->mpath[0] = 0; | |
| 242 | + s->mpath[1] = 0; | |
| 243 | + s->format = 0x0a; | |
| 244 | + s->idx_in = sizeof(s->data_in); | |
| 245 | + s->req_in = 0; | |
| 246 | + s->idx_out = 0; | |
| 247 | + s->req_out = 0; | |
| 248 | + wm8750_mask_update(s); | |
| 249 | + s->i2c_len = 0; | |
| 250 | +} | |
| 251 | + | |
| 252 | +static void wm8750_event(i2c_slave *i2c, enum i2c_event event) | |
| 253 | +{ | |
| 254 | + struct wm8750_s *s = (struct wm8750_s *) i2c; | |
| 255 | + | |
| 256 | + switch (event) { | |
| 257 | + case I2C_START_SEND: | |
| 258 | + s->i2c_len = 0; | |
| 259 | + break; | |
| 260 | + case I2C_FINISH: | |
| 261 | +#ifdef VERBOSE | |
| 262 | + if (s->i2c_len < 2) | |
| 263 | + printf("%s: message too short (%i bytes)\n", | |
| 264 | + __FUNCTION__, s->i2c_len); | |
| 265 | +#endif | |
| 266 | + break; | |
| 267 | + default: | |
| 268 | + break; | |
| 269 | + } | |
| 270 | +} | |
| 271 | + | |
| 272 | +#define WM8750_LINVOL 0x00 | |
| 273 | +#define WM8750_RINVOL 0x01 | |
| 274 | +#define WM8750_LOUT1V 0x02 | |
| 275 | +#define WM8750_ROUT1V 0x03 | |
| 276 | +#define WM8750_ADCDAC 0x05 | |
| 277 | +#define WM8750_IFACE 0x07 | |
| 278 | +#define WM8750_SRATE 0x08 | |
| 279 | +#define WM8750_LDAC 0x0a | |
| 280 | +#define WM8750_RDAC 0x0b | |
| 281 | +#define WM8750_BASS 0x0c | |
| 282 | +#define WM8750_TREBLE 0x0d | |
| 283 | +#define WM8750_RESET 0x0f | |
| 284 | +#define WM8750_3D 0x10 | |
| 285 | +#define WM8750_ALC1 0x11 | |
| 286 | +#define WM8750_ALC2 0x12 | |
| 287 | +#define WM8750_ALC3 0x13 | |
| 288 | +#define WM8750_NGATE 0x14 | |
| 289 | +#define WM8750_LADC 0x15 | |
| 290 | +#define WM8750_RADC 0x16 | |
| 291 | +#define WM8750_ADCTL1 0x17 | |
| 292 | +#define WM8750_ADCTL2 0x18 | |
| 293 | +#define WM8750_PWR1 0x19 | |
| 294 | +#define WM8750_PWR2 0x1a | |
| 295 | +#define WM8750_ADCTL3 0x1b | |
| 296 | +#define WM8750_ADCIN 0x1f | |
| 297 | +#define WM8750_LADCIN 0x20 | |
| 298 | +#define WM8750_RADCIN 0x21 | |
| 299 | +#define WM8750_LOUTM1 0x22 | |
| 300 | +#define WM8750_LOUTM2 0x23 | |
| 301 | +#define WM8750_ROUTM1 0x24 | |
| 302 | +#define WM8750_ROUTM2 0x25 | |
| 303 | +#define WM8750_MOUTM1 0x26 | |
| 304 | +#define WM8750_MOUTM2 0x27 | |
| 305 | +#define WM8750_LOUT2V 0x28 | |
| 306 | +#define WM8750_ROUT2V 0x29 | |
| 307 | +#define WM8750_MOUTV 0x2a | |
| 308 | + | |
| 309 | +static int wm8750_tx(i2c_slave *i2c, uint8_t data) | |
| 310 | +{ | |
| 311 | + struct wm8750_s *s = (struct wm8750_s *) i2c; | |
| 312 | + uint8_t cmd; | |
| 313 | + uint16_t value; | |
| 314 | + | |
| 315 | + if (s->i2c_len >= 2) { | |
| 316 | + printf("%s: long message (%i bytes)\n", __FUNCTION__, s->i2c_len); | |
| 317 | +#ifdef VERBOSE | |
| 318 | + return 1; | |
| 319 | +#endif | |
| 320 | + } | |
| 321 | + s->i2c_data[s->i2c_len ++] = data; | |
| 322 | + if (s->i2c_len != 2) | |
| 323 | + return 0; | |
| 324 | + | |
| 325 | + cmd = s->i2c_data[0] >> 1; | |
| 326 | + value = ((s->i2c_data[0] << 8) | s->i2c_data[1]) & 0x1ff; | |
| 327 | + | |
| 328 | + switch (cmd) { | |
| 329 | + case WM8750_LADCIN: /* ADC Signal Path Control (Left) */ | |
| 330 | + s->diff[0] = (((value >> 6) & 3) == 3); /* LINSEL */ | |
| 331 | + if (s->diff[0]) | |
| 332 | + s->in[0] = &s->adc_voice[0 + s->ds * 1]; | |
| 333 | + else | |
| 334 | + s->in[0] = &s->adc_voice[((value >> 6) & 3) * 1 + 0]; | |
| 335 | + break; | |
| 336 | + | |
| 337 | + case WM8750_RADCIN: /* ADC Signal Path Control (Right) */ | |
| 338 | + s->diff[1] = (((value >> 6) & 3) == 3); /* RINSEL */ | |
| 339 | + if (s->diff[1]) | |
| 340 | + s->in[1] = &s->adc_voice[0 + s->ds * 1]; | |
| 341 | + else | |
| 342 | + s->in[1] = &s->adc_voice[((value >> 6) & 3) * 1 + 0]; | |
| 343 | + break; | |
| 344 | + | |
| 345 | + case WM8750_ADCIN: /* ADC Input Mode */ | |
| 346 | + s->ds = (value >> 8) & 1; /* DS */ | |
| 347 | + if (s->diff[0]) | |
| 348 | + s->in[0] = &s->adc_voice[0 + s->ds * 1]; | |
| 349 | + if (s->diff[1]) | |
| 350 | + s->in[1] = &s->adc_voice[0 + s->ds * 1]; | |
| 351 | + s->monomix[0] = (value >> 6) & 3; /* MONOMIX */ | |
| 352 | + break; | |
| 353 | + | |
| 354 | + case WM8750_ADCTL1: /* Additional Control (1) */ | |
| 355 | + s->monomix[1] = (value >> 1) & 1; /* DMONOMIX */ | |
| 356 | + break; | |
| 357 | + | |
| 358 | + case WM8750_PWR1: /* Power Management (1) */ | |
| 359 | + s->enable = ((value >> 6) & 7) == 3; /* VMIDSEL, VREF */ | |
| 360 | + wm8750_set_format(s); | |
| 361 | + break; | |
| 362 | + | |
| 363 | + case WM8750_LINVOL: /* Left Channel PGA */ | |
| 364 | + s->invol[0] = value & 0x3f; /* LINVOL */ | |
| 365 | + s->inmute[0] = (value >> 7) & 1; /* LINMUTE */ | |
| 366 | + wm8750_mask_update(s); | |
| 367 | + break; | |
| 368 | + | |
| 369 | + case WM8750_RINVOL: /* Right Channel PGA */ | |
| 370 | + s->invol[1] = value & 0x3f; /* RINVOL */ | |
| 371 | + s->inmute[1] = (value >> 7) & 1; /* RINMUTE */ | |
| 372 | + wm8750_mask_update(s); | |
| 373 | + break; | |
| 374 | + | |
| 375 | + case WM8750_ADCDAC: /* ADC and DAC Control */ | |
| 376 | + s->pol = (value >> 5) & 3; /* ADCPOL */ | |
| 377 | + s->mute = (value >> 3) & 1; /* DACMU */ | |
| 378 | + wm8750_mask_update(s); | |
| 379 | + break; | |
| 380 | + | |
| 381 | + case WM8750_ADCTL3: /* Additional Control (3) */ | |
| 382 | + break; | |
| 383 | + | |
| 384 | + case WM8750_LADC: /* Left ADC Digital Volume */ | |
| 385 | + s->invol[2] = value & 0xff; /* LADCVOL */ | |
| 386 | + break; | |
| 387 | + | |
| 388 | + case WM8750_RADC: /* Right ADC Digital Volume */ | |
| 389 | + s->invol[3] = value & 0xff; /* RADCVOL */ | |
| 390 | + break; | |
| 391 | + | |
| 392 | + case WM8750_ALC1: /* ALC Control (1) */ | |
| 393 | + s->alc = (value >> 7) & 3; /* ALCSEL */ | |
| 394 | + break; | |
| 395 | + | |
| 396 | + case WM8750_NGATE: /* Noise Gate Control */ | |
| 397 | + case WM8750_3D: /* 3D enhance */ | |
| 398 | + break; | |
| 399 | + | |
| 400 | + case WM8750_LDAC: /* Left Channel Digital Volume */ | |
| 401 | + s->outvol[0] = value & 0xff; /* LDACVOL */ | |
| 402 | + break; | |
| 403 | + | |
| 404 | + case WM8750_RDAC: /* Right Channel Digital Volume */ | |
| 405 | + s->outvol[1] = value & 0xff; /* RDACVOL */ | |
| 406 | + break; | |
| 407 | + | |
| 408 | + case WM8750_BASS: /* Bass Control */ | |
| 409 | + break; | |
| 410 | + | |
| 411 | + case WM8750_LOUTM1: /* Left Mixer Control (1) */ | |
| 412 | + s->path[0] = (value >> 8) & 1; /* LD2LO */ | |
| 413 | + break; | |
| 414 | + | |
| 415 | + case WM8750_LOUTM2: /* Left Mixer Control (2) */ | |
| 416 | + s->path[1] = (value >> 8) & 1; /* RD2LO */ | |
| 417 | + break; | |
| 418 | + | |
| 419 | + case WM8750_ROUTM1: /* Right Mixer Control (1) */ | |
| 420 | + s->path[2] = (value >> 8) & 1; /* LD2RO */ | |
| 421 | + break; | |
| 422 | + | |
| 423 | + case WM8750_ROUTM2: /* Right Mixer Control (2) */ | |
| 424 | + s->path[3] = (value >> 8) & 1; /* RD2RO */ | |
| 425 | + break; | |
| 426 | + | |
| 427 | + case WM8750_MOUTM1: /* Mono Mixer Control (1) */ | |
| 428 | + s->mpath[0] = (value >> 8) & 1; /* LD2MO */ | |
| 429 | + break; | |
| 430 | + | |
| 431 | + case WM8750_MOUTM2: /* Mono Mixer Control (2) */ | |
| 432 | + s->mpath[1] = (value >> 8) & 1; /* RD2MO */ | |
| 433 | + break; | |
| 434 | + | |
| 435 | + case WM8750_LOUT1V: /* LOUT1 Volume */ | |
| 436 | + s->outvol[2] = value & 0x7f; /* LOUT2VOL */ | |
| 437 | + break; | |
| 438 | + | |
| 439 | + case WM8750_LOUT2V: /* LOUT2 Volume */ | |
| 440 | + s->outvol[4] = value & 0x7f; /* LOUT2VOL */ | |
| 441 | + break; | |
| 442 | + | |
| 443 | + case WM8750_ROUT1V: /* ROUT1 Volume */ | |
| 444 | + s->outvol[3] = value & 0x7f; /* ROUT2VOL */ | |
| 445 | + break; | |
| 446 | + | |
| 447 | + case WM8750_ROUT2V: /* ROUT2 Volume */ | |
| 448 | + s->outvol[5] = value & 0x7f; /* ROUT2VOL */ | |
| 449 | + break; | |
| 450 | + | |
| 451 | + case WM8750_MOUTV: /* MONOOUT Volume */ | |
| 452 | + s->outvol[6] = value & 0x7f; /* MONOOUTVOL */ | |
| 453 | + break; | |
| 454 | + | |
| 455 | + case WM8750_ADCTL2: /* Additional Control (2) */ | |
| 456 | + break; | |
| 457 | + | |
| 458 | + case WM8750_PWR2: /* Power Management (2) */ | |
| 459 | + s->power = value & 0x7e; | |
| 460 | + break; | |
| 461 | + | |
| 462 | + case WM8750_IFACE: /* Digital Audio Interface Format */ | |
| 463 | +#ifdef VERBOSE | |
| 464 | + if (value & 0x40) /* MS */ | |
| 465 | + printf("%s: attempt to enable Master Mode\n", __FUNCTION__); | |
| 466 | +#endif | |
| 467 | + s->format = value; | |
| 468 | + wm8750_set_format(s); | |
| 469 | + break; | |
| 470 | + | |
| 471 | + case WM8750_SRATE: /* Clocking and Sample Rate Control */ | |
| 472 | + s->rate = &wm_rate_table[(value >> 1) & 0x1f]; | |
| 473 | + wm8750_set_format(s); | |
| 474 | + break; | |
| 475 | + | |
| 476 | + case WM8750_RESET: /* Reset */ | |
| 477 | + wm8750_reset(&s->i2c); | |
| 478 | + break; | |
| 479 | + | |
| 480 | +#ifdef VERBOSE | |
| 481 | + default: | |
| 482 | + printf("%s: unknown register %02x\n", __FUNCTION__, cmd); | |
| 483 | +#endif | |
| 484 | + } | |
| 485 | + | |
| 486 | + return 0; | |
| 487 | +} | |
| 488 | + | |
| 489 | +static int wm8750_rx(i2c_slave *i2c) | |
| 490 | +{ | |
| 491 | + return 0x00; | |
| 492 | +} | |
| 493 | + | |
| 494 | +i2c_slave *wm8750_init(i2c_bus *bus, AudioState *audio) | |
| 495 | +{ | |
| 496 | + struct wm8750_s *s = (struct wm8750_s *) | |
| 497 | + i2c_slave_init(bus, 0, sizeof(struct wm8750_s)); | |
| 498 | + s->i2c.event = wm8750_event; | |
| 499 | + s->i2c.recv = wm8750_rx; | |
| 500 | + s->i2c.send = wm8750_tx; | |
| 501 | + | |
| 502 | + AUD_register_card(audio, CODEC, &s->card); | |
| 503 | + wm8750_reset(&s->i2c); | |
| 504 | + | |
| 505 | + return &s->i2c; | |
| 506 | +} | |
| 507 | + | |
| 508 | +void wm8750_fini(i2c_slave *i2c) | |
| 509 | +{ | |
| 510 | + struct wm8750_s *s = (struct wm8750_s *) i2c; | |
| 511 | + wm8750_reset(&s->i2c); | |
| 512 | + AUD_remove_card(&s->card); | |
| 513 | + qemu_free(s); | |
| 514 | +} | |
| 515 | + | |
| 516 | +void wm8750_data_req_set(i2c_slave *i2c, | |
| 517 | + void (*data_req)(void *, int, int), void *opaque) | |
| 518 | +{ | |
| 519 | + struct wm8750_s *s = (struct wm8750_s *) i2c; | |
| 520 | + s->data_req = data_req; | |
| 521 | + s->opaque = opaque; | |
| 522 | +} | |
| 523 | + | |
| 524 | +void wm8750_dac_dat(void *opaque, uint32_t sample) | |
| 525 | +{ | |
| 526 | + struct wm8750_s *s = (struct wm8750_s *) opaque; | |
| 527 | + uint32_t *data = (uint32_t *) &s->data_out[s->idx_out]; | |
| 528 | + *data = sample & s->outmask; | |
| 529 | + s->req_out -= 4; | |
| 530 | + s->idx_out += 4; | |
| 531 | + if (s->idx_out >= sizeof(s->data_out) || s->req_out <= 0) | |
| 532 | + wm8750_out_flush(s); | |
| 533 | +} | |
| 534 | + | |
| 535 | +uint32_t wm8750_adc_dat(void *opaque) | |
| 536 | +{ | |
| 537 | + struct wm8750_s *s = (struct wm8750_s *) opaque; | |
| 538 | + uint32_t *data; | |
| 539 | + if (s->idx_in >= sizeof(s->data_in)) | |
| 540 | + wm8750_in_load(s); | |
| 541 | + data = (uint32_t *) &s->data_in[s->idx_in]; | |
| 542 | + s->req_in -= 4; | |
| 543 | + s->idx_in += 4; | |
| 544 | + return *data & s->inmask; | |
| 545 | +} | ... | ... |