qemu/hw/audio/pl041.c
Markus Armbruster efec3dd631 qdev: Replace no_user by cannot_instantiate_with_device_add_yet
In an ideal world, machines can be built by wiring devices together
with configuration, not code.  Unfortunately, that's not the world we
live in right now.  We still have quite a few devices that need to be
wired up by code.  If you try to device_add such a device, it'll fail
in sometimes mysterious ways.  If you're lucky, you get an
unmysterious immediate crash.

To protect users from such badness, DeviceClass member no_user used to
make device models unavailable with -device / device_add, but that
regressed in commit 18b6dad.  The device model is still omitted from
help, but is available anyway.

Attempts to fix the regression have been rejected with the argument
that the purpose of no_user isn't clear, and it's prone to misuse.

This commit clarifies no_user's purpose.  Anthony suggested to rename
it cannot_instantiate_with_device_add_yet_due_to_internal_bugs, which
I shorten somewhat to keep checkpatch happy.  While there, make it
bool.

Every use of cannot_instantiate_with_device_add_yet gets a FIXME
comment asking for rationale.  The next few commits will clean them
all up, either by providing a rationale, or by getting rid of the use.

With that done, the regression fix is hopefully acceptable.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Marcel Apfelbaum <marcel.a@redhat.com>
Signed-off-by: Andreas Färber <afaerber@suse.de>
2013-12-23 00:27:22 +01:00

653 lines
17 KiB
C

/*
* Arm PrimeCell PL041 Advanced Audio Codec Interface
*
* Copyright (c) 2011
* Written by Mathieu Sonet - www.elasticsheep.com
*
* This code is licensed under the GPL.
*
* *****************************************************************
*
* This driver emulates the ARM AACI interface
* connected to a LM4549 codec.
*
* Limitations:
* - Supports only a playback on one channel (Versatile/Vexpress)
* - Supports only one TX FIFO in compact-mode or non-compact mode.
* - Supports playback of 12, 16, 18 and 20 bits samples.
* - Record is not supported.
* - The PL041 is hardwired to a LM4549 codec.
*
*/
#include "hw/sysbus.h"
#include "pl041.h"
#include "lm4549.h"
#if 0
#define PL041_DEBUG_LEVEL 1
#endif
#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1)
#define DBG_L1(fmt, ...) \
do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DBG_L1(fmt, ...) \
do { } while (0)
#endif
#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 2)
#define DBG_L2(fmt, ...) \
do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DBG_L2(fmt, ...) \
do { } while (0)
#endif
#define MAX_FIFO_DEPTH (1024)
#define DEFAULT_FIFO_DEPTH (8)
#define SLOT1_RW (1 << 19)
/* This FIFO only stores 20-bit samples on 32-bit words.
So its level is independent of the selected mode */
typedef struct {
uint32_t level;
uint32_t data[MAX_FIFO_DEPTH];
} pl041_fifo;
typedef struct {
pl041_fifo tx_fifo;
uint8_t tx_enabled;
uint8_t tx_compact_mode;
uint8_t tx_sample_size;
pl041_fifo rx_fifo;
uint8_t rx_enabled;
uint8_t rx_compact_mode;
uint8_t rx_sample_size;
} pl041_channel;
#define TYPE_PL041 "pl041"
#define PL041(obj) OBJECT_CHECK(PL041State, (obj), TYPE_PL041)
typedef struct PL041State {
SysBusDevice parent_obj;
MemoryRegion iomem;
qemu_irq irq;
uint32_t fifo_depth; /* FIFO depth in non-compact mode */
pl041_regfile regs;
pl041_channel fifo1;
lm4549_state codec;
} PL041State;
static const unsigned char pl041_default_id[8] = {
0x41, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1
};
#if defined(PL041_DEBUG_LEVEL)
#define REGISTER(name, offset) #name,
static const char *pl041_regs_name[] = {
#include "pl041.hx"
};
#undef REGISTER
#endif
#if defined(PL041_DEBUG_LEVEL)
static const char *get_reg_name(hwaddr offset)
{
if (offset <= PL041_dr1_7) {
return pl041_regs_name[offset >> 2];
}
return "unknown";
}
#endif
static uint8_t pl041_compute_periphid3(PL041State *s)
{
uint8_t id3 = 1; /* One channel */
/* Add the fifo depth information */
switch (s->fifo_depth) {
case 8:
id3 |= 0 << 3;
break;
case 32:
id3 |= 1 << 3;
break;
case 64:
id3 |= 2 << 3;
break;
case 128:
id3 |= 3 << 3;
break;
case 256:
id3 |= 4 << 3;
break;
case 512:
id3 |= 5 << 3;
break;
case 1024:
id3 |= 6 << 3;
break;
case 2048:
id3 |= 7 << 3;
break;
}
return id3;
}
static void pl041_reset(PL041State *s)
{
DBG_L1("pl041_reset\n");
memset(&s->regs, 0x00, sizeof(pl041_regfile));
s->regs.slfr = SL1TXEMPTY | SL2TXEMPTY | SL12TXEMPTY;
s->regs.sr1 = TXFE | RXFE | TXHE;
s->regs.isr1 = 0;
memset(&s->fifo1, 0x00, sizeof(s->fifo1));
}
static void pl041_fifo1_write(PL041State *s, uint32_t value)
{
pl041_channel *channel = &s->fifo1;
pl041_fifo *fifo = &s->fifo1.tx_fifo;
/* Push the value in the FIFO */
if (channel->tx_compact_mode == 0) {
/* Non-compact mode */
if (fifo->level < s->fifo_depth) {
/* Pad the value with 0 to obtain a 20-bit sample */
switch (channel->tx_sample_size) {
case 12:
value = (value << 8) & 0xFFFFF;
break;
case 16:
value = (value << 4) & 0xFFFFF;
break;
case 18:
value = (value << 2) & 0xFFFFF;
break;
case 20:
default:
break;
}
/* Store the sample in the FIFO */
fifo->data[fifo->level++] = value;
}
#if defined(PL041_DEBUG_LEVEL)
else {
DBG_L1("fifo1 write: overrun\n");
}
#endif
} else {
/* Compact mode */
if ((fifo->level + 2) < s->fifo_depth) {
uint32_t i = 0;
uint32_t sample = 0;
for (i = 0; i < 2; i++) {
sample = value & 0xFFFF;
value = value >> 16;
/* Pad each sample with 0 to obtain a 20-bit sample */
switch (channel->tx_sample_size) {
case 12:
sample = sample << 8;
break;
case 16:
default:
sample = sample << 4;
break;
}
/* Store the sample in the FIFO */
fifo->data[fifo->level++] = sample;
}
}
#if defined(PL041_DEBUG_LEVEL)
else {
DBG_L1("fifo1 write: overrun\n");
}
#endif
}
/* Update the status register */
if (fifo->level > 0) {
s->regs.sr1 &= ~(TXUNDERRUN | TXFE);
}
if (fifo->level >= (s->fifo_depth / 2)) {
s->regs.sr1 &= ~TXHE;
}
if (fifo->level >= s->fifo_depth) {
s->regs.sr1 |= TXFF;
}
DBG_L2("fifo1_push sr1 = 0x%08x\n", s->regs.sr1);
}
static void pl041_fifo1_transmit(PL041State *s)
{
pl041_channel *channel = &s->fifo1;
pl041_fifo *fifo = &s->fifo1.tx_fifo;
uint32_t slots = s->regs.txcr1 & TXSLOT_MASK;
uint32_t written_samples;
/* Check if FIFO1 transmit is enabled */
if ((channel->tx_enabled) && (slots & (TXSLOT3 | TXSLOT4))) {
if (fifo->level >= (s->fifo_depth / 2)) {
int i;
DBG_L1("Transfer FIFO level = %i\n", fifo->level);
/* Try to transfer the whole FIFO */
for (i = 0; i < (fifo->level / 2); i++) {
uint32_t left = fifo->data[i * 2];
uint32_t right = fifo->data[i * 2 + 1];
/* Transmit two 20-bit samples to the codec */
if (lm4549_write_samples(&s->codec, left, right) == 0) {
DBG_L1("Codec buffer full\n");
break;
}
}
written_samples = i * 2;
if (written_samples > 0) {
/* Update the FIFO level */
fifo->level -= written_samples;
/* Move back the pending samples to the start of the FIFO */
for (i = 0; i < fifo->level; i++) {
fifo->data[i] = fifo->data[written_samples + i];
}
/* Update the status register */
s->regs.sr1 &= ~TXFF;
if (fifo->level <= (s->fifo_depth / 2)) {
s->regs.sr1 |= TXHE;
}
if (fifo->level == 0) {
s->regs.sr1 |= TXFE | TXUNDERRUN;
DBG_L1("Empty FIFO\n");
}
}
}
}
}
static void pl041_isr1_update(PL041State *s)
{
/* Update ISR1 */
if (s->regs.sr1 & TXUNDERRUN) {
s->regs.isr1 |= URINTR;
} else {
s->regs.isr1 &= ~URINTR;
}
if (s->regs.sr1 & TXHE) {
s->regs.isr1 |= TXINTR;
} else {
s->regs.isr1 &= ~TXINTR;
}
if (!(s->regs.sr1 & TXBUSY) && (s->regs.sr1 & TXFE)) {
s->regs.isr1 |= TXCINTR;
} else {
s->regs.isr1 &= ~TXCINTR;
}
/* Update the irq state */
qemu_set_irq(s->irq, ((s->regs.isr1 & s->regs.ie1) > 0) ? 1 : 0);
DBG_L2("Set interrupt sr1 = 0x%08x isr1 = 0x%08x masked = 0x%08x\n",
s->regs.sr1, s->regs.isr1, s->regs.isr1 & s->regs.ie1);
}
static void pl041_request_data(void *opaque)
{
PL041State *s = (PL041State *)opaque;
/* Trigger pending transfers */
pl041_fifo1_transmit(s);
pl041_isr1_update(s);
}
static uint64_t pl041_read(void *opaque, hwaddr offset,
unsigned size)
{
PL041State *s = (PL041State *)opaque;
int value;
if ((offset >= PL041_periphid0) && (offset <= PL041_pcellid3)) {
if (offset == PL041_periphid3) {
value = pl041_compute_periphid3(s);
} else {
value = pl041_default_id[(offset - PL041_periphid0) >> 2];
}
DBG_L1("pl041_read [0x%08x] => 0x%08x\n", offset, value);
return value;
} else if (offset <= PL041_dr4_7) {
value = *((uint32_t *)&s->regs + (offset >> 2));
} else {
DBG_L1("pl041_read: Reserved offset %x\n", (int)offset);
return 0;
}
switch (offset) {
case PL041_allints:
value = s->regs.isr1 & 0x7F;
break;
}
DBG_L1("pl041_read [0x%08x] %s => 0x%08x\n", offset,
get_reg_name(offset), value);
return value;
}
static void pl041_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
PL041State *s = (PL041State *)opaque;
uint16_t control, data;
uint32_t result;
DBG_L1("pl041_write [0x%08x] %s <= 0x%08x\n", offset,
get_reg_name(offset), (unsigned int)value);
/* Write the register */
if (offset <= PL041_dr4_7) {
*((uint32_t *)&s->regs + (offset >> 2)) = value;
} else {
DBG_L1("pl041_write: Reserved offset %x\n", (int)offset);
return;
}
/* Execute the actions */
switch (offset) {
case PL041_txcr1:
{
pl041_channel *channel = &s->fifo1;
uint32_t txen = s->regs.txcr1 & TXEN;
uint32_t tsize = (s->regs.txcr1 & TSIZE_MASK) >> TSIZE_MASK_BIT;
uint32_t compact_mode = (s->regs.txcr1 & TXCOMPACT) ? 1 : 0;
#if defined(PL041_DEBUG_LEVEL)
uint32_t slots = (s->regs.txcr1 & TXSLOT_MASK) >> TXSLOT_MASK_BIT;
uint32_t txfen = (s->regs.txcr1 & TXFEN) > 0 ? 1 : 0;
#endif
DBG_L1("=> txen = %i slots = 0x%01x tsize = %i compact = %i "
"txfen = %i\n", txen, slots, tsize, compact_mode, txfen);
channel->tx_enabled = txen;
channel->tx_compact_mode = compact_mode;
switch (tsize) {
case 0:
channel->tx_sample_size = 16;
break;
case 1:
channel->tx_sample_size = 18;
break;
case 2:
channel->tx_sample_size = 20;
break;
case 3:
channel->tx_sample_size = 12;
break;
}
DBG_L1("TX enabled = %i\n", channel->tx_enabled);
DBG_L1("TX compact mode = %i\n", channel->tx_compact_mode);
DBG_L1("TX sample width = %i\n", channel->tx_sample_size);
/* Check if compact mode is allowed with selected tsize */
if (channel->tx_compact_mode == 1) {
if ((channel->tx_sample_size == 18) ||
(channel->tx_sample_size == 20)) {
channel->tx_compact_mode = 0;
DBG_L1("Compact mode not allowed with 18/20-bit sample size\n");
}
}
break;
}
case PL041_sl1tx:
s->regs.slfr &= ~SL1TXEMPTY;
control = (s->regs.sl1tx >> 12) & 0x7F;
data = (s->regs.sl2tx >> 4) & 0xFFFF;
if ((s->regs.sl1tx & SLOT1_RW) == 0) {
/* Write operation */
lm4549_write(&s->codec, control, data);
} else {
/* Read operation */
result = lm4549_read(&s->codec, control);
/* Store the returned value */
s->regs.sl1rx = s->regs.sl1tx & ~SLOT1_RW;
s->regs.sl2rx = result << 4;
s->regs.slfr &= ~(SL1RXBUSY | SL2RXBUSY);
s->regs.slfr |= SL1RXVALID | SL2RXVALID;
}
break;
case PL041_sl2tx:
s->regs.sl2tx = value;
s->regs.slfr &= ~SL2TXEMPTY;
break;
case PL041_intclr:
DBG_L1("=> Clear interrupt intclr = 0x%08x isr1 = 0x%08x\n",
s->regs.intclr, s->regs.isr1);
if (s->regs.intclr & TXUEC1) {
s->regs.sr1 &= ~TXUNDERRUN;
}
break;
case PL041_maincr:
{
#if defined(PL041_DEBUG_LEVEL)
char debug[] = " AACIFE SL1RXEN SL1TXEN";
if (!(value & AACIFE)) {
debug[0] = '!';
}
if (!(value & SL1RXEN)) {
debug[8] = '!';
}
if (!(value & SL1TXEN)) {
debug[17] = '!';
}
DBG_L1("%s\n", debug);
#endif
if ((s->regs.maincr & AACIFE) == 0) {
pl041_reset(s);
}
break;
}
case PL041_dr1_0:
case PL041_dr1_1:
case PL041_dr1_2:
case PL041_dr1_3:
pl041_fifo1_write(s, value);
break;
}
/* Transmit the FIFO content */
pl041_fifo1_transmit(s);
/* Update the ISR1 register */
pl041_isr1_update(s);
}
static void pl041_device_reset(DeviceState *d)
{
PL041State *s = PL041(d);
pl041_reset(s);
}
static const MemoryRegionOps pl041_ops = {
.read = pl041_read,
.write = pl041_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static int pl041_init(SysBusDevice *dev)
{
PL041State *s = PL041(dev);
DBG_L1("pl041_init 0x%08x\n", (uint32_t)s);
/* Check the device properties */
switch (s->fifo_depth) {
case 8:
case 32:
case 64:
case 128:
case 256:
case 512:
case 1024:
case 2048:
break;
case 16:
default:
/* NC FIFO depth of 16 is not allowed because its id bits in
AACIPERIPHID3 overlap with the id for the default NC FIFO depth */
qemu_log_mask(LOG_UNIMP,
"pl041: unsupported non-compact fifo depth [%i]\n",
s->fifo_depth);
return -1;
}
/* Connect the device to the sysbus */
memory_region_init_io(&s->iomem, OBJECT(s), &pl041_ops, s, "pl041", 0x1000);
sysbus_init_mmio(dev, &s->iomem);
sysbus_init_irq(dev, &s->irq);
/* Init the codec */
lm4549_init(&s->codec, &pl041_request_data, (void *)s);
return 0;
}
static const VMStateDescription vmstate_pl041_regfile = {
.name = "pl041_regfile",
.version_id = 1,
.minimum_version_id = 1,
.minimum_version_id_old = 1,
.fields = (VMStateField[]) {
#define REGISTER(name, offset) VMSTATE_UINT32(name, pl041_regfile),
#include "pl041.hx"
#undef REGISTER
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_pl041_fifo = {
.name = "pl041_fifo",
.version_id = 1,
.minimum_version_id = 1,
.minimum_version_id_old = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(level, pl041_fifo),
VMSTATE_UINT32_ARRAY(data, pl041_fifo, MAX_FIFO_DEPTH),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_pl041_channel = {
.name = "pl041_channel",
.version_id = 1,
.minimum_version_id = 1,
.minimum_version_id_old = 1,
.fields = (VMStateField[]) {
VMSTATE_STRUCT(tx_fifo, pl041_channel, 0,
vmstate_pl041_fifo, pl041_fifo),
VMSTATE_UINT8(tx_enabled, pl041_channel),
VMSTATE_UINT8(tx_compact_mode, pl041_channel),
VMSTATE_UINT8(tx_sample_size, pl041_channel),
VMSTATE_STRUCT(rx_fifo, pl041_channel, 0,
vmstate_pl041_fifo, pl041_fifo),
VMSTATE_UINT8(rx_enabled, pl041_channel),
VMSTATE_UINT8(rx_compact_mode, pl041_channel),
VMSTATE_UINT8(rx_sample_size, pl041_channel),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_pl041 = {
.name = "pl041",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(fifo_depth, PL041State),
VMSTATE_STRUCT(regs, PL041State, 0,
vmstate_pl041_regfile, pl041_regfile),
VMSTATE_STRUCT(fifo1, PL041State, 0,
vmstate_pl041_channel, pl041_channel),
VMSTATE_STRUCT(codec, PL041State, 0,
vmstate_lm4549_state, lm4549_state),
VMSTATE_END_OF_LIST()
}
};
static Property pl041_device_properties[] = {
/* Non-compact FIFO depth property */
DEFINE_PROP_UINT32("nc_fifo_depth", PL041State, fifo_depth,
DEFAULT_FIFO_DEPTH),
DEFINE_PROP_END_OF_LIST(),
};
static void pl041_device_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
k->init = pl041_init;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->cannot_instantiate_with_device_add_yet = true; /* FIXME explain why */
dc->reset = pl041_device_reset;
dc->vmsd = &vmstate_pl041;
dc->props = pl041_device_properties;
}
static const TypeInfo pl041_device_info = {
.name = TYPE_PL041,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(PL041State),
.class_init = pl041_device_class_init,
};
static void pl041_register_types(void)
{
type_register_static(&pl041_device_info);
}
type_init(pl041_register_types)