From 1f6cb8f0476e699c1910ed59f1bb027fbef72ad7 Mon Sep 17 00:00:00 2001 From: MikeTeachman Date: Mon, 29 Nov 2021 09:50:34 -0800 Subject: [PATCH] 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 --- docs/library/machine.I2S.rst | 12 +- ports/mimxrt/Makefile | 9 +- ports/mimxrt/board_init.c | 4 + .../boards/MIMXRT1010_EVK/mpconfigboard.h | 33 + ports/mimxrt/boards/MIMXRT1010_EVK/pins.csv | 7 + .../boards/MIMXRT1020_EVK/mpconfigboard.h | 33 + ports/mimxrt/boards/MIMXRT1020_EVK/pins.csv | 9 +- .../boards/MIMXRT1050_EVK/mpconfigboard.h | 34 + ports/mimxrt/boards/MIMXRT1050_EVK/pins.csv | 7 + .../boards/MIMXRT1060_EVK/mpconfigboard.h | 33 + ports/mimxrt/boards/MIMXRT1060_EVK/pins.csv | 7 + .../boards/MIMXRT1064_EVK/mpconfigboard.h | 33 + ports/mimxrt/boards/MIMXRT1064_EVK/pins.csv | 7 + .../boards/OLIMEX_RT1010/mpconfigboard.h | 36 + ports/mimxrt/boards/OLIMEX_RT1010/pins.csv | 7 + .../boards/SEEED_ARCH_MIX/mpconfigboard.h | 32 + ports/mimxrt/boards/SEEED_ARCH_MIX/pins.csv | 7 + ports/mimxrt/boards/TEENSY40/mpconfigboard.h | 38 + ports/mimxrt/boards/TEENSY40/pins.csv | 9 +- ports/mimxrt/boards/TEENSY41/mpconfigboard.h | 40 + ports/mimxrt/boards/TEENSY41/pins.csv | 9 +- ports/mimxrt/{dma_channel.c => dma_manager.c} | 16 +- ports/mimxrt/{dma_channel.h => dma_manager.h} | 1 + ports/mimxrt/machine_i2s.c | 1228 +++++++++++++++++ ports/mimxrt/machine_spi.c | 7 +- ports/mimxrt/main.c | 3 + ports/mimxrt/modmachine.c | 3 + ports/mimxrt/modmachine.h | 3 + ports/mimxrt/mpconfigport.h | 13 + ports/mimxrt/sdcard.c | 2 +- 30 files changed, 1662 insertions(+), 20 deletions(-) rename ports/mimxrt/{dma_channel.c => dma_manager.c} (86%) rename ports/mimxrt/{dma_channel.h => dma_manager.h} (98%) create mode 100644 ports/mimxrt/machine_i2s.c diff --git a/docs/library/machine.I2S.rst b/docs/library/machine.I2S.rst index d4e5ac9433..e68a863d7c 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 be5083965f..f9c5bf402a 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 d96645feff..82af620a8d 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 726a63904d..75fda0cad1 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 1f01215ddd..d8710f8ce5 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 2e7ee34e8d..1dfe02b651 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 8420bbd821..e1f2669b08 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 c2783a3e59..613095e67f 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 463079b129..4bdd9e4f03 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 475d07ddb1..a3f0062389 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 463079b129..4bdd9e4f03 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 874991ac65..fe1fb532b8 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 463079b129..4bdd9e4f03 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 e004050aee..c30caa0470 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 d294f86b6d..d3c68aba2d 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 53a3d4ed78..a6502d3353 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 0183795cd7..6b757fae4b 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 08a774d886..f2ea86bd0e 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 0ea4f13731..b4509e5848 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 3b8d497c3f..7d75f62f36 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 88b91f2540..b28a22312c 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 similarity index 86% rename from ports/mimxrt/dma_channel.c rename to ports/mimxrt/dma_manager.c index c6cae9da9c..2890a15586 100644 --- a/ports/mimxrt/dma_channel.c +++ b/ports/mimxrt/dma_manager.c @@ -24,7 +24,10 @@ * THE SOFTWARE. */ -#include "dma_channel.h" +#include +#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 similarity index 98% rename from ports/mimxrt/dma_channel.h rename to ports/mimxrt/dma_manager.h index 3fe66a3dbb..77e9689b22 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 0000000000..a41dfe94f1 --- /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 +#include +#include +#include +#include + +#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, ¬_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, ¬_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, ¬_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 e25e219c79..b8b5a4d5b5 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 a6a0d2e196..82e07868a1 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 63cec05507..d2358c5069 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 d18a227624..6502eea09f 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 b4df533f4a..8572f69354 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 1a9a97b681..31853b400e 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];