概述

本文描述了添加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控制器的框架代码,详细的代码修改,请参考文末的工程连接。

工程链接

https://gitee.com/yanl1229/qemu.git

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐