Statistics
| Branch: | Revision:

root / hw / marvell_88w8618_audio.c @ a8170e5e

History | View | Annotate | Download (8.3 kB)

1
/*
2
 * Marvell 88w8618 audio emulation extracted from
3
 * Marvell MV88w8618 / Freecom MusicPal emulation.
4
 *
5
 * Copyright (c) 2008 Jan Kiszka
6
 *
7
 * This code is licensed under the GNU GPL v2.
8
 *
9
 * Contributions after 2012-01-13 are licensed under the terms of the
10
 * GNU GPL, version 2 or (at your option) any later version.
11
 */
12
#include "sysbus.h"
13
#include "hw.h"
14
#include "i2c.h"
15
#include "sysbus.h"
16
#include "audio/audio.h"
17

    
18
#define MP_AUDIO_SIZE           0x00001000
19

    
20
/* Audio register offsets */
21
#define MP_AUDIO_PLAYBACK_MODE  0x00
22
#define MP_AUDIO_CLOCK_DIV      0x18
23
#define MP_AUDIO_IRQ_STATUS     0x20
24
#define MP_AUDIO_IRQ_ENABLE     0x24
25
#define MP_AUDIO_TX_START_LO    0x28
26
#define MP_AUDIO_TX_THRESHOLD   0x2C
27
#define MP_AUDIO_TX_STATUS      0x38
28
#define MP_AUDIO_TX_START_HI    0x40
29

    
30
/* Status register and IRQ enable bits */
31
#define MP_AUDIO_TX_HALF        (1 << 6)
32
#define MP_AUDIO_TX_FULL        (1 << 7)
33

    
34
/* Playback mode bits */
35
#define MP_AUDIO_16BIT_SAMPLE   (1 << 0)
36
#define MP_AUDIO_PLAYBACK_EN    (1 << 7)
37
#define MP_AUDIO_CLOCK_24MHZ    (1 << 9)
38
#define MP_AUDIO_MONO           (1 << 14)
39

    
40
typedef struct mv88w8618_audio_state {
41
    SysBusDevice busdev;
42
    MemoryRegion iomem;
43
    qemu_irq irq;
44
    uint32_t playback_mode;
45
    uint32_t status;
46
    uint32_t irq_enable;
47
    uint32_t phys_buf;
48
    uint32_t target_buffer;
49
    uint32_t threshold;
50
    uint32_t play_pos;
51
    uint32_t last_free;
52
    uint32_t clock_div;
53
    void *wm;
54
} mv88w8618_audio_state;
55

    
56
static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
57
{
58
    mv88w8618_audio_state *s = opaque;
59
    int16_t *codec_buffer;
60
    int8_t buf[4096];
61
    int8_t *mem_buffer;
62
    int pos, block_size;
63

    
64
    if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
65
        return;
66
    }
67
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
68
        free_out <<= 1;
69
    }
70
    if (!(s->playback_mode & MP_AUDIO_MONO)) {
71
        free_out <<= 1;
72
    }
73
    block_size = s->threshold / 2;
74
    if (free_out - s->last_free < block_size) {
75
        return;
76
    }
77
    if (block_size > 4096) {
78
        return;
79
    }
80
    cpu_physical_memory_read(s->target_buffer + s->play_pos, (void *)buf,
81
                             block_size);
82
    mem_buffer = buf;
83
    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
84
        if (s->playback_mode & MP_AUDIO_MONO) {
85
            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
86
            for (pos = 0; pos < block_size; pos += 2) {
87
                *codec_buffer++ = *(int16_t *)mem_buffer;
88
                *codec_buffer++ = *(int16_t *)mem_buffer;
89
                mem_buffer += 2;
90
            }
91
        } else {
92
            memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
93
                   (uint32_t *)mem_buffer, block_size);
94
        }
95
    } else {
96
        if (s->playback_mode & MP_AUDIO_MONO) {
97
            codec_buffer = wm8750_dac_buffer(s->wm, block_size);
98
            for (pos = 0; pos < block_size; pos++) {
99
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
100
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
101
            }
102
        } else {
103
            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
104
            for (pos = 0; pos < block_size; pos += 2) {
105
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
106
                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
107
            }
108
        }
109
    }
110
    wm8750_dac_commit(s->wm);
111

    
112
    s->last_free = free_out - block_size;
113

    
114
    if (s->play_pos == 0) {
115
        s->status |= MP_AUDIO_TX_HALF;
116
        s->play_pos = block_size;
117
    } else {
118
        s->status |= MP_AUDIO_TX_FULL;
119
        s->play_pos = 0;
120
    }
121

    
122
    if (s->status & s->irq_enable) {
123
        qemu_irq_raise(s->irq);
124
    }
125
}
126

    
127
static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
128
{
129
    int rate;
130

    
131
    if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
132
        rate = 24576000 / 64; /* 24.576MHz */
133
    } else {
134
        rate = 11289600 / 64; /* 11.2896MHz */
135
    }
136
    rate /= ((s->clock_div >> 8) & 0xff) + 1;
137

    
138
    wm8750_set_bclk_in(s->wm, rate);
139
}
140

    
141
static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
142
                                    unsigned size)
143
{
144
    mv88w8618_audio_state *s = opaque;
145

    
146
    switch (offset) {
147
    case MP_AUDIO_PLAYBACK_MODE:
148
        return s->playback_mode;
149

    
150
    case MP_AUDIO_CLOCK_DIV:
151
        return s->clock_div;
152

    
153
    case MP_AUDIO_IRQ_STATUS:
154
        return s->status;
155

    
156
    case MP_AUDIO_IRQ_ENABLE:
157
        return s->irq_enable;
158

    
159
    case MP_AUDIO_TX_STATUS:
160
        return s->play_pos >> 2;
161

    
162
    default:
163
        return 0;
164
    }
165
}
166

    
167
static void mv88w8618_audio_write(void *opaque, hwaddr offset,
168
                                  uint64_t value, unsigned size)
169
{
170
    mv88w8618_audio_state *s = opaque;
171

    
172
    switch (offset) {
173
    case MP_AUDIO_PLAYBACK_MODE:
174
        if (value & MP_AUDIO_PLAYBACK_EN &&
175
            !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
176
            s->status = 0;
177
            s->last_free = 0;
178
            s->play_pos = 0;
179
        }
180
        s->playback_mode = value;
181
        mv88w8618_audio_clock_update(s);
182
        break;
183

    
184
    case MP_AUDIO_CLOCK_DIV:
185
        s->clock_div = value;
186
        s->last_free = 0;
187
        s->play_pos = 0;
188
        mv88w8618_audio_clock_update(s);
189
        break;
190

    
191
    case MP_AUDIO_IRQ_STATUS:
192
        s->status &= ~value;
193
        break;
194

    
195
    case MP_AUDIO_IRQ_ENABLE:
196
        s->irq_enable = value;
197
        if (s->status & s->irq_enable) {
198
            qemu_irq_raise(s->irq);
199
        }
200
        break;
201

    
202
    case MP_AUDIO_TX_START_LO:
203
        s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
204
        s->target_buffer = s->phys_buf;
205
        s->play_pos = 0;
206
        s->last_free = 0;
207
        break;
208

    
209
    case MP_AUDIO_TX_THRESHOLD:
210
        s->threshold = (value + 1) * 4;
211
        break;
212

    
213
    case MP_AUDIO_TX_START_HI:
214
        s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
215
        s->target_buffer = s->phys_buf;
216
        s->play_pos = 0;
217
        s->last_free = 0;
218
        break;
219
    }
220
}
221

    
222
static void mv88w8618_audio_reset(DeviceState *d)
223
{
224
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state,
225
                                           sysbus_from_qdev(d));
226

    
227
    s->playback_mode = 0;
228
    s->status = 0;
229
    s->irq_enable = 0;
230
    s->clock_div = 0;
231
    s->threshold = 0;
232
    s->phys_buf = 0;
233
}
234

    
235
static const MemoryRegionOps mv88w8618_audio_ops = {
236
    .read = mv88w8618_audio_read,
237
    .write = mv88w8618_audio_write,
238
    .endianness = DEVICE_NATIVE_ENDIAN,
239
};
240

    
241
static int mv88w8618_audio_init(SysBusDevice *dev)
242
{
243
    mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev);
244

    
245
    sysbus_init_irq(dev, &s->irq);
246

    
247
    wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
248

    
249
    memory_region_init_io(&s->iomem, &mv88w8618_audio_ops, s,
250
                          "audio", MP_AUDIO_SIZE);
251
    sysbus_init_mmio(dev, &s->iomem);
252

    
253
    return 0;
254
}
255

    
256
static const VMStateDescription mv88w8618_audio_vmsd = {
257
    .name = "mv88w8618_audio",
258
    .version_id = 1,
259
    .minimum_version_id = 1,
260
    .minimum_version_id_old = 1,
261
    .fields = (VMStateField[]) {
262
        VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
263
        VMSTATE_UINT32(status, mv88w8618_audio_state),
264
        VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
265
        VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
266
        VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
267
        VMSTATE_UINT32(threshold, mv88w8618_audio_state),
268
        VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
269
        VMSTATE_UINT32(last_free, mv88w8618_audio_state),
270
        VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
271
        VMSTATE_END_OF_LIST()
272
    }
273
};
274

    
275
static Property mv88w8618_audio_properties[] = {
276
    DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm),
277
    {/* end of list */},
278
};
279

    
280
static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
281
{
282
    DeviceClass *dc = DEVICE_CLASS(klass);
283
    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
284

    
285
    k->init = mv88w8618_audio_init;
286
    dc->reset = mv88w8618_audio_reset;
287
    dc->vmsd = &mv88w8618_audio_vmsd;
288
    dc->props = mv88w8618_audio_properties;
289
}
290

    
291
static TypeInfo mv88w8618_audio_info = {
292
    .name          = "mv88w8618_audio",
293
    .parent        = TYPE_SYS_BUS_DEVICE,
294
    .instance_size = sizeof(mv88w8618_audio_state),
295
    .class_init    = mv88w8618_audio_class_init,
296
};
297

    
298
static void mv88w8618_register_types(void)
299
{
300
    type_register_static(&mv88w8618_audio_info);
301
}
302

    
303
type_init(mv88w8618_register_types)