xref: /openbmc/qemu/hw/audio/marvell_88w8618.c (revision 28ae3179fc52d2e4d870b635c4a412aab99759e7)
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