summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikeTeachman <mike.teachman@gmail.com>2021-11-29 09:50:34 -0800
committerDamien George <damien@micropython.org>2022-03-30 14:12:40 +1100
commit1f6cb8f0476e699c1910ed59f1bb027fbef72ad7 (patch)
tree0c8513ca9b0ab56b35fe2e1f23bf4811abe627b9
parent5e685a9c6faeb764c0b689bc736ed00736bfd6b6 (diff)
mixmrt/machine_i2s: Add I2S protocol support.
This commit adds support for machine.I2S on the mimxrt port. The I2S API is consistent with the existing stm32, esp32, and rp2 implementations. I2S features: - controller transmit and controller receive - 16-bit and 32-bit sample sizes - mono and stereo formats - sampling frequencies from 8kHz to 48kHz - 3 modes of operation: - blocking - non-blocking with callback - uasyncio - configurable internal buffer - optional MCK Tested with the following development boards: - MIMXRT1010_EVK, MIMXRT1015_EVK, MIMXRT1020_EVK, MIMXRT1050_EVK - Teensy 4.0, Teensy 4.1 - Olimex RT1010 - Seeed ARCH MIX Tested with the following I2S hardware peripherals: - UDA1334 - GY-SPH0645LM4H - WM8960 codec on board the MIMXRT boards and separate breakout board - INMP441 - PCM5102 - SGTL5000 on the Teensy audio shield Signed-off-by: Mike Teachman <mike.teachman@gmail.com>
-rw-r--r--docs/library/machine.I2S.rst12
-rw-r--r--ports/mimxrt/Makefile9
-rw-r--r--ports/mimxrt/board_init.c4
-rw-r--r--ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h33
-rw-r--r--ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv7
-rw-r--r--ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h33
-rw-r--r--ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv9
-rw-r--r--ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h34
-rw-r--r--ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv7
-rw-r--r--ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h33
-rw-r--r--ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv7
-rw-r--r--ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h33
-rw-r--r--ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv7
-rw-r--r--ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h36
-rw-r--r--ports/mimxrt/boards/OLIMEX_RT1010/pins.csv7
-rw-r--r--ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h32
-rw-r--r--ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv7
-rw-r--r--ports/mimxrt/boards/TEENSY40/mpconfigboard.h38
-rw-r--r--ports/mimxrt/boards/TEENSY40/pins.csv9
-rw-r--r--ports/mimxrt/boards/TEENSY41/mpconfigboard.h40
-rwxr-xr-xports/mimxrt/boards/TEENSY41/pins.csv9
-rw-r--r--ports/mimxrt/dma_manager.c (renamed from ports/mimxrt/dma_channel.c)16
-rw-r--r--ports/mimxrt/dma_manager.h (renamed from ports/mimxrt/dma_channel.h)1
-rw-r--r--ports/mimxrt/machine_i2s.c1228
-rw-r--r--ports/mimxrt/machine_spi.c7
-rw-r--r--ports/mimxrt/main.c3
-rw-r--r--ports/mimxrt/modmachine.c3
-rw-r--r--ports/mimxrt/modmachine.h3
-rw-r--r--ports/mimxrt/mpconfigport.h13
-rw-r--r--ports/mimxrt/sdcard.c2
30 files changed, 1662 insertions, 20 deletions
diff --git a/docs/library/machine.I2S.rst b/docs/library/machine.I2S.rst
index d4e5ac943..e68a863d7 100644
--- a/docs/library/machine.I2S.rst
+++ b/docs/library/machine.I2S.rst
@@ -75,23 +75,19 @@ uasyncio::
Constructor
-----------
-.. class:: I2S(id, *, sck, ws, sd, mode, bits, format, rate, ibuf)
+.. class:: I2S(id, *, sck, ws, sd, mck=None, mode, bits, format, rate, ibuf)
Construct an I2S object of the given id:
- - ``id`` identifies a particular I2S bus.
-
- ``id`` is board and port specific:
-
- - PYBv1.0/v1.1: has one I2S bus with id=2.
- - PYBD-SFxW: has two I2S buses with id=1 and id=2.
- - ESP32: has two I2S buses with id=0 and id=1.
+ - ``id`` identifies a particular I2S bus; it is board and port specific
Keyword-only parameters that are supported on all ports:
- ``sck`` is a pin object for the serial clock line
- ``ws`` is a pin object for the word select line
- ``sd`` is a pin object for the serial data line
+ - ``mck`` is a pin object for the master clock line;
+ master clock frequency is sampling rate * 256
- ``mode`` specifies receive or transmit
- ``bits`` specifies sample size (bits), 16 or 32
- ``format`` specifies channel format, STEREO or MONO
diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile
index be5083965..f9c5bf402 100644
--- a/ports/mimxrt/Makefile
+++ b/ports/mimxrt/Makefile
@@ -77,7 +77,7 @@ INC += -I$(TOP)/lib/tinyusb/hw
INC += -I$(TOP)/lib/tinyusb/hw/bsp/teensy_40
INC += -I$(TOP)/lib/tinyusb/src
-CFLAGS_MCU = -mtune=cortex-m7 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16
+CFLAGS_MCU = -mtune=cortex-m7 -mcpu=cortex-m7
CFLAGS += $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 -nostdlib -mthumb $(CFLAGS_MCU)
CFLAGS += -DCPU_$(MCU_SERIES) -DCPU_$(MCU_VARIANT) -DBOARD_$(BOARD)
CFLAGS += -DXIP_EXTERNAL_FLASH=1 \
@@ -100,12 +100,14 @@ CFLAGS += $(CFLAGS_MOD) $(CFLAGS_EXTRA)
# Configure floating point support
ifeq ($(MICROPY_FLOAT_IMPL),double)
CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE
+CFLAGS_MCU += -mfloat-abi=hard -mfpu=fpv5-d16
else
ifeq ($(MICROPY_FLOAT_IMPL),none)
CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_NONE
else
CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT
CFLAGS += -fsingle-precision-constant
+CFLAGS_MCU += -mfloat-abi=softfp -mfpu=fpv5-sp-d16
endif
endif
@@ -188,6 +190,7 @@ SRC_HAL_IMX_C += \
$(MCU_DIR)/drivers/fsl_lpuart.c \
$(MCU_DIR)/drivers/fsl_pit.c \
$(MCU_DIR)/drivers/fsl_pwm.c \
+ $(MCU_DIR)/drivers/fsl_sai.c \
$(MCU_DIR)/drivers/fsl_snvs_lp.c \
$(MCU_DIR)/drivers/fsl_trng.c \
$(MCU_DIR)/drivers/fsl_wdog.c \
@@ -210,7 +213,7 @@ endif
SRC_C += \
board_init.c \
- dma_channel.c \
+ dma_manager.c \
drivers/bus/softspi.c \
drivers/dht/dht.c \
eth.c \
@@ -224,6 +227,7 @@ SRC_C += \
machine_adc.c \
machine_bitstream.c \
machine_i2c.c \
+ machine_i2s.c \
machine_led.c \
machine_pin.c \
machine_rtc.c \
@@ -396,6 +400,7 @@ SRC_QSTR += \
extmod/modonewire.c \
extmod/uos_dupterm.c \
machine_adc.c \
+ machine_i2s.c \
machine_led.c \
machine_pin.c \
machine_pwm.c \
diff --git a/ports/mimxrt/board_init.c b/ports/mimxrt/board_init.c
index d96645fef..82af620a8 100644
--- a/ports/mimxrt/board_init.c
+++ b/ports/mimxrt/board_init.c
@@ -96,6 +96,10 @@ void board_init(void) {
#if MICROPY_PY_MACHINE_SDCARD
machine_sdcard_init0();
#endif
+
+ #if MICROPY_PY_MACHINE_I2S
+ machine_i2s_init0();
+ #endif
}
void USB_OTG1_IRQHandler(void) {
diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h
index 726a63904..75fda0cad 100644
--- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h
+++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h
@@ -44,3 +44,36 @@
#define IOMUX_TABLE_I2C \
{ IOMUXC_GPIO_02_LPI2C1_SCL }, { IOMUXC_GPIO_01_LPI2C1_SDA }, \
{ IOMUXC_GPIO_10_LPI2C2_SCL }, { IOMUXC_GPIO_09_LPI2C2_SDA },
+
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx }
+#define I2S_WM8960_RX_MODE (1)
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_08, IOMUXC_GPIO_08_SAI1_MCLK), \
+ I2S_GPIO(1, SCK, RX, GPIO_01, IOMUXC_GPIO_01_SAI1_RX_BCLK), \
+ I2S_GPIO(1, WS, RX, GPIO_02, IOMUXC_GPIO_02_SAI1_RX_SYNC), \
+ I2S_GPIO(1, SD, RX, GPIO_03, IOMUXC_GPIO_03_SAI1_RX_DATA00), \
+ I2S_GPIO(1, SCK, TX, GPIO_06, IOMUXC_GPIO_06_SAI1_TX_BCLK), \
+ I2S_GPIO(1, WS, TX, GPIO_07, IOMUXC_GPIO_07_SAI1_TX_SYNC), \
+ I2S_GPIO(1, SD, TX, GPIO_04, IOMUXC_GPIO_04_SAI1_TX_DATA00), \
+ }
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv
index 1f01215dd..d8710f8ce 100644
--- a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv
+++ b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv
@@ -34,3 +34,10 @@ PWM_CB,GPIO_05
ENC_A,GPIO_AD_05
ENC_B,GPIO_AD_06
LED_GREEN,GPIO_11
+MCK,GPIO_08
+SCK_RX,GPIO_01
+WS_RX,GPIO_02
+SD_RX,GPIO_03
+SCK_TX,GPIO_06
+WS_TX,GPIO_07
+SD_TX,GPIO_04
diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h
index 2e7ee34e8..1dfe02b65 100644
--- a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h
+++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h
@@ -65,6 +65,36 @@
{ 0 }, { 0 }, \
{ IOMUXC_GPIO_SD_B1_02_LPI2C4_SCL }, { IOMUXC_GPIO_SD_B1_03_LPI2C4_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_00, IOMUXC_GPIO_AD_B1_00_SAI1_MCLK), \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_06, IOMUXC_GPIO_AD_B1_06_SAI1_RX_BCLK), \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_04, IOMUXC_GPIO_AD_B1_04_SAI1_RX_SYNC), \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_05, IOMUXC_GPIO_AD_B1_05_SAI1_RX_DATA00), \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_01, IOMUXC_GPIO_AD_B1_01_SAI1_TX_BCLK), \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_02, IOMUXC_GPIO_AD_B1_02_SAI1_TX_SYNC), \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_03, IOMUXC_GPIO_AD_B1_03_SAI1_TX_DATA00), \
+ }
+
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
{ \
@@ -142,3 +172,6 @@
{ IOMUXC_GPIO_AD_B0_15_ENET_TDATA01, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_40_ENET_MDIO, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_41_ENET_MDC, 0, 0xB0E9u },
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv
index 8420bbd82..e1f2669b0 100644
--- a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv
+++ b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv
@@ -30,4 +30,11 @@ SCK,GPIO_AD_B0_10
SDI,GPIO_AD_B0_13
SDO,GPIO_AD_B0_12
CS,GPIO_AD_B0_11
-LED_GREEN,GPIO_AD_B0_05 \ No newline at end of file
+LED_GREEN,GPIO_AD_B0_05
+MCK,GPIO_AD_B1_00
+SCK_RX,GPIO_AD_B1_06
+WS_RX,GPIO_AD_B1_04
+SD_RX,GPIO_AD_B1_05
+SCK_TX,GPIO_AD_B1_01
+WS_TX,GPIO_AD_B1_02
+SD_TX,GPIO_AD_B1_03
diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h
index c2783a3e5..613095e67 100644
--- a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h
+++ b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h
@@ -53,6 +53,37 @@
{ 0 }, { 0 }, \
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+#define I2S_WM8960_RX_MODE (1)
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00), \
+ }
+
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
@@ -131,3 +162,6 @@
{ IOMUXC_GPIO_B1_11_ENET_RX_ER, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_41_ENET_MDIO, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_40_ENET_MDC, 0, 0xB0E9u },
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv b/ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv
index 463079b12..4bdd9e4f0 100644
--- a/ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv
+++ b/ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv
@@ -29,3 +29,10 @@ SDI,GPIO_SD_B0_03
SDO,GPIO_SD_B0_02
CS,GPIO_SD_B0_01
LED_GREEN,GPIO_AD_B0_09
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_AD_B1_12
+SCK_TX,GPIO_AD_B1_14
+WS_TX,GPIO_AD_B1_15
+SD_TX,GPIO_AD_B1_13
diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h
index 475d07ddb..a3f006238 100644
--- a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h
+++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h
@@ -53,6 +53,36 @@
{ 0 }, { 0 }, \
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+#define I2S_WM8960_RX_MODE (1)
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00), \
+ }
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
{ \
@@ -130,3 +160,6 @@
{ IOMUXC_GPIO_B1_11_ENET_RX_ER, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_41_ENET_MDIO, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_40_ENET_MDC, 0, 0xB0E9u },
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv
index 463079b12..4bdd9e4f0 100644
--- a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv
+++ b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv
@@ -29,3 +29,10 @@ SDI,GPIO_SD_B0_03
SDO,GPIO_SD_B0_02
CS,GPIO_SD_B0_01
LED_GREEN,GPIO_AD_B0_09
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_AD_B1_12
+SCK_TX,GPIO_AD_B1_14
+WS_TX,GPIO_AD_B1_15
+SD_TX,GPIO_AD_B1_13
diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h
index 874991ac6..fe1fb532b 100644
--- a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h
+++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h
@@ -53,6 +53,36 @@
{ 0 }, { 0 }, \
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+#define I2S_WM8960_RX_MODE (1)
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00), \
+ }
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
{ \
@@ -130,3 +160,6 @@
{ IOMUXC_GPIO_B1_11_ENET_RX_ER, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_41_ENET_MDIO, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_EMC_40_ENET_MDC, 0, 0xB0E9u },
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv
index 463079b12..4bdd9e4f0 100644
--- a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv
+++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv
@@ -29,3 +29,10 @@ SDI,GPIO_SD_B0_03
SDO,GPIO_SD_B0_02
CS,GPIO_SD_B0_01
LED_GREEN,GPIO_AD_B0_09
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_AD_B1_12
+SCK_TX,GPIO_AD_B1_14
+WS_TX,GPIO_AD_B1_15
+SD_TX,GPIO_AD_B1_13
diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h
index e004050ae..c30caa047 100644
--- a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h
+++ b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h
@@ -48,3 +48,39 @@
#define IOMUX_TABLE_I2C \
{ IOMUXC_GPIO_AD_14_LPI2C1_SCL }, { IOMUXC_GPIO_AD_13_LPI2C1_SDA }, \
{ IOMUXC_GPIO_AD_08_LPI2C2_SCL }, { IOMUXC_GPIO_AD_07_LPI2C2_SDA },
+
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (3)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, 0, kCLOCK_Sai3Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, 0, kCLOCK_Sai3PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, 0, kCLOCK_Sai3Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, 0, kIOMUXC_GPR_SAI3MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, 0, kDmaRequestMuxSai3Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, 0, kDmaRequestMuxSai3Tx }
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_08, IOMUXC_GPIO_08_SAI1_MCLK), /* pin D8 */ \
+ I2S_GPIO(1, SCK, RX, GPIO_01, IOMUXC_GPIO_01_SAI1_RX_BCLK), /* pin D1 */ \
+ I2S_GPIO(1, WS, RX, GPIO_02, IOMUXC_GPIO_02_SAI1_RX_SYNC), /* pin D2 */ \
+ I2S_GPIO(1, SD, RX, GPIO_03, IOMUXC_GPIO_03_SAI1_RX_DATA00), /* pin D3 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_06, IOMUXC_GPIO_06_SAI1_TX_BCLK), /* pin D6 */ \
+ I2S_GPIO(1, WS, TX, GPIO_07, IOMUXC_GPIO_07_SAI1_TX_SYNC), /* pin D7 */ \
+ I2S_GPIO(1, SD, TX, GPIO_04, IOMUXC_GPIO_04_SAI1_TX_DATA00), /* pin D4 */ \
+ I2S_GPIO(3, SCK, TX, GPIO_SD_01, IOMUXC_GPIO_SD_01_SAI3_TX_BCLK), /* pin D10 */ \
+ I2S_GPIO(3, WS, TX, GPIO_SD_00, IOMUXC_GPIO_SD_00_SAI3_TX_SYNC), /* pin D9 */ \
+ I2S_GPIO(3, SD, TX, GPIO_SD_02, IOMUXC_GPIO_SD_02_SAI3_TX_DATA) /* pin D11 */ \
+ }
+
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/pins.csv b/ports/mimxrt/boards/OLIMEX_RT1010/pins.csv
index d294f86b6..d3c68aba2 100644
--- a/ports/mimxrt/boards/OLIMEX_RT1010/pins.csv
+++ b/ports/mimxrt/boards/OLIMEX_RT1010/pins.csv
@@ -34,3 +34,10 @@ RX, GPIO_09
TX, GPIO_10
RELAY1,GPIO_SD_12
RELAY2,GPIO_SD_13
+MCK,GPIO_08
+SCK_RX,GPIO_01
+WS_RX,GPIO_02
+SD_RX,GPIO_03
+SCK_TX,GPIO_06
+WS_TX,GPIO_07
+SD_TX,GPIO_04
diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h
index 53a3d4ed7..a6502d335 100644
--- a/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h
+++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/mpconfigboard.h
@@ -65,6 +65,35 @@
{ IOMUXC_GPIO_B0_04_LPI2C2_SCL }, { IOMUXC_GPIO_B0_05_LPI2C2_SDA }, \
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA }
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (1)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx }
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), /* pin J4 09 */ \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), /* pin J4 11 */ \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), /* pin J4 10 */ \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), /* pin J4 12 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), /* pin J4 14 */ \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), /* pin J4 15 */ \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00) /* pin J4 13 */ \
+ }
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
@@ -144,3 +173,6 @@
#define MIMXRT_IOMUXC_SEMC_WE IOMUXC_GPIO_EMC_28_SEMC_WE
#define MIMXRT_IOMUXC_SEMC_CS0 IOMUXC_GPIO_EMC_29_SEMC_CS0
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv b/ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv
index 0183795cd..6b757fae4 100644
--- a/ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv
+++ b/ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv
@@ -60,3 +60,10 @@ J5_50,GPIO_AD_B0_02
LED_RED,GPIO_AD_B0_09
LED_GREEN,GPIO_AD_B0_10
LED_BLUE,GPIO_AD_B0_11
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_AD_B1_12
+SCK_TX,GPIO_AD_B1_14
+WS_TX,GPIO_AD_B1_15
+SD_TX,GPIO_AD_B1_13
diff --git a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h
index 08a774d88..f2ea86bd0 100644
--- a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h
+++ b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h
@@ -58,6 +58,41 @@
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA }, \
{ IOMUXC_GPIO_AD_B0_12_LPI2C4_SCL }, { IOMUXC_GPIO_AD_B0_13_LPI2C4_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (2)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), /* pin 21 */ \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), /* pin 20 */ \
+ I2S_GPIO(1, SD, RX, GPIO_B1_00, IOMUXC_GPIO_B1_00_SAI1_RX_DATA00), /* pin 8 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), /* pin 26 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_B1_02, IOMUXC_GPIO_B1_02_SAI1_TX_BCLK), /* pin 36 */ \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), /* pin 27 */ \
+ I2S_GPIO(1, WS, TX, GPIO_B1_03, IOMUXC_GPIO_B1_03_SAI1_TX_SYNC), /* pin 37 */ \
+ I2S_GPIO(1, SD, TX, GPIO_B1_01, IOMUXC_GPIO_B1_01_SAI1_TX_DATA00), /* pin 7 */ \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), /* pin 23 */ \
+ I2S_GPIO(2, SCK, TX, GPIO_EMC_06, IOMUXC_GPIO_EMC_06_SAI2_TX_BCLK), /* pin 4 */ \
+ I2S_GPIO(2, WS, TX, GPIO_EMC_05, IOMUXC_GPIO_EMC_05_SAI2_TX_SYNC), /* pin 3 */ \
+ I2S_GPIO(2, SD, TX, GPIO_EMC_04, IOMUXC_GPIO_EMC_04_SAI2_TX_DATA), /* pin 2 */ \
+ I2S_GPIO(2, MCK, TX, GPIO_EMC_07, IOMUXC_GPIO_EMC_07_SAI2_MCLK) /* pin 33 */ \
+ }
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
{ \
@@ -69,3 +104,6 @@
.data2 = { GPIO_SD_B0_04_USDHC1_DATA2 }, \
.data3 = { GPIO_SD_B0_05_USDHC1_DATA3 }, \
}
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/TEENSY40/pins.csv b/ports/mimxrt/boards/TEENSY40/pins.csv
index 0ea4f1373..b4509e584 100644
--- a/ports/mimxrt/boards/TEENSY40/pins.csv
+++ b/ports/mimxrt/boards/TEENSY40/pins.csv
@@ -52,4 +52,11 @@ A10,GPIO_AD_B0_12
A11,GPIO_AD_B0_13
A12,GPIO_AD_B1_14
A13,GPIO_AD_B1_15
-LED,GPIO_B0_03 \ No newline at end of file
+LED,GPIO_B0_03
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_B1_00
+SCK_TX,GPIO_EMC_06
+WS_TX,GPIO_EMC_05
+SD_TX,GPIO_EMC_04
diff --git a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h
index 3b8d497c3..7d75f62f3 100644
--- a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h
+++ b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h
@@ -58,6 +58,43 @@
{ IOMUXC_GPIO_AD_B1_07_LPI2C3_SCL }, { IOMUXC_GPIO_AD_B1_06_LPI2C3_SDA }, \
{ IOMUXC_GPIO_AD_B0_12_LPI2C4_SCL }, { IOMUXC_GPIO_AD_B0_13_LPI2C4_SDA },
+#define MICROPY_PY_MACHINE_I2S (1)
+#define MICROPY_HW_I2S_NUM (2)
+#define I2S_CLOCK_MUX { 0, kCLOCK_Sai1Mux, kCLOCK_Sai2Mux }
+#define I2S_CLOCK_PRE_DIV { 0, kCLOCK_Sai1PreDiv, kCLOCK_Sai2PreDiv }
+#define I2S_CLOCK_DIV { 0, kCLOCK_Sai1Div, kCLOCK_Sai2Div }
+#define I2S_IOMUXC_GPR_MODE { 0, kIOMUXC_GPR_SAI1MClkOutputDir, kIOMUXC_GPR_SAI2MClkOutputDir }
+#define I2S_DMA_REQ_SRC_RX { 0, kDmaRequestMuxSai1Rx, kDmaRequestMuxSai2Rx }
+#define I2S_DMA_REQ_SRC_TX { 0, kDmaRequestMuxSai1Tx, kDmaRequestMuxSai2Tx }
+
+#define I2S_GPIO(_hwid, _fn, _mode, _pin, _iomux) \
+ { \
+ .hw_id = _hwid, \
+ .fn = _fn, \
+ .mode = _mode, \
+ .name = MP_QSTR_##_pin, \
+ .iomux = {_iomux}, \
+ }
+
+#define I2S_GPIO_MAP \
+ { \
+ I2S_GPIO(1, SCK, RX, GPIO_AD_B1_11, IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK), /* pin 21 */ \
+ I2S_GPIO(1, WS, RX, GPIO_AD_B1_10, IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC), /* pin 20 */ \
+ I2S_GPIO(1, SD, RX, GPIO_AD_B1_12, IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00), /* pin 38 */ \
+ I2S_GPIO(1, SD, RX, GPIO_B1_00, IOMUXC_GPIO_B1_00_SAI1_RX_DATA00), /* pin 8 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_AD_B1_14, IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK), /* pin 26 */ \
+ I2S_GPIO(1, SCK, TX, GPIO_B1_02, IOMUXC_GPIO_B1_02_SAI1_TX_BCLK), /* pin 36 */ \
+ I2S_GPIO(1, WS, TX, GPIO_AD_B1_15, IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC), /* pin 27 */ \
+ I2S_GPIO(1, WS, TX, GPIO_B1_03, IOMUXC_GPIO_B1_03_SAI1_TX_SYNC), /* pin 37 */ \
+ I2S_GPIO(1, SD, TX, GPIO_AD_B1_13, IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00), /* pin 39 */ \
+ I2S_GPIO(1, SD, TX, GPIO_B1_01, IOMUXC_GPIO_B1_01_SAI1_TX_DATA00), /* pin 7 */ \
+ I2S_GPIO(1, MCK, TX, GPIO_AD_B1_09, IOMUXC_GPIO_AD_B1_09_SAI1_MCLK), /* pin 23 */ \
+ I2S_GPIO(2, SCK, TX, GPIO_EMC_06, IOMUXC_GPIO_EMC_06_SAI2_TX_BCLK), /* pin 4 */ \
+ I2S_GPIO(2, WS, TX, GPIO_EMC_05, IOMUXC_GPIO_EMC_05_SAI2_TX_SYNC), /* pin 3 */ \
+ I2S_GPIO(2, SD, TX, GPIO_EMC_04, IOMUXC_GPIO_EMC_04_SAI2_TX_DATA), /* pin 2 */ \
+ I2S_GPIO(2, MCK, TX, GPIO_EMC_07, IOMUXC_GPIO_EMC_07_SAI2_MCLK) /* pin 33 */ \
+ }
+
#define USDHC_DUMMY_PIN NULL, 0
#define MICROPY_USDHC1 \
{ \
@@ -90,3 +127,6 @@
{ IOMUXC_GPIO_B1_11_ENET_RX_ER, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_B1_15_ENET_MDIO, 0, 0xB0E9u }, \
{ IOMUXC_GPIO_B1_14_ENET_MDC, 0, 0xB0E9u },
+
+#define MICROPY_BOARD_ROOT_POINTERS \
+ struct _machine_i2s_obj_t *machine_i2s_obj[MICROPY_HW_I2S_NUM];
diff --git a/ports/mimxrt/boards/TEENSY41/pins.csv b/ports/mimxrt/boards/TEENSY41/pins.csv
index 88b91f254..b28a22312 100755
--- a/ports/mimxrt/boards/TEENSY41/pins.csv
+++ b/ports/mimxrt/boards/TEENSY41/pins.csv
@@ -80,4 +80,11 @@ A10,GPIO_AD_B0_12
A11,GPIO_AD_B0_13
A12,GPIO_AD_B1_14
A13,GPIO_AD_B1_15
-LED,GPIO_B0_03 \ No newline at end of file
+LED,GPIO_B0_03
+MCK,GPIO_AD_B1_09
+SCK_RX,GPIO_AD_B1_11
+WS_RX,GPIO_AD_B1_10
+SD_RX,GPIO_B1_00
+SCK_TX,GPIO_EMC_06
+WS_TX,GPIO_EMC_05
+SD_TX,GPIO_EMC_04
diff --git a/ports/mimxrt/dma_channel.c b/ports/mimxrt/dma_manager.c
index c6cae9da9..2890a1558 100644
--- a/ports/mimxrt/dma_channel.c
+++ b/ports/mimxrt/dma_manager.c
@@ -24,7 +24,10 @@
* THE SOFTWARE.
*/
-#include "dma_channel.h"
+#include <stdbool.h>
+#include "py/mpconfig.h"
+#include "fsl_edma.h"
+#include "dma_manager.h"
// List of channel flags: true: channel used, false: channel available
static bool channel_list[FSL_FEATURE_DMAMUX_MODULE_CHANNEL] = {
@@ -39,6 +42,8 @@ static bool channel_list[FSL_FEATURE_DMAMUX_MODULE_CHANNEL] = {
#endif
};
+STATIC bool dma_initialized = false;
+
// allocate_channel(): retrieve an available channel. Return the number or -1
int allocate_dma_channel(void) {
for (int i = 0; i < ARRAY_SIZE(channel_list); i++) {
@@ -56,3 +61,12 @@ void free_dma_channel(int n) {
channel_list[n] = false;
}
}
+
+void dma_init(void) {
+ if (!dma_initialized) {
+ edma_config_t dmaConfig;
+ EDMA_GetDefaultConfig(&dmaConfig);
+ EDMA_Init(DMA0, &dmaConfig);
+ dma_initialized = true;
+ }
+}
diff --git a/ports/mimxrt/dma_channel.h b/ports/mimxrt/dma_manager.h
index 3fe66a3db..77e9689b2 100644
--- a/ports/mimxrt/dma_channel.h
+++ b/ports/mimxrt/dma_manager.h
@@ -30,5 +30,6 @@
int allocate_dma_channel(void);
void free_dma_channel(int n);
+void dma_init(void);
#endif // MICROPY_INCLUDED_MIMXRT_DMACHANNEL_H
diff --git a/ports/mimxrt/machine_i2s.c b/ports/mimxrt/machine_i2s.c
new file mode 100644
index 000000000..a41dfe94f
--- /dev/null
+++ b/ports/mimxrt/machine_i2s.c
@@ -0,0 +1,1228 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022 Mike Teachman
+ * Copyright (c) 2022 Robert Hammelrath
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "py/misc.h"
+#include "py/stream.h"
+#include "py/objstr.h"
+#include "modmachine.h"
+#include "dma_manager.h"
+
+#include "clock_config.h"
+#include "fsl_iomuxc.h"
+#include "fsl_dmamux.h"
+#include "fsl_edma.h"
+#include "fsl_sai.h"
+
+#if MICROPY_PY_MACHINE_I2S
+// The I2S module has 3 modes of operation:
+//
+// Mode1: Blocking
+// - readinto() and write() methods block until the supplied buffer is filled (read) or emptied (write)
+// - this is the default mode of operation
+//
+// Mode2: Non-Blocking
+// - readinto() and write() methods return immediately
+// - buffer filling and emptying happens asynchronously to the main MicroPython task
+// - a callback function is called when the supplied buffer has been filled (read) or emptied (write)
+// - non-blocking mode is enabled when a callback is set with the irq() method
+// - the DMA callback is used to implement the asynchronous background operations
+//
+// Mode3: Uasyncio
+// - implements the stream protocol
+// - uasyncio mode is enabled when the ioctl() function is called
+// - the state of the internal ring buffer is used to detect that I2S samples can be read or written
+//
+// The samples contained in the app buffer supplied for the readinto() and write() methods have the following convention:
+// Mono: little endian format
+// Stereo: little endian format, left channel first
+//
+// I2S terms:
+// "frame": consists of two audio samples (Left audio sample + Right audio sample)
+//
+// Misc:
+// - for Mono configuration:
+// - readinto method: samples are gathered from the L channel only
+// - write method: every sample is output to both the L and R channels
+// - for readinto method the I2S hardware is read using 8-byte frames
+// (this is standard for almost all I2S hardware, such as MEMS microphones)
+// - all 3 Modes of operation are implemented using the peripheral drivers in the NXP MCUXpresso SDK
+// - all sample data transfers use DMA
+// - the DMA ping-pong buffer needs to be aligned to a cache line size of 32 bytes. 32 byte
+// alignment is needed to use the routines that clean and invalidate D-Cache which work on a
+// 32 byte address boundary.
+// - master clock frequency is sampling frequency * 256
+
+// DMA ping-pong buffer size was empirically determined. It is a tradeoff between:
+// 1. memory use (smaller buffer size desirable to reduce memory footprint)
+// 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency)
+// The sizeof 1/2 of the DMA buffer must be evenly divisible by the cache line size of 32 bytes.
+#define SIZEOF_DMA_BUFFER_IN_BYTES (256)
+#define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2)
+
+// For non-blocking mode, to avoid underflow/overflow, sample data is written/read to/from the ring buffer at a rate faster
+// than the DMA transfer rate
+#define NON_BLOCKING_RATE_MULTIPLIER (4)
+#define SIZEOF_NON_BLOCKING_COPY_IN_BYTES (SIZEOF_HALF_DMA_BUFFER_IN_BYTES * NON_BLOCKING_RATE_MULTIPLIER)
+
+#define NUM_I2S_USER_FORMATS (4)
+#define I2S_RX_FRAME_SIZE_IN_BYTES (8)
+#define AUDIO_PLL_CLOCK (2U)
+#define SAI_CHANNEL_0 (0)
+#define SAI_NUM_AUDIO_CHANNELS (2U)
+
+typedef enum {
+ SCK,
+ WS,
+ SD,
+ MCK
+} i2s_pin_function_t;
+
+typedef enum {
+ RX,
+ TX,
+} i2s_mode_t;
+
+typedef enum {
+ MONO,
+ STEREO
+} format_t;
+
+typedef enum {
+ BLOCKING,
+ NON_BLOCKING,
+ UASYNCIO
+} io_mode_t;
+
+typedef enum {
+ TOP_HALF,
+ BOTTOM_HALF
+} ping_pong_t;
+
+typedef struct _ring_buf_t {
+ uint8_t *buffer;
+ size_t head;
+ size_t tail;
+ size_t size;
+} ring_buf_t;
+
+typedef struct _non_blocking_descriptor_t {
+ mp_buffer_info_t appbuf;
+ uint32_t index;
+ bool copy_in_progress;
+} non_blocking_descriptor_t;
+
+typedef struct _machine_i2s_obj_t {
+ mp_obj_base_t base;
+ uint8_t i2s_id;
+ mp_hal_pin_obj_t sck;
+ mp_hal_pin_obj_t ws;
+ mp_hal_pin_obj_t sd;
+ mp_hal_pin_obj_t mck;
+ i2s_mode_t mode;
+ int8_t bits;
+ format_t format;
+ int32_t rate;
+ int32_t ibuf;
+ mp_obj_t callback_for_non_blocking;
+ uint8_t dma_buffer[SIZEOF_DMA_BUFFER_IN_BYTES + 0x1f]; // 0x1f related to D-Cache alignment
+ uint8_t *dma_buffer_dcache_aligned;
+ ring_buf_t ring_buffer;
+ uint8_t *ring_buffer_storage;
+ non_blocking_descriptor_t non_blocking_descriptor;
+ io_mode_t io_mode;
+ I2S_Type *i2s_inst;
+ int dma_channel;
+ edma_handle_t edmaHandle;
+ edma_tcd_t *edmaTcd;
+} machine_i2s_obj_t;
+
+typedef struct _iomux_table_t {
+ uint32_t muxRegister;
+ uint32_t muxMode;
+ uint32_t inputRegister;
+ uint32_t inputDaisy;
+ uint32_t configRegister;
+} iomux_table_t;
+
+typedef struct _gpio_map_t {
+ uint8_t hw_id;
+ i2s_pin_function_t fn;
+ i2s_mode_t mode;
+ qstr name;
+ iomux_table_t iomux;
+} gpio_map_t;
+
+typedef struct _i2s_clock_config_t {
+ sai_sample_rate_t rate;
+ const clock_audio_pll_config_t *pll_config;
+ uint32_t clock_pre_divider;
+ uint32_t clock_divider;
+} i2s_clock_config_t;
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in);
+
+// The frame map is used with the readinto() method to transform the audio sample data coming
+// from DMA memory (32-bit stereo) to the format specified
+// in the I2S constructor. e.g. 16-bit mono
+STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = {
+ {-1, -1, 0, 1, -1, -1, -1, -1 }, // Mono, 16-bits
+ { 0, 1, 2, 3, -1, -1, -1, -1 }, // Mono, 32-bits
+ {-1, -1, 0, 1, -1, -1, 2, 3 }, // Stereo, 16-bits
+ { 0, 1, 2, 3, 4, 5, 6, 7 }, // Stereo, 32-bits
+};
+
+// 2 PLL configurations
+// PLL output frequency = 24MHz * (.loopDivider + .numerator/.denominator)
+
+// Configuration 1: for sampling frequencies [Hz]: 8000, 12000, 16000, 24000, 32000, 48000
+// Clock frequency = 786,432,480 Hz
+STATIC const clock_audio_pll_config_t audioPllConfig_8000_48000 = {
+ .loopDivider = 32, // PLL loop divider. Valid range for DIV_SELECT divider value: 27~54
+ .postDivider = 1, // Divider after the PLL, should only be 1, 2, 4, 8, 16
+ .numerator = 76802, // 30 bit numerator of fractional loop divider
+ .denominator = 100000, // 30 bit denominator of fractional loop divider
+ .src = kCLOCK_PllClkSrc24M // Pll clock source
+};
+
+// Configuration 2: for sampling frequencies [Hz]: 11025, 22050, 44100
+// Clock frequency = 722,534,880
+STATIC const clock_audio_pll_config_t audioPllConfig_11025_44100 = {
+ .loopDivider = 30, // PLL loop divider. Valid range for DIV_SELECT divider value: 27~54
+ .postDivider = 1, // Divider after the PLL, should only be 1, 2, 4, 8, 16
+ .numerator = 10562, // 30 bit numerator of fractional loop divider
+ .denominator = 100000, // 30 bit denominator of fractional loop divider
+ .src = kCLOCK_PllClkSrc24M // Pll clock source
+};
+
+STATIC const i2s_clock_config_t clock_config_map[] = {
+ {kSAI_SampleRate8KHz, &audioPllConfig_8000_48000, 5, 63},
+ {kSAI_SampleRate11025Hz, &audioPllConfig_11025_44100, 3, 63},
+ {kSAI_SampleRate12KHz, &audioPllConfig_8000_48000, 3, 63},
+ {kSAI_SampleRate16KHz, &audioPllConfig_8000_48000, 2, 63},
+ {kSAI_SampleRate22050Hz, &audioPllConfig_11025_44100, 1, 63},
+ {kSAI_SampleRate24KHz, &audioPllConfig_8000_48000, 1, 63},
+ {kSAI_SampleRate32KHz, &audioPllConfig_8000_48000, 1, 47},
+ {kSAI_SampleRate44100Hz, &audioPllConfig_11025_44100, 0, 63},
+ {kSAI_SampleRate48KHz, &audioPllConfig_8000_48000, 0, 63}
+};
+
+STATIC const I2S_Type *i2s_base_ptr[] = I2S_BASE_PTRS;
+STATIC const clock_mux_t i2s_clock_mux[] = I2S_CLOCK_MUX;
+STATIC const clock_div_t i2s_clock_pre_div[] = I2S_CLOCK_PRE_DIV;
+STATIC const clock_div_t i2s_clock_div[] = I2S_CLOCK_DIV;
+STATIC const iomuxc_gpr_mode_t i2s_iomuxc_gpr_mode[] = I2S_IOMUXC_GPR_MODE;
+STATIC const dma_request_source_t i2s_dma_req_src_tx[] = I2S_DMA_REQ_SRC_TX;
+STATIC const dma_request_source_t i2s_dma_req_src_rx[] = I2S_DMA_REQ_SRC_RX;
+STATIC const gpio_map_t i2s_gpio_map[] = I2S_GPIO_MAP;
+AT_NONCACHEABLE_SECTION_ALIGN(STATIC edma_tcd_t edmaTcd[MICROPY_HW_I2S_NUM], 32);
+
+// called on processor reset
+void machine_i2s_init0() {
+ for (uint8_t i = 0; i < MICROPY_HW_I2S_NUM; i++) {
+ MP_STATE_PORT(machine_i2s_obj)[i] = NULL;
+ }
+}
+
+// called on soft reboot
+void machine_i2s_deinit_all(void) {
+ for (uint8_t i = 0; i < MICROPY_HW_I2S_NUM; i++) {
+ machine_i2s_obj_t *i2s_obj = MP_STATE_PORT(machine_i2s_obj)[i];
+ if (i2s_obj != NULL) {
+ machine_i2s_deinit(i2s_obj);
+ MP_STATE_PORT(machine_i2s_obj)[i] = NULL;
+ }
+ }
+}
+
+// Ring Buffer
+// Thread safe when used with these constraints:
+// - Single Producer, Single Consumer
+// - Sequential atomic operations
+// One byte of capacity is used to detect buffer empty/full
+
+STATIC void ringbuf_init(ring_buf_t *rbuf, uint8_t *buffer, size_t size) {
+ rbuf->buffer = buffer;
+ rbuf->size = size;
+ rbuf->head = 0;
+ rbuf->tail = 0;
+}
+
+STATIC bool ringbuf_push(ring_buf_t *rbuf, uint8_t data) {
+ size_t next_tail = (rbuf->tail + 1) % rbuf->size;
+
+ if (next_tail != rbuf->head) {
+ rbuf->buffer[rbuf->tail] = data;
+ rbuf->tail = next_tail;
+ return true;
+ }
+
+ // full
+ return false;
+}
+
+STATIC bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data) {
+ if (rbuf->head == rbuf->tail) {
+ // empty
+ return false;
+ }
+
+ *data = rbuf->buffer[rbuf->head];
+ rbuf->head = (rbuf->head + 1) % rbuf->size;
+ return true;
+}
+
+STATIC bool ringbuf_is_empty(ring_buf_t *rbuf) {
+ return rbuf->head == rbuf->tail;
+}
+
+STATIC bool ringbuf_is_full(ring_buf_t *rbuf) {
+ return ((rbuf->tail + 1) % rbuf->size) == rbuf->head;
+}
+
+STATIC size_t ringbuf_available_data(ring_buf_t *rbuf) {
+ return (rbuf->tail - rbuf->head + rbuf->size) % rbuf->size;
+}
+
+STATIC size_t ringbuf_available_space(ring_buf_t *rbuf) {
+ return rbuf->size - ringbuf_available_data(rbuf) - 1;
+}
+
+STATIC int8_t get_frame_mapping_index(int8_t bits, format_t format) {
+ if (format == MONO) {
+ if (bits == 16) {
+ return 0;
+ } else { // 32 bits
+ return 1;
+ }
+ } else { // STEREO
+ if (bits == 16) {
+ return 2;
+ } else { // 32 bits
+ return 3;
+ }
+ }
+}
+
+STATIC int8_t get_dma_bits(uint16_t mode, int8_t bits) {
+ if (mode == TX) {
+ if (bits == 16) {
+ return 16;
+ } else {
+ return 32;
+ }
+ return bits;
+ } else { // RX
+ // always read 32 bit words for I2S e.g. I2S MEMS microphones
+ return 32;
+ }
+}
+
+STATIC bool lookup_gpio(const machine_pin_obj_t *pin, i2s_pin_function_t fn, uint8_t hw_id, uint16_t *index) {
+ for (uint16_t i = 0; i < ARRAY_SIZE(i2s_gpio_map); i++) {
+ if ((pin->name == i2s_gpio_map[i].name) &&
+ (i2s_gpio_map[i].fn == fn) &&
+ (i2s_gpio_map[i].hw_id == hw_id)) {
+ *index = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+STATIC bool set_iomux(const machine_pin_obj_t *pin, i2s_pin_function_t fn, uint8_t hw_id) {
+ uint16_t mapping_index;
+ if (lookup_gpio(pin, fn, hw_id, &mapping_index)) {
+ iomux_table_t iom = i2s_gpio_map[mapping_index].iomux;
+ IOMUXC_SetPinMux(iom.muxRegister, iom.muxMode, iom.inputRegister, iom.inputDaisy, iom.configRegister, 1U);
+ IOMUXC_SetPinConfig(iom.muxRegister, iom.muxMode, iom.inputRegister, iom.inputDaisy, iom.configRegister,
+ pin_generate_config(PIN_PULL_DISABLED, PIN_MODE_OUT, 2, iom.configRegister));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+STATIC bool is_rate_supported(int32_t rate) {
+ for (uint16_t i = 0; i < ARRAY_SIZE(clock_config_map); i++) {
+ if (clock_config_map[i].rate == rate) {
+ return true;
+ }
+ }
+ return false;
+}
+
+STATIC const clock_audio_pll_config_t *get_pll_config(int32_t rate) {
+ for (uint16_t i = 0; i < ARRAY_SIZE(clock_config_map); i++) {
+ if (clock_config_map[i].rate == rate) {
+ return clock_config_map[i].pll_config;
+ }
+ }
+ return 0;
+}
+
+STATIC const uint32_t get_clock_pre_divider(int32_t rate) {
+ for (uint16_t i = 0; i < ARRAY_SIZE(clock_config_map); i++) {
+ if (clock_config_map[i].rate == rate) {
+ return clock_config_map[i].clock_pre_divider;
+ }
+ }
+ return 0;
+}
+
+STATIC const uint32_t get_clock_divider(int32_t rate) {
+ for (uint16_t i = 0; i < ARRAY_SIZE(clock_config_map); i++) {
+ if (clock_config_map[i].rate == rate) {
+ return clock_config_map[i].clock_divider;
+ }
+ }
+ return 0;
+}
+
+STATIC uint32_t fill_appbuf_from_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+
+ // copy audio samples from the ring buffer to the app buffer
+ // loop, copying samples until the app buffer is filled
+ // For uasyncio mode, the loop will make an early exit if the ring buffer becomes empty
+ // Example:
+ // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample).
+ // For every frame coming from the ring buffer (8 bytes), 2 bytes are "cherry picked" and
+ // copied to the supplied app buffer.
+ // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from the ring buffer.
+ // If a 8kB app buffer is supplied, 32kB of audio samples is read from the ring buffer.
+
+ uint32_t num_bytes_copied_to_appbuf = 0;
+ uint8_t *app_p = (uint8_t *)appbuf->buf;
+ uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
+ uint32_t num_bytes_needed_from_ringbuf = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
+ uint8_t discard_byte;
+ while (num_bytes_needed_from_ringbuf) {
+
+ uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
+
+ for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
+ int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
+ if (r_to_a_mapping != -1) {
+ if (self->io_mode == BLOCKING) {
+ // poll the ringbuf until a sample becomes available, copy into appbuf using the mapping transform
+ while (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
+ ;
+ }
+ num_bytes_copied_to_appbuf++;
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
+ // ring buffer is empty, exit
+ goto exit;
+ } else {
+ num_bytes_copied_to_appbuf++;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ } else { // r_a_mapping == -1
+ // discard unused byte from ring buffer
+ if (self->io_mode == BLOCKING) {
+ // poll the ringbuf until a sample becomes available
+ while (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
+ ;
+ }
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
+ // ring buffer is empty, exit
+ goto exit;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ }
+ num_bytes_needed_from_ringbuf--;
+ }
+ app_p += appbuf_sample_size_in_bytes;
+ }
+exit:
+ return num_bytes_copied_to_appbuf;
+}
+
+// function is used in IRQ context
+STATIC void fill_appbuf_from_ringbuf_non_blocking(machine_i2s_obj_t *self) {
+
+ // attempt to copy a block of audio samples from the ring buffer to the supplied app buffer.
+ // audio samples will be formatted as part of the copy operation
+
+ uint32_t num_bytes_copied_to_appbuf = 0;
+ uint8_t *app_p = &(((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index]);
+
+ uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
+ uint32_t num_bytes_remaining_to_copy_to_appbuf = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
+ uint32_t num_bytes_remaining_to_copy_from_ring_buffer = num_bytes_remaining_to_copy_to_appbuf *
+ (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
+ uint32_t num_bytes_needed_from_ringbuf = MIN(SIZEOF_NON_BLOCKING_COPY_IN_BYTES, num_bytes_remaining_to_copy_from_ring_buffer);
+ uint8_t discard_byte;
+ if (ringbuf_available_data(&self->ring_buffer) >= num_bytes_needed_from_ringbuf) {
+ while (num_bytes_needed_from_ringbuf) {
+
+ uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
+
+ for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
+ int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
+ if (r_to_a_mapping != -1) {
+ ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping);
+ num_bytes_copied_to_appbuf++;
+ } else { // r_a_mapping == -1
+ // discard unused byte from ring buffer
+ ringbuf_pop(&self->ring_buffer, &discard_byte);
+ }
+ num_bytes_needed_from_ringbuf--;
+ }
+ app_p += appbuf_sample_size_in_bytes;
+ }
+ self->non_blocking_descriptor.index += num_bytes_copied_to_appbuf;
+
+ if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
+ self->non_blocking_descriptor.copy_in_progress = false;
+ mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
+ }
+ }
+}
+
+STATIC uint32_t copy_appbuf_to_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
+
+ // copy audio samples from the app buffer to the ring buffer
+ // loop, reading samples until the app buffer is emptied
+ // for uasyncio mode, the loop will make an early exit if the ring buffer becomes full
+
+ uint32_t a_index = 0;
+
+ while (a_index < appbuf->len) {
+ if (self->io_mode == BLOCKING) {
+ // copy a byte to the ringbuf when space becomes available
+ while (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
+ ;
+ }
+ a_index++;
+ } else if (self->io_mode == UASYNCIO) {
+ if (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
+ // ring buffer is full, exit
+ break;
+ } else {
+ a_index++;
+ }
+ } else {
+ return 0; // should never get here (non-blocking mode does not use this function)
+ }
+ }
+
+ return a_index;
+}
+
+// function is used in IRQ context
+STATIC void copy_appbuf_to_ringbuf_non_blocking(machine_i2s_obj_t *self) {
+
+ // copy audio samples from app buffer into ring buffer
+ uint32_t num_bytes_remaining_to_copy = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
+ uint32_t num_bytes_to_copy = MIN(SIZEOF_NON_BLOCKING_COPY_IN_BYTES, num_bytes_remaining_to_copy);
+
+ if (ringbuf_available_space(&self->ring_buffer) >= num_bytes_to_copy) {
+ for (uint32_t i = 0; i < num_bytes_to_copy; i++) {
+ ringbuf_push(&self->ring_buffer,
+ ((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index + i]);
+ }
+
+ self->non_blocking_descriptor.index += num_bytes_to_copy;
+ if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
+ self->non_blocking_descriptor.copy_in_progress = false;
+ mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
+ }
+ }
+}
+
+// function is used in IRQ context
+STATIC void empty_dma(machine_i2s_obj_t *self, ping_pong_t dma_ping_pong) {
+ uint16_t dma_buffer_offset = 0;
+
+ if (dma_ping_pong == TOP_HALF) {
+ dma_buffer_offset = 0;
+ } else { // BOTTOM_HALF
+ dma_buffer_offset = SIZEOF_HALF_DMA_BUFFER_IN_BYTES;
+ }
+
+ uint8_t *dma_buffer_p = &self->dma_buffer_dcache_aligned[dma_buffer_offset];
+
+ // flush and invalidate cache so the CPU reads data placed into RAM by DMA
+ MP_HAL_CLEANINVALIDATE_DCACHE(dma_buffer_p, SIZEOF_HALF_DMA_BUFFER_IN_BYTES);
+
+ // when space exists, copy samples into ring buffer
+ if (ringbuf_available_space(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) {
+ ringbuf_push(&self->ring_buffer, dma_buffer_p[i]);
+ }
+ }
+}
+
+// function is used in IRQ context
+STATIC void feed_dma(machine_i2s_obj_t *self, ping_pong_t dma_ping_pong) {
+ uint16_t dma_buffer_offset = 0;
+
+ if (dma_ping_pong == TOP_HALF) {
+ dma_buffer_offset = 0;
+ } else { // BOTTOM_HALF
+ dma_buffer_offset = SIZEOF_HALF_DMA_BUFFER_IN_BYTES;
+ }
+
+ uint8_t *dma_buffer_p = &self->dma_buffer_dcache_aligned[dma_buffer_offset];
+
+ // when data exists, copy samples from ring buffer
+ if (ringbuf_available_data(&self->ring_buffer) >= SIZEOF_HALF_DMA_BUFFER_IN_BYTES) {
+
+ // copy a block of samples from the ring buffer to the dma buffer.
+ // mono format is implemented by duplicating each sample into both L and R channels.
+ if ((self->format == MONO) && (self->bits == 16)) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 4; i++) {
+ for (uint8_t b = 0; b < sizeof(uint16_t); b++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 4 + b]);
+ dma_buffer_p[i * 4 + b + 2] = dma_buffer_p[i * 4 + b]; // duplicated mono sample
+ }
+ }
+ } else if ((self->format == MONO) && (self->bits == 32)) {
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES / 8; i++) {
+ for (uint8_t b = 0; b < sizeof(uint32_t); b++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 8 + b]);
+ dma_buffer_p[i * 8 + b + 4] = dma_buffer_p[i * 8 + b]; // duplicated mono sample
+ }
+ }
+ } else { // STEREO, both 16-bit and 32-bit
+ for (uint32_t i = 0; i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES; i++) {
+ ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i]);
+ }
+ }
+ } else {
+ // underflow. clear buffer to transmit "silence" on the I2S bus
+ memset(dma_buffer_p, 0, SIZEOF_HALF_DMA_BUFFER_IN_BYTES);
+ }
+
+ // flush cache to RAM so DMA can read the sample data
+ MP_HAL_CLEAN_DCACHE(dma_buffer_p, SIZEOF_HALF_DMA_BUFFER_IN_BYTES);
+}
+
+STATIC void edma_i2s_callback(edma_handle_t *handle, void *userData, bool transferDone, uint32_t tcds) {
+ machine_i2s_obj_t *self = userData;
+
+ if (self->mode == TX) {
+ // for non-blocking mode, sample copying (appbuf->ibuf) is initiated in this callback routine
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ copy_appbuf_to_ringbuf_non_blocking(self);
+ }
+
+ if (transferDone) {
+ // bottom half of buffer now emptied,
+ // safe to fill the bottom half while the top half of buffer is being emptied
+ feed_dma(self, BOTTOM_HALF);
+ } else {
+ // top half of buffer now emptied,
+ // safe to fill the top half while the bottom half of buffer is being emptied
+ feed_dma(self, TOP_HALF);
+ }
+ } else { // RX
+ if (transferDone) {
+ // bottom half of buffer now filled,
+ // safe to empty the bottom half while the top half of buffer is being filled
+ empty_dma(self, BOTTOM_HALF);
+ } else {
+ // top half of buffer now filled,
+ // safe to empty the top half while the bottom half of buffer is being filled
+ empty_dma(self, TOP_HALF);
+ }
+
+ // for non-blocking mode, sample copying (ibuf->appbuf) is initiated in this callback routine
+ if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
+ fill_appbuf_from_ringbuf_non_blocking(self);
+ }
+ }
+}
+
+STATIC bool i2s_init(machine_i2s_obj_t *self) {
+
+ CLOCK_InitAudioPll(get_pll_config(self->rate));
+ CLOCK_SetMux(i2s_clock_mux[self->i2s_id], AUDIO_PLL_CLOCK);
+ CLOCK_SetDiv(i2s_clock_pre_div[self->i2s_id], get_clock_pre_divider(self->rate));
+ CLOCK_SetDiv(i2s_clock_div[self->i2s_id], get_clock_divider(self->rate));
+
+ if (!set_iomux(self->sck, SCK, self->i2s_id)) {
+ return false;
+ }
+
+ if (!set_iomux(self->ws, WS, self->i2s_id)) {
+ return false;
+ }
+
+ if (!set_iomux(self->sd, SD, self->i2s_id)) {
+ return false;
+ }
+
+ if (self->mck) {
+ if (!set_iomux(self->mck, MCK, self->i2s_id)) {
+ return false;
+ }
+ IOMUXC_EnableMode(IOMUXC_GPR, i2s_iomuxc_gpr_mode[self->i2s_id], true);
+ }
+
+ self->dma_channel = allocate_dma_channel();
+
+ DMAMUX_Init(DMAMUX);
+ if (self->mode == TX) {
+ DMAMUX_SetSource(DMAMUX, self->dma_channel, i2s_dma_req_src_tx[self->i2s_id]);
+ } else { // RX
+ DMAMUX_SetSource(DMAMUX, self->dma_channel, i2s_dma_req_src_rx[self->i2s_id]);
+ }
+ DMAMUX_EnableChannel(DMAMUX, self->dma_channel);
+
+ dma_init();
+ EDMA_CreateHandle(&self->edmaHandle, DMA0, self->dma_channel);
+ EDMA_SetCallback(&self->edmaHandle, edma_i2s_callback, self);
+ EDMA_ResetChannel(DMA0, self->dma_channel);
+
+ SAI_Init(self->i2s_inst);
+
+ sai_transceiver_t saiConfig;
+ SAI_GetClassicI2SConfig(&saiConfig, get_dma_bits(self->mode, self->bits), kSAI_Stereo, kSAI_Channel0Mask);
+ saiConfig.masterSlave = kSAI_Master;
+
+ uint16_t sck_index;
+ lookup_gpio(self->sck, SCK, self->i2s_id, &sck_index);
+
+ if ((self->mode == TX) && (i2s_gpio_map[sck_index].mode == TX)) {
+ saiConfig.syncMode = kSAI_ModeAsync;
+ SAI_TxSetConfig(self->i2s_inst, &saiConfig);
+ } else if ((self->mode == RX) && (i2s_gpio_map[sck_index].mode == RX)) {
+ saiConfig.syncMode = kSAI_ModeAsync;
+ SAI_RxSetConfig(self->i2s_inst, &saiConfig);
+ } else if ((self->mode == TX) && (i2s_gpio_map[sck_index].mode == RX)) {
+ saiConfig.syncMode = kSAI_ModeAsync;
+ SAI_RxSetConfig(self->i2s_inst, &saiConfig);
+ saiConfig.bitClock.bclkSrcSwap = true;
+ saiConfig.syncMode = kSAI_ModeSync;
+ SAI_TxSetConfig(self->i2s_inst, &saiConfig);
+ } else if ((self->mode == RX) && (i2s_gpio_map[sck_index].mode == TX)) {
+ saiConfig.syncMode = kSAI_ModeAsync;
+ SAI_TxSetConfig(self->i2s_inst, &saiConfig);
+ saiConfig.syncMode = kSAI_ModeSync;
+ SAI_RxSetConfig(self->i2s_inst, &saiConfig);
+ } else {
+ return false; // should never happen
+ }
+
+ uint32_t clock_freq =
+ (CLOCK_GetFreq(kCLOCK_AudioPllClk) / (get_clock_divider(self->rate) + 1U) /
+ (get_clock_pre_divider(self->rate) + 1U));
+
+ SAI_TxSetBitClockRate(self->i2s_inst, clock_freq, self->rate, get_dma_bits(self->mode, self->bits),
+ SAI_NUM_AUDIO_CHANNELS);
+ SAI_RxSetBitClockRate(self->i2s_inst, clock_freq, self->rate, get_dma_bits(self->mode, self->bits),
+ SAI_NUM_AUDIO_CHANNELS);
+
+ edma_transfer_config_t transferConfig;
+ uint8_t bytes_per_sample = get_dma_bits(self->mode, self->bits) / 8;
+
+ if (self->mode == TX) {
+ uint32_t destAddr = SAI_TxGetDataRegisterAddress(self->i2s_inst, SAI_CHANNEL_0);
+ EDMA_PrepareTransfer(&transferConfig,
+ self->dma_buffer_dcache_aligned, bytes_per_sample,
+ (void *)destAddr, bytes_per_sample,
+ (FSL_FEATURE_SAI_FIFO_COUNT - saiConfig.fifo.fifoWatermark) * bytes_per_sample,
+ SIZEOF_DMA_BUFFER_IN_BYTES, kEDMA_MemoryToPeripheral);
+ } else { // RX
+ uint32_t srcAddr = SAI_RxGetDataRegisterAddress(self->i2s_inst, SAI_CHANNEL_0);
+ EDMA_PrepareTransfer(&transferConfig,
+ (void *)srcAddr, bytes_per_sample,
+ self->dma_buffer_dcache_aligned, bytes_per_sample,
+ (FSL_FEATURE_SAI_FIFO_COUNT - saiConfig.fifo.fifoWatermark) * bytes_per_sample,
+ SIZEOF_DMA_BUFFER_IN_BYTES, kEDMA_PeripheralToMemory);
+ }
+
+ memset(self->edmaTcd, 0, sizeof(edma_tcd_t));
+
+ // continuous DMA operation is acheived using the scatter/gather feature, with one TCD linked back to itself
+ EDMA_TcdSetTransferConfig(self->edmaTcd, &transferConfig, self->edmaTcd);
+ EDMA_TcdEnableInterrupts(self->edmaTcd, kEDMA_MajorInterruptEnable | kEDMA_HalfInterruptEnable);
+ EDMA_InstallTCD(DMA0, self->dma_channel, self->edmaTcd);
+ EDMA_StartTransfer(&self->edmaHandle);
+
+ if (self->mode == TX) {
+ SAI_TxEnableDMA(self->i2s_inst, kSAI_FIFORequestDMAEnable, true);
+ SAI_TxEnable(self->i2s_inst, true);
+ SAI_TxSetChannelFIFOMask(self->i2s_inst, kSAI_Channel0Mask);
+ } else { // RX
+ SAI_RxEnableDMA(self->i2s_inst, kSAI_FIFORequestDMAEnable, true);
+ SAI_RxEnable(self->i2s_inst, true);
+ SAI_RxSetChannelFIFOMask(self->i2s_inst, kSAI_Channel0Mask);
+ }
+
+ return true;
+}
+
+STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+
+ enum {
+ ARG_sck,
+ ARG_ws,
+ ARG_sd,
+ ARG_mck,
+ ARG_mode,
+ ARG_bits,
+ ARG_format,
+ ARG_rate,
+ ARG_ibuf,
+ };
+
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_mck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_format, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_ibuf, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ //
+ // ---- Check validity of arguments ----
+ //
+
+ // is Mode valid?
+ uint16_t i2s_mode = args[ARG_mode].u_int;
+ if ((i2s_mode != (RX)) &&
+ (i2s_mode != (TX))) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid mode"));
+ }
+
+ // are I2S pin assignments valid?
+ uint16_t not_used;
+
+ // is SCK valid?
+ const machine_pin_obj_t *pin_sck = pin_find(args[ARG_sck].u_obj);
+ if (!lookup_gpio(pin_sck, SCK, self->i2s_id, &not_used)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid SCK pin"));
+ }
+
+ // is WS valid?
+ const machine_pin_obj_t *pin_ws = pin_find(args[ARG_ws].u_obj);
+ if (!lookup_gpio(pin_ws, WS, self->i2s_id, &not_used)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid WS pin"));
+ }
+
+ // is SD valid?
+ const machine_pin_obj_t *pin_sd = pin_find(args[ARG_sd].u_obj);
+ uint16_t mapping_index;
+ bool invalid_sd = true;
+ if (lookup_gpio(pin_sd, SD, self->i2s_id, &mapping_index)) {
+ if (i2s_mode == i2s_gpio_map[mapping_index].mode) {
+ invalid_sd = false;
+ }
+ }
+
+ if (invalid_sd) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid SD pin"));
+ }
+
+ // is MCK defined and valid?
+ const machine_pin_obj_t *pin_mck = NULL;
+ if (args[ARG_mck].u_obj != mp_const_none) {
+ pin_mck = pin_find(args[ARG_mck].u_obj);
+ if (!lookup_gpio(pin_mck, MCK, self->i2s_id, &not_used)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid MCK pin"));
+ }
+ }
+
+ // is Bits valid?
+ int8_t i2s_bits = args[ARG_bits].u_int;
+ if ((i2s_bits != 16) &&
+ (i2s_bits != 32)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ }
+
+ // is Format valid?
+ format_t i2s_format = args[ARG_format].u_int;
+ if ((i2s_format != MONO) &&
+ (i2s_format != STEREO)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid format"));
+ }
+
+ // is Rate valid?
+ int32_t i2s_rate = args[ARG_rate].u_int;
+ if (!is_rate_supported(i2s_rate)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid rate"));
+ }
+
+ // is Ibuf valid?
+ int32_t ring_buffer_len = args[ARG_ibuf].u_int;
+ if (ring_buffer_len > 0) {
+ uint8_t *buffer = m_new(uint8_t, ring_buffer_len);
+ self->ring_buffer_storage = buffer;
+ ringbuf_init(&self->ring_buffer, buffer, ring_buffer_len);
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid ibuf"));
+ }
+
+ self->sck = pin_sck;
+ self->ws = pin_ws;
+ self->sd = pin_sd;
+ self->mck = pin_mck;
+ self->mode = i2s_mode;
+ self->bits = i2s_bits;
+ self->format = i2s_format;
+ self->rate = i2s_rate;
+ self->ibuf = ring_buffer_len;
+ self->callback_for_non_blocking = MP_OBJ_NULL;
+ self->non_blocking_descriptor.copy_in_progress = false;
+ self->io_mode = BLOCKING;
+ self->i2s_inst = (I2S_Type *)i2s_base_ptr[self->i2s_id];
+
+ // init the I2S bus
+ if (!i2s_init(self)) {
+ mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("I2S init failed"));
+ }
+}
+
+STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_printf(print, "I2S(id=%u,\n"
+ "sck="MP_HAL_PIN_FMT ",\n"
+ "ws="MP_HAL_PIN_FMT ",\n"
+ "sd="MP_HAL_PIN_FMT ",\n"
+ "mck="MP_HAL_PIN_FMT ",\n"
+ "mode=%u,\n"
+ "bits=%u, format=%u,\n"
+ "rate=%d, ibuf=%d)",
+ self->i2s_id,
+ mp_hal_pin_name(self->sck),
+ mp_hal_pin_name(self->ws),
+ mp_hal_pin_name(self->sd),
+ mp_hal_pin_name(self->mck),
+ self->mode,
+ self->bits, self->format,
+ self->rate, self->ibuf
+ );
+}
+
+STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) {
+ mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true);
+ uint8_t i2s_id = mp_obj_get_int(args[0]);
+
+ if (i2s_id < 1 || i2s_id > MICROPY_HW_I2S_NUM) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2S(%d) does not exist"), i2s_id);
+ }
+
+ uint8_t i2s_id_zero_base = i2s_id - 1;
+
+ machine_i2s_obj_t *self;
+ if (MP_STATE_PORT(machine_i2s_obj)[i2s_id_zero_base] == NULL) {
+ self = m_new_obj(machine_i2s_obj_t);
+ MP_STATE_PORT(machine_i2s_obj)[i2s_id_zero_base] = self;
+ self->base.type = &machine_i2s_type;
+ self->i2s_id = i2s_id;
+ self->edmaTcd = &edmaTcd[i2s_id_zero_base];
+ } else {
+ self = MP_STATE_PORT(machine_i2s_obj)[i2s_id_zero_base];
+ machine_i2s_deinit(MP_OBJ_FROM_PTR(self));
+ }
+
+ // align DMA buffer to the cache line size (32 bytes)
+ self->dma_buffer_dcache_aligned = (uint8_t *)((uint32_t)(self->dma_buffer + 0x1f) & ~0x1f);
+
+ // fill the DMA buffer with NULLs
+ memset(self->dma_buffer_dcache_aligned, 0, SIZEOF_DMA_BUFFER_IN_BYTES);
+
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args);
+ machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args);
+ return MP_OBJ_FROM_PTR(self);
+}
+
+STATIC mp_obj_t machine_i2s_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
+ machine_i2s_deinit(MP_OBJ_FROM_PTR(self));
+ machine_i2s_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_init);
+
+STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ // use self->i2s_inst as in indication that I2S object has already been de-initialized
+ if (self->i2s_inst != NULL) {
+ EDMA_AbortTransfer(&self->edmaHandle);
+
+ if (self->mode == TX) {
+ SAI_TxSetChannelFIFOMask(self->i2s_inst, 0);
+ SAI_TxEnableDMA(self->i2s_inst, kSAI_FIFORequestDMAEnable, false);
+ SAI_TxEnable(self->i2s_inst, false);
+ SAI_TxReset(self->i2s_inst);
+ } else { // RX
+ SAI_RxSetChannelFIFOMask(self->i2s_inst, 0);
+ SAI_RxEnableDMA(self->i2s_inst, kSAI_FIFORequestDMAEnable, false);
+ SAI_RxEnable(self->i2s_inst, false);
+ SAI_RxReset(self->i2s_inst);
+ }
+
+ SAI_Deinit(self->i2s_inst);
+ free_dma_channel(self->dma_channel);
+ m_free(self->ring_buffer_storage);
+ self->i2s_inst = NULL; // flag object as de-initialized
+ }
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit);
+
+STATIC mp_obj_t machine_i2s_irq(mp_obj_t self_in, mp_obj_t handler) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ if (handler != mp_const_none && !mp_obj_is_callable(handler)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid callback"));
+ }
+
+ if (handler != mp_const_none) {
+ self->io_mode = NON_BLOCKING;
+ } else {
+ self->io_mode = BLOCKING;
+ }
+
+ self->callback_for_non_blocking = handler;
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_irq_obj, machine_i2s_irq);
+
+// Shift() is typically used as a volume control.
+// shift=1 increases volume by 6dB, shift=-1 decreases volume by 6dB
+STATIC mp_obj_t machine_i2s_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ enum { ARG_buf, ARG_bits, ARG_shift};
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_bits, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_shift, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
+ };
+
+ // parse args
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_RW);
+
+ int16_t *buf_16 = bufinfo.buf;
+ int32_t *buf_32 = bufinfo.buf;
+
+ uint8_t bits = args[ARG_bits].u_int;
+ int8_t shift = args[ARG_shift].u_int;
+
+ uint32_t num_audio_samples;
+ switch (bits) {
+ case 16:
+ num_audio_samples = bufinfo.len / sizeof(uint16_t);
+ break;
+
+ case 32:
+ num_audio_samples = bufinfo.len / sizeof(uint32_t);
+ break;
+
+ default:
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
+ break;
+ }
+
+ for (uint32_t i = 0; i < num_audio_samples; i++) {
+ switch (bits) {
+ case 16:
+ if (shift >= 0) {
+ buf_16[i] = buf_16[i] << shift;
+ } else {
+ buf_16[i] = buf_16[i] >> abs(shift);
+ }
+ break;
+ case 32:
+ if (shift >= 0) {
+ buf_32[i] = buf_32[i] << shift;
+ } else {
+ buf_32[i] = buf_32[i] >> abs(shift);
+ }
+ break;
+ }
+ }
+
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_shift_fun_obj, 0, machine_i2s_shift);
+STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(machine_i2s_shift_obj, MP_ROM_PTR(&machine_i2s_shift_fun_obj));
+
+STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) },
+ { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
+ { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2s_irq_obj) },
+
+ // Static method
+ { MP_ROM_QSTR(MP_QSTR_shift), MP_ROM_PTR(&machine_i2s_shift_obj) },
+
+ // Constants
+ { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_INT(RX) },
+ { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_INT(TX) },
+ { MP_ROM_QSTR(MP_QSTR_STEREO), MP_ROM_INT(STEREO) },
+ { MP_ROM_QSTR(MP_QSTR_MONO), MP_ROM_INT(MONO) },
+};
+MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table);
+
+STATIC mp_uint_t machine_i2s_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != RX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
+ if (size % appbuf_sample_size_in_bytes != 0) {
+ *errcode = MP_EINVAL;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ self->non_blocking_descriptor.appbuf.buf = (void *)buf_in;
+ self->non_blocking_descriptor.appbuf.len = size;
+ self->non_blocking_descriptor.index = 0;
+ self->non_blocking_descriptor.copy_in_progress = true;
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_read = fill_appbuf_from_ringbuf(self, &appbuf);
+ return num_bytes_read;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_stream_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (self->mode != TX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ if (self->io_mode == NON_BLOCKING) {
+ self->non_blocking_descriptor.appbuf.buf = (void *)buf_in;
+ self->non_blocking_descriptor.appbuf.len = size;
+ self->non_blocking_descriptor.index = 0;
+ self->non_blocking_descriptor.copy_in_progress = true;
+ return size;
+ } else { // blocking or uasyncio mode
+ mp_buffer_info_t appbuf;
+ appbuf.buf = (void *)buf_in;
+ appbuf.len = size;
+ uint32_t num_bytes_written = copy_appbuf_to_ringbuf(self, &appbuf);
+ return num_bytes_written;
+ }
+}
+
+STATIC mp_uint_t machine_i2s_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
+ machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_uint_t ret;
+ uintptr_t flags = arg;
+ self->io_mode = UASYNCIO; // a call to ioctl() is an indication that uasyncio is being used
+
+ if (request == MP_STREAM_POLL) {
+ ret = 0;
+
+ if (flags & MP_STREAM_POLL_RD) {
+ if (self->mode != RX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (!ringbuf_is_empty(&self->ring_buffer)) {
+ ret |= MP_STREAM_POLL_RD;
+ }
+ }
+
+ if (flags & MP_STREAM_POLL_WR) {
+ if (self->mode != TX) {
+ *errcode = MP_EPERM;
+ return MP_STREAM_ERROR;
+ }
+
+ if (!ringbuf_is_full(&self->ring_buffer)) {
+ ret |= MP_STREAM_POLL_WR;
+ }
+ }
+ } else {
+ *errcode = MP_EINVAL;
+ ret = MP_STREAM_ERROR;
+ }
+
+ return ret;
+}
+
+STATIC const mp_stream_p_t i2s_stream_p = {
+ .read = machine_i2s_stream_read,
+ .write = machine_i2s_stream_write,
+ .ioctl = machine_i2s_ioctl,
+ .is_text = false,
+};
+
+const mp_obj_type_t machine_i2s_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_I2S,
+ .print = machine_i2s_print,
+ .getiter = mp_identity_getiter,
+ .iternext = mp_stream_unbuffered_iter,
+ .protocol = &i2s_stream_p,
+ .make_new = machine_i2s_make_new,
+ .locals_dict = (mp_obj_dict_t *)&machine_i2s_locals_dict,
+};
+
+#endif // MICROPY_PY_MACHINE_I2S
diff --git a/ports/mimxrt/machine_spi.c b/ports/mimxrt/machine_spi.c
index e25e219c7..b8b5a4d5b 100644
--- a/ports/mimxrt/machine_spi.c
+++ b/ports/mimxrt/machine_spi.c
@@ -30,7 +30,7 @@
#include "py/mperrno.h"
#include "extmod/machine_spi.h"
#include "modmachine.h"
-#include "dma_channel.h"
+#include "dma_manager.h"
#include "fsl_cache.h"
#include "fsl_dmamux.h"
@@ -256,8 +256,6 @@ STATIC void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8
bool use_dma = chan_rx >= 0 && chan_tx >= 0;
if (use_dma) {
- edma_config_t userConfig;
-
/* DMA MUX init*/
DMAMUX_Init(DMAMUX);
@@ -267,8 +265,7 @@ STATIC void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8
DMAMUX_SetSource(DMAMUX, chan_tx, dma_req_src_tx[self->spi_hw_id]);
DMAMUX_EnableChannel(DMAMUX, chan_tx);
- EDMA_GetDefaultConfig(&userConfig);
- EDMA_Init(DMA0, &userConfig);
+ dma_init();
lpspi_master_edma_handle_t g_master_edma_handle;
edma_handle_t lpspiEdmaMasterRxRegToRxDataHandle;
diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c
index a6a0d2e19..82e07868a 100644
--- a/ports/mimxrt/main.c
+++ b/ports/mimxrt/main.c
@@ -113,6 +113,9 @@ int main(void) {
soft_reset_exit:
mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n");
machine_pin_irq_deinit();
+ #if MICROPY_PY_MACHINE_I2S
+ machine_i2s_deinit_all();
+ #endif
#if MICROPY_PY_NETWORK
mod_network_deinit();
#endif
diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c
index 63cec0550..d2358c506 100644
--- a/ports/mimxrt/modmachine.c
+++ b/ports/mimxrt/modmachine.c
@@ -131,6 +131,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) },
{ MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) },
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) },
+ #if MICROPY_PY_MACHINE_I2S
+ { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) },
+ #endif
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) },
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) },
{ MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) },
diff --git a/ports/mimxrt/modmachine.h b/ports/mimxrt/modmachine.h
index d18a22762..6502eea09 100644
--- a/ports/mimxrt/modmachine.h
+++ b/ports/mimxrt/modmachine.h
@@ -31,6 +31,7 @@
extern const mp_obj_type_t machine_adc_type;
extern const mp_obj_type_t machine_i2c_type;
+extern const mp_obj_type_t machine_i2s_type;
extern const mp_obj_type_t machine_pwm_type;
extern const mp_obj_type_t machine_rtc_type;
extern const mp_obj_type_t machine_sdcard_type;
@@ -45,5 +46,7 @@ void machine_pwm_deinit_all(void);
void machine_timer_init_PIT(void);
void machine_sdcard_init0(void);
void mimxrt_sdram_init(void);
+void machine_i2s_init0();
+void machine_i2s_deinit_all(void);
#endif // MICROPY_INCLUDED_MIMXRT_MODMACHINE_H
diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h
index b4df533f4..8572f6935 100644
--- a/ports/mimxrt/mpconfigport.h
+++ b/ports/mimxrt/mpconfigport.h
@@ -140,6 +140,9 @@ uint32_t trng_random_u32(void);
#define MICROPY_PY_MACHINE_PWM_DUTY_U16_NS (1)
#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/mimxrt/machine_pwm.c"
#define MICROPY_PY_MACHINE_I2C (1)
+#ifndef MICROPY_PY_MACHINE_I2S
+#define MICROPY_PY_MACHINE_I2S (0)
+#endif
#define MICROPY_PY_MACHINE_SOFTI2C (1)
#define MICROPY_PY_MACHINE_SPI (1)
#define MICROPY_PY_MACHINE_SOFTSPI (1)
@@ -277,6 +280,10 @@ extern const struct _mp_obj_type_t network_lan_type;
#define MICROPY_HW_PIT_NUM_CHANNELS 3
+#ifndef MICROPY_BOARD_ROOT_POINTERS
+#define MICROPY_BOARD_ROOT_POINTERS
+#endif
+
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[8]; \
struct _machine_timer_obj_t *timer_table[MICROPY_HW_PIT_NUM_CHANNELS]; \
@@ -285,6 +292,8 @@ extern const struct _mp_obj_type_t network_lan_type;
mp_obj_list_t mod_network_nic_list; \
/* root pointers for sub-systems */ \
MICROPY_PORT_ROOT_POINTER_MBEDTLS \
+ /* root pointers defined by a board */ \
+ MICROPY_BOARD_ROOT_POINTERS \
#define MP_STATE_PORT MP_STATE_VM
@@ -298,6 +307,10 @@ extern const struct _mp_obj_type_t network_lan_type;
#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1))
+#define MP_HAL_CLEANINVALIDATE_DCACHE(addr, size) \
+ (SCB_CleanInvalidateDCache_by_Addr((uint32_t *)((uint32_t)addr & ~0x1f), \
+ ((uint32_t)((uint8_t *)addr + size + 0x1f) & ~0x1f) - ((uint32_t)addr & ~0x1f)))
+
#define MP_HAL_CLEAN_DCACHE(addr, size) \
(SCB_CleanDCache_by_Addr((uint32_t *)((uint32_t)addr & ~0x1f), \
((uint32_t)((uint8_t *)addr + size + 0x1f) & ~0x1f) - ((uint32_t)addr & ~0x1f)))
diff --git a/ports/mimxrt/sdcard.c b/ports/mimxrt/sdcard.c
index 1a9a97b68..31853b400 100644
--- a/ports/mimxrt/sdcard.c
+++ b/ports/mimxrt/sdcard.c
@@ -822,7 +822,7 @@ bool sdcard_write(mimxrt_sdcard_obj_t *card, uint8_t *buffer, uint32_t block_num
.command = &command,
};
- status_t status = sdcard_transfer_blocking(card->usdhc_inst, &card->handle, &transfer, 500);
+ status_t status = sdcard_transfer_blocking(card->usdhc_inst, &card->handle, &transfer, 3000);
if (status == kStatus_Success) {
card->status = command.response[0];