Qemu-NUC980(十二):I2S Controller
·
概述
本文描述了添加NUC980 I2S控制器功能代码的步骤,在描述过程中,为了清晰的描述添加框架步骤,部分代码的细节被删除,详细的代码,请参考文末的工程链接。
添加步骤
1、修改hw/arm/Kconfig,如下所示:
+号部分为新增加内容
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 9fa2d4ba..7b3fa9a3 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -125,6 +125,7 @@ config NUC980
select NUC980_SD
select NUC980_I2C
select NUC980_SPI
+ select NUC980_I2S
2、修改hw/arm/nuc980_soc.c,如下所示:
+号部分为新增加内容
diff --git a/hw/arm/nuc980_soc.c b/hw/arm/nuc980_soc.c
index 85b8086a..ebd5b5b5 100644
--- a/hw/arm/nuc980_soc.c
+++ b/hw/arm/nuc980_soc.c
@@ -12,6 +12,7 @@
#include "hw/i2c/i2c.h"
#include "hw/sysbus.h"
#include "hw/arm/nuc980.h"
+#include "hw/audio/wm8750.h"
@@ -102,6 +103,8 @@ static void nuc980_init(Object *obj)
sysbus_init_child_obj(obj, name, &s->i2c[i], sizeof(s->i2c[i]),
TYPE_NUC980_I2C);
}
+ /* i2s */
+ sysbus_init_child_obj(obj, "i2s", &s->i2s, sizeof(s->i2s), TYPE_NUC980I2S);
}
static void nuc980_realize(DeviceState *dev, Error **errp)
@@ -110,7 +113,9 @@ static void nuc980_realize(DeviceState *dev, Error **errp)
Error *err = NULL;
int i;
SSIBus *spi_bus;
+ I2CBus *i2c;
DeviceState *spi_dev;
+ DeviceState *wm8750_dev;
/* By default A9,A15 and ARM1176 CPUs have EL3 enabled. This board
* does not currently support EL3 so the CPU EL3 property is disabled
* before realization.
@@ -222,6 +227,28 @@ static void nuc980_realize(DeviceState *dev, Error **errp)
spi_bus = (SSIBus *)qdev_get_child_bus(spi_dev, "spi");
if (spi_bus)
ssi_create_slave(spi_bus, "w25q256fv");
+ /* i2s */
+ object_property_set_link(OBJECT(&s->i2s), OBJECT(get_system_memory()), "downstream",
+ &error_fatal);
+ if (error_fatal != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ dev = DEVICE(&(s->i2c[0]));
+ i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
+ wm8750_dev = i2c_create_slave(i2c, TYPE_WM8750, 0x1b);
+
+ object_property_set_link(OBJECT(&s->i2s), OBJECT(wm8750_dev),
+ "wm8750", NULL);
+ object_property_set_bool(OBJECT(&s->i2s), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2s), 0, I2S_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2s), 0,
+ qdev_get_gpio_in(DEVICE(&s->aic), I2S_IRQ));
}
static void nuc980_class_init(ObjectClass *oc, void *data)
3、修改hw/audio/Kconfig,如下所示:
+号部分为新增加内容
diff --git a/hw/audio/Kconfig b/hw/audio/Kconfig
index e9c6fed8..56eb0c2e 100644
--- a/hw/audio/Kconfig
+++ b/hw/audio/Kconfig
@@ -50,3 +50,6 @@ config CS4231
config MARVELL_88W8618
bool
+
+config NUC980_I2S
+ bool
4、修改hw/audio/Makefile.objs,如下所示:
+号部分为新增加内容
diff --git a/hw/audio/Makefile.objs b/hw/audio/Makefile.objs
index 63db3837..978fddf2 100644
--- a/hw/audio/Makefile.objs
+++ b/hw/audio/Makefile.objs
@@ -15,4 +15,7 @@ common-obj-$(CONFIG_CS4231) += cs4231.o
common-obj-$(CONFIG_MARVELL_88W8618) += marvell_88w8618.o
common-obj-$(CONFIG_MILKYMIST) += milkymist-ac97.o
+obj-$(CONFIG_NUC980_I2S) += nuc980_i2s.o
+
common-obj-y += soundhw.o
+
5、添加hw/audio/nuc980_i2s.c,如下所示:
+号部分为新增加内容
diff --git a/hw/audio/nuc980_i2s.c b/hw/audio/nuc980_i2s.c
new file mode 100644
index 00000000..31b9a646
--- /dev/null
+++ b/hw/audio/nuc980_i2s.c
@@ -0,0 +1,316 @@
+/*
+ */
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/audio/nuc980_i2s.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "hw/audio/wm8750.h"
+
+static void nuc980_audio_callback(void *opaque, int free_out, int free_in)
+{
+ NUC980I2SState *s = opaque;
+}
+
+static void nuc980_i2s_device_reset(DeviceState *d)
+{
+ NUC980I2SState *s = NUC980I2S(d);
+
+ s->play_buff_limit = 0;
+}
+
+static void nuc980_audio_clock_update(NUC980I2SState *s)
+{
+ wm8750_set_bclk_in(s->wm, 44100);
+}
+
+static uint64_t nuc980_i2s_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ NUC980I2SState *s = (NUC980I2SState *)opaque;
+
+ return value;
+}
+
+static void nuc980_i2s_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ NUC980I2SState *s = (NUC980I2SState *)opaque;
+ break;
+ }
+}
+
+static const MemoryRegionOps nuc980_i2s_ops = {
+ .read = nuc980_i2s_read,
+ .write = nuc980_i2s_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void nuc980_i2s_init(Object *obj)
+{
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+ NUC980I2SState *s = NUC980I2S(dev);
+
+ /* Connect the device to the sysbus */
+ memory_region_init_io(&s->iomem, obj, &nuc980_i2s_ops, s, TYPE_NUC980I2S, 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+ object_property_add_link(OBJECT(dev), "wm8750", TYPE_WM8750,
+ (Object **) &s->wm,
+ qdev_prop_allow_set_link_before_realize,
+ 0, &error_abort);
+}
+
+static void nuc980_i2s_realize(DeviceState *dev, Error **errp)
+{
+ NUC980I2SState *s = NUC980I2S(dev);
+
+ wm8750_data_req_set(s->wm, nuc980_audio_callback, s);
+ if (!s->downstream) {
+ error_setg(errp, "nuc980 i2s dma 'downstream' link not set");
+ return;
+ }
+ address_space_init(&s->downstream_as, s->downstream, "nuc980-i2s");
+}
+
+static Property nuc980_dma_properties[] = {
+ DEFINE_PROP_LINK("downstream", NUC980I2SState, downstream,
+ TYPE_MEMORY_REGION, MemoryRegion *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_nuc980_i2s = {
+ .name = TYPE_NUC980I2S,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(i2s_glbcon, NUC980I2SState),
......
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void nuc980_i2s_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = nuc980_i2s_realize;
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ dc->reset = nuc980_i2s_device_reset;
+ dc->vmsd = &vmstate_nuc980_i2s;
+ dc->props = nuc980_dma_properties;
+}
+
+static const TypeInfo nuc980_i2s_device_info = {
+ .name = TYPE_NUC980I2S,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NUC980I2SState),
+ .instance_init = nuc980_i2s_init,
+ .class_init = nuc980_i2s_device_class_init,
+};
+
+static void nuc980_i2s_register_types(void)
+{
+ type_register_static(&nuc980_i2s_device_info);
+}
+
+type_init(nuc980_i2s_register_types)
6、修改include/hw/arm/nuc980.h,如下所示:
+号部分为新增加内容
index 038af1fd..44aa7392 100644
--- a/include/hw/arm/nuc980.h
+++ b/include/hw/arm/nuc980.h
@@ -23,6 +23,7 @@
#include "hw/sd/nuc980_sd.h"
#include "hw/i2c/nuc980_i2c.h"
#include "hw/ssi/nuc980_spi.h"
+#include "hw/audio/nuc980_i2s.h"
#define SDRAM_BASE 0x0000000
#define SDRAM_SIZE (64 *1024 * 1024)
@@ -66,6 +67,9 @@
#define SPI0_BASE 0xb0061000
#define SPI0_IRQ 52
+#define I2S_BASE 0xb0020000
+#define I2S_IRQ 12
+
#define AIC_BASE 0xb0042000
#define SDH_BASE 0xb0018000
@@ -91,6 +95,7 @@ typedef struct NUC980State {
NUC980TimerState timer[TIMER_COUNT];
NUC980GPIOState gpio[GPIO_COUNT];
NUC980SDState sd;
+ NUC980I2SState i2s;
NUC980I2CState i2c[I2C_COUNT];
NUC980SPIState spi0;
7、添加include/hw/audio/nuc980_i2s.h,如下所示:
+号部分为新增加内容
new file mode 100644
index 00000000..113d34e4
--- /dev/null
+++ b/include/hw/audio/nuc980_i2s.h
@@ -0,0 +1,106 @@
+/*
+ * ARM NUC980 I2S controller
+ *
+ */
+
+#ifndef HW_NUC980_I2S_H
+#define HW_NUC980_I2S_H
+
+#include "hw/sysbus.h"
+
+#define I2S_GLBCON 0x00
+#define I2S_RESET 0x04
+#define I2S_RDESB 0x08
+#define I2S_RDES_LENGTH 0x0c
+#define I2S_RDESC 0x10
+#define I2S_PDESB 0x14
+#define I2S_PDES_LENGTH 0x18
+#define I2S_PDESC 0x1c
+#define I2S_RSR 0x20
+#define I2S_PSR 0x24
+#define I2S_CON 0x28
+#define I2S_COUNTER 0x2c
+#define I2S_PCMCON 0x30
+#define I2S_PCMS1ST 0x34
+#define I2S_PCMS2ST 0x38
+#define I2S_RDESB2 0x40
+#define I2S_PDESB2 0x44
+
+/* Status register and IRQ enable bits */
+#define MP_AUDIO_TX_HALF (1 << 6)
+#define MP_AUDIO_TX_FULL (1 << 7)
+
+/* Playback mode bits */
+#define MP_AUDIO_16BIT_SAMPLE (1 << 0)
+#define MP_AUDIO_PLAYBACK_EN (1 << 7)
+#define MP_AUDIO_CLOCK_24MHZ (1 << 9)
+#define MP_AUDIO_MONO (1 << 14)
+
+#define I2S_GLBCON_P_DMA_IRQ_EN (1 << 20)
+#define I2S_GLBCON_P_DMA_IRQ_HALF (1 << 12)
+#define I2S_GLBCON_P_DMA_IRQ (1 << 10)
+#define I2S_GLBCON_8BITS_SELECT (0 << 8)
+#define I2S_GLBCON_16BITS_SELECT (1 << 8)
+#define I2S_GLBCON_24BITS_SELECT (2 << 8)
+#define I2S_GLBCON_FIFO_TH (1 << 7)
+#define I2S_GLBCON_ACK (1 << 2)
+#define I2S_GLBCON_I2S_EN (1 << 0)
+
+#define I2S_RESET_MONO (2 << 12)
+#define I2S_RESET_PLAY (1 << 5)
+
+#define I2S_PSR_P_DMA_RIA_IRQ (1 << 0)
+
+#define TYPE_NUC980I2S "nuc980-i2s"
+#define NUC980I2S(obj) OBJECT_CHECK(NUC980I2SState, (obj), TYPE_NUC980I2S)
+
+typedef struct NUC980I2SState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ uint32_t i2s_glbcon;
+ uint32_t i2s_reset;
+ uint32_t i2s_rdesb;
+ uint32_t i2s_rdes_length;
+ uint32_t i2s_rdesc;
+ uint32_t i2s_pdesb;
+ uint32_t i2s_pdes_length;
+ uint32_t i2s_pdesc;
+ uint32_t i2s_rsr;
+ uint32_t i2s_psr;
+ uint32_t i2s_con;
+ uint32_t i2s_counter;
+ uint32_t i2s_pcmcon;
+ uint32_t i2s_pcms1st;
+ uint32_t i2s_pcms2st;
+ uint32_t i2s_rdesb2;
+ uint32_t i2s_pdesb2;
+
+ uint32_t play_buff_limit;
+
+ MemoryRegion *downstream;
+ AddressSpace downstream_as;
+
+ void *wm;
+} NUC980I2SState;
+
+#endif
\ No newline at end of file
总结
本文概要描述了使用qemu模拟NUC980 I2S控制器的框架代码,详细的代码修改,请参考文末的工程连接。
工程链接
更多推荐
所有评论(0)