|  | /* | 
|  | *  QEMU model of the Milkymist System Controller. | 
|  | * | 
|  | *  Copyright (c) 2010 Michael Walle <michael@walle.cc> | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | 
|  | * | 
|  | * | 
|  | * Specification available at: | 
|  | *   http://www.milkymist.org/socdoc/ac97.pdf | 
|  | */ | 
|  |  | 
|  | #include "hw/hw.h" | 
|  | #include "hw/sysbus.h" | 
|  | #include "trace.h" | 
|  | #include "audio/audio.h" | 
|  | #include "qemu/error-report.h" | 
|  |  | 
|  | enum { | 
|  | R_AC97_CTRL = 0, | 
|  | R_AC97_ADDR, | 
|  | R_AC97_DATAOUT, | 
|  | R_AC97_DATAIN, | 
|  | R_D_CTRL, | 
|  | R_D_ADDR, | 
|  | R_D_REMAINING, | 
|  | R_RESERVED, | 
|  | R_U_CTRL, | 
|  | R_U_ADDR, | 
|  | R_U_REMAINING, | 
|  | R_MAX | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | AC97_CTRL_RQEN  = (1<<0), | 
|  | AC97_CTRL_WRITE = (1<<1), | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | CTRL_EN = (1<<0), | 
|  | }; | 
|  |  | 
|  | struct MilkymistAC97State { | 
|  | SysBusDevice busdev; | 
|  | MemoryRegion regs_region; | 
|  |  | 
|  | QEMUSoundCard card; | 
|  | SWVoiceIn *voice_in; | 
|  | SWVoiceOut *voice_out; | 
|  |  | 
|  | uint32_t regs[R_MAX]; | 
|  |  | 
|  | qemu_irq crrequest_irq; | 
|  | qemu_irq crreply_irq; | 
|  | qemu_irq dmar_irq; | 
|  | qemu_irq dmaw_irq; | 
|  | }; | 
|  | typedef struct MilkymistAC97State MilkymistAC97State; | 
|  |  | 
|  | static void update_voices(MilkymistAC97State *s) | 
|  | { | 
|  | if (s->regs[R_D_CTRL] & CTRL_EN) { | 
|  | AUD_set_active_out(s->voice_out, 1); | 
|  | } else { | 
|  | AUD_set_active_out(s->voice_out, 0); | 
|  | } | 
|  |  | 
|  | if (s->regs[R_U_CTRL] & CTRL_EN) { | 
|  | AUD_set_active_in(s->voice_in, 1); | 
|  | } else { | 
|  | AUD_set_active_in(s->voice_in, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint64_t ac97_read(void *opaque, hwaddr addr, | 
|  | unsigned size) | 
|  | { | 
|  | MilkymistAC97State *s = opaque; | 
|  | uint32_t r = 0; | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_AC97_CTRL: | 
|  | case R_AC97_ADDR: | 
|  | case R_AC97_DATAOUT: | 
|  | case R_AC97_DATAIN: | 
|  | case R_D_CTRL: | 
|  | case R_D_ADDR: | 
|  | case R_D_REMAINING: | 
|  | case R_U_CTRL: | 
|  | case R_U_ADDR: | 
|  | case R_U_REMAINING: | 
|  | r = s->regs[addr]; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error_report("milkymist_ac97: read access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  |  | 
|  | trace_milkymist_ac97_memory_read(addr << 2, r); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static void ac97_write(void *opaque, hwaddr addr, uint64_t value, | 
|  | unsigned size) | 
|  | { | 
|  | MilkymistAC97State *s = opaque; | 
|  |  | 
|  | trace_milkymist_ac97_memory_write(addr, value); | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_AC97_CTRL: | 
|  | /* always raise an IRQ according to the direction */ | 
|  | if (value & AC97_CTRL_RQEN) { | 
|  | if (value & AC97_CTRL_WRITE) { | 
|  | trace_milkymist_ac97_pulse_irq_crrequest(); | 
|  | qemu_irq_pulse(s->crrequest_irq); | 
|  | } else { | 
|  | trace_milkymist_ac97_pulse_irq_crreply(); | 
|  | qemu_irq_pulse(s->crreply_irq); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* RQEN is self clearing */ | 
|  | s->regs[addr] = value & ~AC97_CTRL_RQEN; | 
|  | break; | 
|  | case R_D_CTRL: | 
|  | case R_U_CTRL: | 
|  | s->regs[addr] = value; | 
|  | update_voices(s); | 
|  | break; | 
|  | case R_AC97_ADDR: | 
|  | case R_AC97_DATAOUT: | 
|  | case R_AC97_DATAIN: | 
|  | case R_D_ADDR: | 
|  | case R_D_REMAINING: | 
|  | case R_U_ADDR: | 
|  | case R_U_REMAINING: | 
|  | s->regs[addr] = value; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error_report("milkymist_ac97: write access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr); | 
|  | break; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | static const MemoryRegionOps ac97_mmio_ops = { | 
|  | .read = ac97_read, | 
|  | .write = ac97_write, | 
|  | .valid = { | 
|  | .min_access_size = 4, | 
|  | .max_access_size = 4, | 
|  | }, | 
|  | .endianness = DEVICE_NATIVE_ENDIAN, | 
|  | }; | 
|  |  | 
|  | static void ac97_in_cb(void *opaque, int avail_b) | 
|  | { | 
|  | MilkymistAC97State *s = opaque; | 
|  | uint8_t buf[4096]; | 
|  | uint32_t remaining = s->regs[R_U_REMAINING]; | 
|  | int temp = audio_MIN(remaining, avail_b); | 
|  | uint32_t addr = s->regs[R_U_ADDR]; | 
|  | int transferred = 0; | 
|  |  | 
|  | trace_milkymist_ac97_in_cb(avail_b, remaining); | 
|  |  | 
|  | /* prevent from raising an IRQ */ | 
|  | if (temp == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | while (temp) { | 
|  | int acquired, to_copy; | 
|  |  | 
|  | to_copy = audio_MIN(temp, sizeof(buf)); | 
|  | acquired = AUD_read(s->voice_in, buf, to_copy); | 
|  | if (!acquired) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | cpu_physical_memory_write(addr, buf, acquired); | 
|  |  | 
|  | temp -= acquired; | 
|  | addr += acquired; | 
|  | transferred += acquired; | 
|  | } | 
|  |  | 
|  | trace_milkymist_ac97_in_cb_transferred(transferred); | 
|  |  | 
|  | s->regs[R_U_ADDR] = addr; | 
|  | s->regs[R_U_REMAINING] -= transferred; | 
|  |  | 
|  | if ((s->regs[R_U_CTRL] & CTRL_EN) && (s->regs[R_U_REMAINING] == 0)) { | 
|  | trace_milkymist_ac97_pulse_irq_dmaw(); | 
|  | qemu_irq_pulse(s->dmaw_irq); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ac97_out_cb(void *opaque, int free_b) | 
|  | { | 
|  | MilkymistAC97State *s = opaque; | 
|  | uint8_t buf[4096]; | 
|  | uint32_t remaining = s->regs[R_D_REMAINING]; | 
|  | int temp = audio_MIN(remaining, free_b); | 
|  | uint32_t addr = s->regs[R_D_ADDR]; | 
|  | int transferred = 0; | 
|  |  | 
|  | trace_milkymist_ac97_out_cb(free_b, remaining); | 
|  |  | 
|  | /* prevent from raising an IRQ */ | 
|  | if (temp == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | while (temp) { | 
|  | int copied, to_copy; | 
|  |  | 
|  | to_copy = audio_MIN(temp, sizeof(buf)); | 
|  | cpu_physical_memory_read(addr, buf, to_copy); | 
|  | copied = AUD_write(s->voice_out, buf, to_copy); | 
|  | if (!copied) { | 
|  | break; | 
|  | } | 
|  | temp -= copied; | 
|  | addr += copied; | 
|  | transferred += copied; | 
|  | } | 
|  |  | 
|  | trace_milkymist_ac97_out_cb_transferred(transferred); | 
|  |  | 
|  | s->regs[R_D_ADDR] = addr; | 
|  | s->regs[R_D_REMAINING] -= transferred; | 
|  |  | 
|  | if ((s->regs[R_D_CTRL] & CTRL_EN) && (s->regs[R_D_REMAINING] == 0)) { | 
|  | trace_milkymist_ac97_pulse_irq_dmar(); | 
|  | qemu_irq_pulse(s->dmar_irq); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void milkymist_ac97_reset(DeviceState *d) | 
|  | { | 
|  | MilkymistAC97State *s = container_of(d, MilkymistAC97State, busdev.qdev); | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < R_MAX; i++) { | 
|  | s->regs[i] = 0; | 
|  | } | 
|  |  | 
|  | AUD_set_active_in(s->voice_in, 0); | 
|  | AUD_set_active_out(s->voice_out, 0); | 
|  | } | 
|  |  | 
|  | static int ac97_post_load(void *opaque, int version_id) | 
|  | { | 
|  | MilkymistAC97State *s = opaque; | 
|  |  | 
|  | update_voices(s); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int milkymist_ac97_init(SysBusDevice *dev) | 
|  | { | 
|  | MilkymistAC97State *s = FROM_SYSBUS(typeof(*s), dev); | 
|  |  | 
|  | struct audsettings as; | 
|  | sysbus_init_irq(dev, &s->crrequest_irq); | 
|  | sysbus_init_irq(dev, &s->crreply_irq); | 
|  | sysbus_init_irq(dev, &s->dmar_irq); | 
|  | sysbus_init_irq(dev, &s->dmaw_irq); | 
|  |  | 
|  | AUD_register_card("Milkymist AC'97", &s->card); | 
|  |  | 
|  | as.freq = 48000; | 
|  | as.nchannels = 2; | 
|  | as.fmt = AUD_FMT_S16; | 
|  | as.endianness = 1; | 
|  |  | 
|  | s->voice_in = AUD_open_in(&s->card, s->voice_in, | 
|  | "mm_ac97.in", s, ac97_in_cb, &as); | 
|  | s->voice_out = AUD_open_out(&s->card, s->voice_out, | 
|  | "mm_ac97.out", s, ac97_out_cb, &as); | 
|  |  | 
|  | memory_region_init_io(&s->regs_region, OBJECT(s), &ac97_mmio_ops, s, | 
|  | "milkymist-ac97", R_MAX * 4); | 
|  | sysbus_init_mmio(dev, &s->regs_region); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_milkymist_ac97 = { | 
|  | .name = "milkymist-ac97", | 
|  | .version_id = 1, | 
|  | .minimum_version_id = 1, | 
|  | .minimum_version_id_old = 1, | 
|  | .post_load = ac97_post_load, | 
|  | .fields      = (VMStateField[]) { | 
|  | VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | static void milkymist_ac97_class_init(ObjectClass *klass, void *data) | 
|  | { | 
|  | DeviceClass *dc = DEVICE_CLASS(klass); | 
|  | SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); | 
|  |  | 
|  | k->init = milkymist_ac97_init; | 
|  | dc->reset = milkymist_ac97_reset; | 
|  | dc->vmsd = &vmstate_milkymist_ac97; | 
|  | } | 
|  |  | 
|  | static const TypeInfo milkymist_ac97_info = { | 
|  | .name          = "milkymist-ac97", | 
|  | .parent        = TYPE_SYS_BUS_DEVICE, | 
|  | .instance_size = sizeof(MilkymistAC97State), | 
|  | .class_init    = milkymist_ac97_class_init, | 
|  | }; | 
|  |  | 
|  | static void milkymist_ac97_register_types(void) | 
|  | { | 
|  | type_register_static(&milkymist_ac97_info); | 
|  | } | 
|  |  | 
|  | type_init(milkymist_ac97_register_types) |