134b8f63eSPaolo Bonzini /*
234b8f63eSPaolo Bonzini * Marvell 88w8618 audio emulation extracted from
334b8f63eSPaolo Bonzini * Marvell MV88w8618 / Freecom MusicPal emulation.
434b8f63eSPaolo Bonzini *
534b8f63eSPaolo Bonzini * Copyright (c) 2008 Jan Kiszka
634b8f63eSPaolo Bonzini *
734b8f63eSPaolo Bonzini * This code is licensed under the GNU GPL v2.
834b8f63eSPaolo Bonzini *
934b8f63eSPaolo Bonzini * Contributions after 2012-01-13 are licensed under the terms of the
1034b8f63eSPaolo Bonzini * GNU GPL, version 2 or (at your option) any later version.
1134b8f63eSPaolo Bonzini */
120b8fa32fSMarkus Armbruster
136086a565SPeter Maydell #include "qemu/osdep.h"
1434b8f63eSPaolo Bonzini #include "hw/sysbus.h"
15d6454270SMarkus Armbruster #include "migration/vmstate.h"
1664552b6bSMarkus Armbruster #include "hw/irq.h"
17a27bd6c7SMarkus Armbruster #include "hw/qdev-properties.h"
187ab14c5aSPhilippe Mathieu-Daudé #include "hw/audio/wm8750.h"
1934b8f63eSPaolo Bonzini #include "audio/audio.h"
20a8299ec1SMao Zhongyi #include "qapi/error.h"
210b8fa32fSMarkus Armbruster #include "qemu/module.h"
22db1015e9SEduardo Habkost #include "qom/object.h"
2334b8f63eSPaolo Bonzini
2434b8f63eSPaolo Bonzini #define MP_AUDIO_SIZE 0x00001000
2534b8f63eSPaolo Bonzini
2634b8f63eSPaolo Bonzini /* Audio register offsets */
2734b8f63eSPaolo Bonzini #define MP_AUDIO_PLAYBACK_MODE 0x00
2834b8f63eSPaolo Bonzini #define MP_AUDIO_CLOCK_DIV 0x18
2934b8f63eSPaolo Bonzini #define MP_AUDIO_IRQ_STATUS 0x20
3034b8f63eSPaolo Bonzini #define MP_AUDIO_IRQ_ENABLE 0x24
3134b8f63eSPaolo Bonzini #define MP_AUDIO_TX_START_LO 0x28
3234b8f63eSPaolo Bonzini #define MP_AUDIO_TX_THRESHOLD 0x2C
3334b8f63eSPaolo Bonzini #define MP_AUDIO_TX_STATUS 0x38
3434b8f63eSPaolo Bonzini #define MP_AUDIO_TX_START_HI 0x40
3534b8f63eSPaolo Bonzini
3634b8f63eSPaolo Bonzini /* Status register and IRQ enable bits */
3734b8f63eSPaolo Bonzini #define MP_AUDIO_TX_HALF (1 << 6)
3834b8f63eSPaolo Bonzini #define MP_AUDIO_TX_FULL (1 << 7)
3934b8f63eSPaolo Bonzini
4034b8f63eSPaolo Bonzini /* Playback mode bits */
4134b8f63eSPaolo Bonzini #define MP_AUDIO_16BIT_SAMPLE (1 << 0)
4234b8f63eSPaolo Bonzini #define MP_AUDIO_PLAYBACK_EN (1 << 7)
4334b8f63eSPaolo Bonzini #define MP_AUDIO_CLOCK_24MHZ (1 << 9)
4434b8f63eSPaolo Bonzini #define MP_AUDIO_MONO (1 << 14)
4534b8f63eSPaolo Bonzini
468063396bSEduardo Habkost OBJECT_DECLARE_SIMPLE_TYPE(mv88w8618_audio_state, MV88W8618_AUDIO)
479e3f8599SAndreas Färber
48db1015e9SEduardo Habkost struct mv88w8618_audio_state {
499e3f8599SAndreas Färber SysBusDevice parent_obj;
509e3f8599SAndreas Färber
5134b8f63eSPaolo Bonzini MemoryRegion iomem;
5234b8f63eSPaolo Bonzini qemu_irq irq;
5334b8f63eSPaolo Bonzini uint32_t playback_mode;
5434b8f63eSPaolo Bonzini uint32_t status;
5534b8f63eSPaolo Bonzini uint32_t irq_enable;
5634b8f63eSPaolo Bonzini uint32_t phys_buf;
5734b8f63eSPaolo Bonzini uint32_t target_buffer;
5834b8f63eSPaolo Bonzini uint32_t threshold;
5934b8f63eSPaolo Bonzini uint32_t play_pos;
6034b8f63eSPaolo Bonzini uint32_t last_free;
6134b8f63eSPaolo Bonzini uint32_t clock_div;
6234b8f63eSPaolo Bonzini void *wm;
63db1015e9SEduardo Habkost };
6434b8f63eSPaolo Bonzini
mv88w8618_audio_callback(void * opaque,int free_out,int free_in)6534b8f63eSPaolo Bonzini static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
6634b8f63eSPaolo Bonzini {
6734b8f63eSPaolo Bonzini mv88w8618_audio_state *s = opaque;
6834b8f63eSPaolo Bonzini int16_t *codec_buffer;
6934b8f63eSPaolo Bonzini int8_t buf[4096];
7034b8f63eSPaolo Bonzini int8_t *mem_buffer;
7134b8f63eSPaolo Bonzini int pos, block_size;
7234b8f63eSPaolo Bonzini
7334b8f63eSPaolo Bonzini if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
7434b8f63eSPaolo Bonzini return;
7534b8f63eSPaolo Bonzini }
7634b8f63eSPaolo Bonzini if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
7734b8f63eSPaolo Bonzini free_out <<= 1;
7834b8f63eSPaolo Bonzini }
7934b8f63eSPaolo Bonzini if (!(s->playback_mode & MP_AUDIO_MONO)) {
8034b8f63eSPaolo Bonzini free_out <<= 1;
8134b8f63eSPaolo Bonzini }
8234b8f63eSPaolo Bonzini block_size = s->threshold / 2;
8334b8f63eSPaolo Bonzini if (free_out - s->last_free < block_size) {
8434b8f63eSPaolo Bonzini return;
8534b8f63eSPaolo Bonzini }
8634b8f63eSPaolo Bonzini if (block_size > 4096) {
8734b8f63eSPaolo Bonzini return;
8834b8f63eSPaolo Bonzini }
89e1fe50dcSStefan Weil cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size);
9034b8f63eSPaolo Bonzini mem_buffer = buf;
9134b8f63eSPaolo Bonzini if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
9234b8f63eSPaolo Bonzini if (s->playback_mode & MP_AUDIO_MONO) {
9334b8f63eSPaolo Bonzini codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
9434b8f63eSPaolo Bonzini for (pos = 0; pos < block_size; pos += 2) {
9534b8f63eSPaolo Bonzini *codec_buffer++ = *(int16_t *)mem_buffer;
9634b8f63eSPaolo Bonzini *codec_buffer++ = *(int16_t *)mem_buffer;
9734b8f63eSPaolo Bonzini mem_buffer += 2;
9834b8f63eSPaolo Bonzini }
9934b8f63eSPaolo Bonzini } else {
10034b8f63eSPaolo Bonzini memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
10134b8f63eSPaolo Bonzini (uint32_t *)mem_buffer, block_size);
10234b8f63eSPaolo Bonzini }
10334b8f63eSPaolo Bonzini } else {
10434b8f63eSPaolo Bonzini if (s->playback_mode & MP_AUDIO_MONO) {
10534b8f63eSPaolo Bonzini codec_buffer = wm8750_dac_buffer(s->wm, block_size);
10634b8f63eSPaolo Bonzini for (pos = 0; pos < block_size; pos++) {
10734b8f63eSPaolo Bonzini *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
10834b8f63eSPaolo Bonzini *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
10934b8f63eSPaolo Bonzini }
11034b8f63eSPaolo Bonzini } else {
11134b8f63eSPaolo Bonzini codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
11234b8f63eSPaolo Bonzini for (pos = 0; pos < block_size; pos += 2) {
11334b8f63eSPaolo Bonzini *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
11434b8f63eSPaolo Bonzini *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
11534b8f63eSPaolo Bonzini }
11634b8f63eSPaolo Bonzini }
11734b8f63eSPaolo Bonzini }
11834b8f63eSPaolo Bonzini wm8750_dac_commit(s->wm);
11934b8f63eSPaolo Bonzini
12034b8f63eSPaolo Bonzini s->last_free = free_out - block_size;
12134b8f63eSPaolo Bonzini
12234b8f63eSPaolo Bonzini if (s->play_pos == 0) {
12334b8f63eSPaolo Bonzini s->status |= MP_AUDIO_TX_HALF;
12434b8f63eSPaolo Bonzini s->play_pos = block_size;
12534b8f63eSPaolo Bonzini } else {
12634b8f63eSPaolo Bonzini s->status |= MP_AUDIO_TX_FULL;
12734b8f63eSPaolo Bonzini s->play_pos = 0;
12834b8f63eSPaolo Bonzini }
12934b8f63eSPaolo Bonzini
13034b8f63eSPaolo Bonzini if (s->status & s->irq_enable) {
13134b8f63eSPaolo Bonzini qemu_irq_raise(s->irq);
13234b8f63eSPaolo Bonzini }
13334b8f63eSPaolo Bonzini }
13434b8f63eSPaolo Bonzini
mv88w8618_audio_clock_update(mv88w8618_audio_state * s)13534b8f63eSPaolo Bonzini static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
13634b8f63eSPaolo Bonzini {
13734b8f63eSPaolo Bonzini int rate;
13834b8f63eSPaolo Bonzini
13934b8f63eSPaolo Bonzini if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
14034b8f63eSPaolo Bonzini rate = 24576000 / 64; /* 24.576MHz */
14134b8f63eSPaolo Bonzini } else {
14234b8f63eSPaolo Bonzini rate = 11289600 / 64; /* 11.2896MHz */
14334b8f63eSPaolo Bonzini }
14434b8f63eSPaolo Bonzini rate /= ((s->clock_div >> 8) & 0xff) + 1;
14534b8f63eSPaolo Bonzini
14634b8f63eSPaolo Bonzini wm8750_set_bclk_in(s->wm, rate);
14734b8f63eSPaolo Bonzini }
14834b8f63eSPaolo Bonzini
mv88w8618_audio_read(void * opaque,hwaddr offset,unsigned size)14934b8f63eSPaolo Bonzini static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
15034b8f63eSPaolo Bonzini unsigned size)
15134b8f63eSPaolo Bonzini {
15234b8f63eSPaolo Bonzini mv88w8618_audio_state *s = opaque;
15334b8f63eSPaolo Bonzini
15434b8f63eSPaolo Bonzini switch (offset) {
15534b8f63eSPaolo Bonzini case MP_AUDIO_PLAYBACK_MODE:
15634b8f63eSPaolo Bonzini return s->playback_mode;
15734b8f63eSPaolo Bonzini
15834b8f63eSPaolo Bonzini case MP_AUDIO_CLOCK_DIV:
15934b8f63eSPaolo Bonzini return s->clock_div;
16034b8f63eSPaolo Bonzini
16134b8f63eSPaolo Bonzini case MP_AUDIO_IRQ_STATUS:
16234b8f63eSPaolo Bonzini return s->status;
16334b8f63eSPaolo Bonzini
16434b8f63eSPaolo Bonzini case MP_AUDIO_IRQ_ENABLE:
16534b8f63eSPaolo Bonzini return s->irq_enable;
16634b8f63eSPaolo Bonzini
16734b8f63eSPaolo Bonzini case MP_AUDIO_TX_STATUS:
16834b8f63eSPaolo Bonzini return s->play_pos >> 2;
16934b8f63eSPaolo Bonzini
17034b8f63eSPaolo Bonzini default:
17134b8f63eSPaolo Bonzini return 0;
17234b8f63eSPaolo Bonzini }
17334b8f63eSPaolo Bonzini }
17434b8f63eSPaolo Bonzini
mv88w8618_audio_write(void * opaque,hwaddr offset,uint64_t value,unsigned size)17534b8f63eSPaolo Bonzini static void mv88w8618_audio_write(void *opaque, hwaddr offset,
17634b8f63eSPaolo Bonzini uint64_t value, unsigned size)
17734b8f63eSPaolo Bonzini {
17834b8f63eSPaolo Bonzini mv88w8618_audio_state *s = opaque;
17934b8f63eSPaolo Bonzini
18034b8f63eSPaolo Bonzini switch (offset) {
18134b8f63eSPaolo Bonzini case MP_AUDIO_PLAYBACK_MODE:
18234b8f63eSPaolo Bonzini if (value & MP_AUDIO_PLAYBACK_EN &&
18334b8f63eSPaolo Bonzini !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
18434b8f63eSPaolo Bonzini s->status = 0;
18534b8f63eSPaolo Bonzini s->last_free = 0;
18634b8f63eSPaolo Bonzini s->play_pos = 0;
18734b8f63eSPaolo Bonzini }
18834b8f63eSPaolo Bonzini s->playback_mode = value;
18934b8f63eSPaolo Bonzini mv88w8618_audio_clock_update(s);
19034b8f63eSPaolo Bonzini break;
19134b8f63eSPaolo Bonzini
19234b8f63eSPaolo Bonzini case MP_AUDIO_CLOCK_DIV:
19334b8f63eSPaolo Bonzini s->clock_div = value;
19434b8f63eSPaolo Bonzini s->last_free = 0;
19534b8f63eSPaolo Bonzini s->play_pos = 0;
19634b8f63eSPaolo Bonzini mv88w8618_audio_clock_update(s);
19734b8f63eSPaolo Bonzini break;
19834b8f63eSPaolo Bonzini
19934b8f63eSPaolo Bonzini case MP_AUDIO_IRQ_STATUS:
20034b8f63eSPaolo Bonzini s->status &= ~value;
20134b8f63eSPaolo Bonzini break;
20234b8f63eSPaolo Bonzini
20334b8f63eSPaolo Bonzini case MP_AUDIO_IRQ_ENABLE:
20434b8f63eSPaolo Bonzini s->irq_enable = value;
20534b8f63eSPaolo Bonzini if (s->status & s->irq_enable) {
20634b8f63eSPaolo Bonzini qemu_irq_raise(s->irq);
20734b8f63eSPaolo Bonzini }
20834b8f63eSPaolo Bonzini break;
20934b8f63eSPaolo Bonzini
21034b8f63eSPaolo Bonzini case MP_AUDIO_TX_START_LO:
21134b8f63eSPaolo Bonzini s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
21234b8f63eSPaolo Bonzini s->target_buffer = s->phys_buf;
21334b8f63eSPaolo Bonzini s->play_pos = 0;
21434b8f63eSPaolo Bonzini s->last_free = 0;
21534b8f63eSPaolo Bonzini break;
21634b8f63eSPaolo Bonzini
21734b8f63eSPaolo Bonzini case MP_AUDIO_TX_THRESHOLD:
21834b8f63eSPaolo Bonzini s->threshold = (value + 1) * 4;
21934b8f63eSPaolo Bonzini break;
22034b8f63eSPaolo Bonzini
22134b8f63eSPaolo Bonzini case MP_AUDIO_TX_START_HI:
22234b8f63eSPaolo Bonzini s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
22334b8f63eSPaolo Bonzini s->target_buffer = s->phys_buf;
22434b8f63eSPaolo Bonzini s->play_pos = 0;
22534b8f63eSPaolo Bonzini s->last_free = 0;
22634b8f63eSPaolo Bonzini break;
22734b8f63eSPaolo Bonzini }
22834b8f63eSPaolo Bonzini }
22934b8f63eSPaolo Bonzini
mv88w8618_audio_reset(DeviceState * d)23034b8f63eSPaolo Bonzini static void mv88w8618_audio_reset(DeviceState *d)
23134b8f63eSPaolo Bonzini {
2329e3f8599SAndreas Färber mv88w8618_audio_state *s = MV88W8618_AUDIO(d);
23334b8f63eSPaolo Bonzini
23434b8f63eSPaolo Bonzini s->playback_mode = 0;
23534b8f63eSPaolo Bonzini s->status = 0;
23634b8f63eSPaolo Bonzini s->irq_enable = 0;
23734b8f63eSPaolo Bonzini s->clock_div = 0;
23834b8f63eSPaolo Bonzini s->threshold = 0;
23934b8f63eSPaolo Bonzini s->phys_buf = 0;
24034b8f63eSPaolo Bonzini }
24134b8f63eSPaolo Bonzini
24234b8f63eSPaolo Bonzini static const MemoryRegionOps mv88w8618_audio_ops = {
24334b8f63eSPaolo Bonzini .read = mv88w8618_audio_read,
24434b8f63eSPaolo Bonzini .write = mv88w8618_audio_write,
24534b8f63eSPaolo Bonzini .endianness = DEVICE_NATIVE_ENDIAN,
24634b8f63eSPaolo Bonzini };
24734b8f63eSPaolo Bonzini
mv88w8618_audio_init(Object * obj)248c025d0abSxiaoqiang zhao static void mv88w8618_audio_init(Object *obj)
24934b8f63eSPaolo Bonzini {
250c025d0abSxiaoqiang zhao SysBusDevice *dev = SYS_BUS_DEVICE(obj);
2519e3f8599SAndreas Färber mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
25234b8f63eSPaolo Bonzini
25334b8f63eSPaolo Bonzini sysbus_init_irq(dev, &s->irq);
25434b8f63eSPaolo Bonzini
255c025d0abSxiaoqiang zhao memory_region_init_io(&s->iomem, obj, &mv88w8618_audio_ops, s,
25634b8f63eSPaolo Bonzini "audio", MP_AUDIO_SIZE);
25734b8f63eSPaolo Bonzini sysbus_init_mmio(dev, &s->iomem);
258a8299ec1SMao Zhongyi
259a8299ec1SMao Zhongyi object_property_add_link(OBJECT(dev), "wm8750", TYPE_WM8750,
260a8299ec1SMao Zhongyi (Object **) &s->wm,
261a8299ec1SMao Zhongyi qdev_prop_allow_set_link_before_realize,
262d2623129SMarkus Armbruster 0);
263c025d0abSxiaoqiang zhao }
26434b8f63eSPaolo Bonzini
mv88w8618_audio_realize(DeviceState * dev,Error ** errp)265c025d0abSxiaoqiang zhao static void mv88w8618_audio_realize(DeviceState *dev, Error **errp)
266c025d0abSxiaoqiang zhao {
267c025d0abSxiaoqiang zhao mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
268c025d0abSxiaoqiang zhao
269c025d0abSxiaoqiang zhao wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
27034b8f63eSPaolo Bonzini }
27134b8f63eSPaolo Bonzini
27234b8f63eSPaolo Bonzini static const VMStateDescription mv88w8618_audio_vmsd = {
27334b8f63eSPaolo Bonzini .name = "mv88w8618_audio",
27434b8f63eSPaolo Bonzini .version_id = 1,
27534b8f63eSPaolo Bonzini .minimum_version_id = 1,
276856a6fe4SRichard Henderson .fields = (const VMStateField[]) {
27734b8f63eSPaolo Bonzini VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
27834b8f63eSPaolo Bonzini VMSTATE_UINT32(status, mv88w8618_audio_state),
27934b8f63eSPaolo Bonzini VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
28034b8f63eSPaolo Bonzini VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
28134b8f63eSPaolo Bonzini VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
28234b8f63eSPaolo Bonzini VMSTATE_UINT32(threshold, mv88w8618_audio_state),
28334b8f63eSPaolo Bonzini VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
28434b8f63eSPaolo Bonzini VMSTATE_UINT32(last_free, mv88w8618_audio_state),
28534b8f63eSPaolo Bonzini VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
28634b8f63eSPaolo Bonzini VMSTATE_END_OF_LIST()
28734b8f63eSPaolo Bonzini }
28834b8f63eSPaolo Bonzini };
28934b8f63eSPaolo Bonzini
mv88w8618_audio_class_init(ObjectClass * klass,void * data)29034b8f63eSPaolo Bonzini static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
29134b8f63eSPaolo Bonzini {
29234b8f63eSPaolo Bonzini DeviceClass *dc = DEVICE_CLASS(klass);
29334b8f63eSPaolo Bonzini
294c025d0abSxiaoqiang zhao dc->realize = mv88w8618_audio_realize;
295*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, mv88w8618_audio_reset);
29634b8f63eSPaolo Bonzini dc->vmsd = &mv88w8618_audio_vmsd;
297e90f2a8cSEduardo Habkost dc->user_creatable = false;
29834b8f63eSPaolo Bonzini }
29934b8f63eSPaolo Bonzini
30034b8f63eSPaolo Bonzini static const TypeInfo mv88w8618_audio_info = {
3019e3f8599SAndreas Färber .name = TYPE_MV88W8618_AUDIO,
30234b8f63eSPaolo Bonzini .parent = TYPE_SYS_BUS_DEVICE,
30334b8f63eSPaolo Bonzini .instance_size = sizeof(mv88w8618_audio_state),
304c025d0abSxiaoqiang zhao .instance_init = mv88w8618_audio_init,
30534b8f63eSPaolo Bonzini .class_init = mv88w8618_audio_class_init,
30634b8f63eSPaolo Bonzini };
30734b8f63eSPaolo Bonzini
mv88w8618_register_types(void)30834b8f63eSPaolo Bonzini static void mv88w8618_register_types(void)
30934b8f63eSPaolo Bonzini {
31034b8f63eSPaolo Bonzini type_register_static(&mv88w8618_audio_info);
31134b8f63eSPaolo Bonzini }
31234b8f63eSPaolo Bonzini
31334b8f63eSPaolo Bonzini type_init(mv88w8618_register_types)
314