diff --git a/arch/arm/src/stm32l4/Kconfig b/arch/arm/src/stm32l4/Kconfig index a518c5cede..eacdd4ed74 100644 --- a/arch/arm/src/stm32l4/Kconfig +++ b/arch/arm/src/stm32l4/Kconfig @@ -49,6 +49,8 @@ config STM32L4_STM32L476XX select STM32L4_HAVE_USART3 select STM32L4_HAVE_UART4 select STM32L4_HAVE_UART5 + select STM32L4_HAVE_SAI1 + select STM32L4_HAVE_SAI2 config STM32L4_STM32L486XX bool @@ -62,6 +64,8 @@ config STM32L4_STM32L486XX select STM32L4_HAVE_USART3 select STM32L4_HAVE_UART4 select STM32L4_HAVE_UART5 + select STM32L4_HAVE_SAI1 + select STM32L4_HAVE_SAI2 select STM32L4_FLASH_1024KB choice @@ -118,6 +122,14 @@ config STM32L4_HAVE_LTDC bool default n +config STM32L4_HAVE_SAI1 + bool + default n + +config STM32L4_HAVE_SAI2 + bool + default n + # These "hidden" settings are the OR of individual peripheral selections # indicating that the general capability is required. @@ -212,6 +224,38 @@ config STM32L4_RNG default n select ARCH_HAVE_RNG +config STM32L4_SAI1_A + bool "SAI1 Block A" + default n + select AUDIO + select I2S + select SCHED_WORKQUEUE + select STM32L4_SAI + +config STM32L4_SAI1_B + bool "SAI1 Block B" + default n + select AUDIO + select I2S + select SCHED_WORKQUEUE + select STM32L4_SAI + +config STM32L4_SAI2_A + bool "SAI2 Block A" + default n + select AUDIO + select I2S + select SCHED_WORKQUEUE + select STM32L4_SAI + +config STM32L4_SAI2_B + bool "SAI2 Block B" + default n + select AUDIO + select I2S + select SCHED_WORKQUEUE + select STM32L4_SAI + comment "AHB3 Peripherals" config STM32L4_FMC @@ -556,12 +600,10 @@ config STM32L4_TIM17 config STM32L4_SAI1 bool "SAI1" default n - select STM32L4_SAI config STM32L4_SAI2 bool "SAI2" default n - select STM32L4_SAI config STM32L4_DFSDM bool "DFSDM" @@ -2996,4 +3038,68 @@ endchoice endmenu +menu "SAI Configuration" + depends on STM32L4_SAI + +choice + prompt "Operation mode" + default STM32L4_SAI_DMA + ---help--- + Select the operation mode the SAI driver should use. + +config STM32L4_SAI_POLLING + bool "Polling" + ---help--- + The SAI registers are polled for events. + +config STM32L4_SAI_INTERRUPTS + bool "Interrupt" + ---help--- + Select to enable interrupt driven SAI support. + +config STM32L4_SAI_DMA + bool "DMA" + ---help--- + Use DMA to improve SAI transfer performance. + +endchoice # Operation mode + +choice + prompt "SAI1 synchronization enable" + default STM32L4_SAI1_BOTH_ASYNC + depends on STM32L4_SAI1_A && STM32L4_SAI1_B + ---help--- + Select the synchronization mode of the SAI sub-blocks + +config STM32L4_SAI1_BOTH_ASYNC + bool "Both asynchronous" + +config STM32L4_SAI1_A_SYNC_WITH_B + bool "Block A is synchronous with Block B" + +config STM32L4_SAI1_B_SYNC_WITH_A + bool "Block B is synchronous with Block A" + +endchoice # SAI1 synchronization enable + +choice + prompt "SAI2 synchronization enable" + default STM32L4_SAI2_BOTH_ASYNC + depends on STM32L4_SAI2_A && STM32L4_SAI2_B + ---help--- + Select the synchronization mode of the SAI sub-blocks + +config STM32L4_SAI2_BOTH_ASYNC + bool "Both asynchronous" + +config STM32L4_SAI2_A_SYNC_WITH_B + bool "Block A is synchronous with Block B" + +config STM32L4_SAI2_B_SYNC_WITH_A + bool "Block B is synchronous with Block A" + +endchoice # SAI2 synchronization enable + +endmenu + endif # ARCH_CHIP_STM32L4 diff --git a/arch/arm/src/stm32l4/Make.defs b/arch/arm/src/stm32l4/Make.defs index 15b50d8692..fb2373dc61 100644 --- a/arch/arm/src/stm32l4/Make.defs +++ b/arch/arm/src/stm32l4/Make.defs @@ -1,6 +1,7 @@ ############################################################################ # arch/arm/src/stm32l4/Make.defs # +# Copyright (C) 2017 Gregory Nutt. All rights reserved. # Copyright (C) 2015-2016 Sebastien Lorquet. All rights reserved. # Author: Sebastien Lorquet # @@ -181,6 +182,10 @@ ifeq ($(CONFIG_STM32L4_RNG),y) CHIP_CSRCS += stm32l4_rng.c endif +ifeq ($(CONFIG_STM32L4_SAI),y) +CHIP_CSRCS += stm32l4_sai.c +endif + ifeq ($(CONFIG_PWM),y) CHIP_CSRCS += stm32l4_pwm.c endif diff --git a/arch/arm/src/stm32l4/chip/stm32l4_sai.h b/arch/arm/src/stm32l4/chip/stm32l4_sai.h index a66c953afd..a7ab977a0f 100644 --- a/arch/arm/src/stm32l4/chip/stm32l4_sai.h +++ b/arch/arm/src/stm32l4/chip/stm32l4_sai.h @@ -51,65 +51,65 @@ #define STM32L4_SAI_GCR_OFFSET 0x0000 /* SAI Global Configuration Register */ -#define STM32L4_SAI_ACR1_OFFSET 0x0004 /* SAI Configuration Register 1 A */ -#define STM32L4_SAI_ACR2_OFFSET 0x0008 /* SAI Configuration Register 2 A */ -#define STM32L4_SAI_AFRCR_OFFSET 0x000c /* SAI Frame Configuration Register A */ -#define STM32L4_SAI_ASLOTR_OFFSET 0x0010 /* SAI Slot Register A */ -#define STM32L4_SAI_AIM_OFFSET 0x0014 /* SAI Interrupt Mask Register 2 A */ -#define STM32L4_SAI_ASR_OFFSET 0x0018 /* SAI Status Register A */ -#define STM32L4_SAI_ACLRFR_OFFSET 0x001c /* SAI Clear Flag Register A */ -#define STM32L4_SAI_ADR_OFFSET 0x0020 /* SAI Data Register A */ +#define STM32L4_SAI_A_OFFSET 0x0004 +#define STM32L4_SAI_B_OFFSET 0x0024 -#define STM32L4_SAI_BCR1_OFFSET 0x0024 /* SAI Configuration Register 1 B */ -#define STM32L4_SAI_BCR2_OFFSET 0x0028 /* SAI Configuration Register 2 B */ -#define STM32L4_SAI_BFRCR_OFFSET 0x002c /* SAI Frame Configuration Register B */ -#define STM32L4_SAI_BSLOTR_OFFSET 0x0030 /* SAI Slot Register B */ -#define STM32L4_SAI_BIM_OFFSET 0x0034 /* SAI Interrupt Mask Register 2 B */ -#define STM32L4_SAI_BSR_OFFSET 0x0038 /* SAI Status Register B */ -#define STM32L4_SAI_BCLRFR_OFFSET 0x003c /* SAI Clear Flag Register A */ -#define STM32L4_SAI_BDR_OFFSET 0x0040 /* SAI Data Register B */ +#define STM32L4_SAI_CR1_OFFSET 0x0000 /* SAI Configuration Register 1 A */ +#define STM32L4_SAI_CR2_OFFSET 0x0004 /* SAI Configuration Register 2 A */ +#define STM32L4_SAI_FRCR_OFFSET 0x0008 /* SAI Frame Configuration Register A */ +#define STM32L4_SAI_SLOTR_OFFSET 0x000c /* SAI Slot Register A */ +#define STM32L4_SAI_IM_OFFSET 0x0010 /* SAI Interrupt Mask Register 2 A */ +#define STM32L4_SAI_SR_OFFSET 0x0014 /* SAI Status Register A */ +#define STM32L4_SAI_CLRFR_OFFSET 0x0018 /* SAI Clear Flag Register A */ +#define STM32L4_SAI_DR_OFFSET 0x001c /* SAI Data Register A */ /* Register Addresses ***************************************************************/ -#define STM32L4_SAI1_GCR (STM32L4_SAI1_BASE+STM32L4_SAI_GCR_OFFSET) +#define STM32L4_SAI1_GCR (STM32L4_SAI_GCR_OFFSET) -#define STM32L4_SAI1_ACR1 (STM32L4_SAI1_BASE+STM32L4_SAI_ACR1_OFFSET) -#define STM32L4_SAI1_ACR2 (STM32L4_SAI1_BASE+STM32L4_SAI_ACR2_OFFSET) -#define STM32L4_SAI1_AFRCR (STM32L4_SAI1_BASE+STM32L4_SAI_AFRCR_OFFSET) -#define STM32L4_SAI1_ASLOTR (STM32L4_SAI1_BASE+STM32L4_SAI_ASLOTR_OFFSET) -#define STM32L4_SAI1_AIM (STM32L4_SAI1_BASE+STM32L4_SAI_AIM_OFFSET) -#define STM32L4_SAI1_ASR (STM32L4_SAI1_BASE+STM32L4_SAI_ASR_OFFSET) -#define STM32L4_SAI1_ACLRFR (STM32L4_SAI1_BASE+STM32L4_SAI_ACLRFR_OFFSET) -#define STM32L4_SAI1_ADR (STM32L4_SAI1_BASE+STM32L4_SAI_ADR_OFFSET) +#define STM32L4_SAI1_A_BASE (STM32L4_SAI1_BASE+STM32L4_SAI_A_OFFSET) +#define STM32L4_SAI1_B_BASE (STM32L4_SAI1_BASE+STM32L4_SAI_B_OFFSET) -#define STM32L4_SAI1_BCR1 (STM32L4_SAI1_BASE+STM32L4_SAI_BCR1_OFFSET) -#define STM32L4_SAI1_BCR2 (STM32L4_SAI1_BASE+STM32L4_SAI_BCR2_OFFSET) -#define STM32L4_SAI1_BFRCR (STM32L4_SAI1_BASE+STM32L4_SAI_BFRCR_OFFSET) -#define STM32L4_SAI1_BSLOTR (STM32L4_SAI1_BASE+STM32L4_SAI_BSLOTR_OFFSET) -#define STM32L4_SAI1_BIM (STM32L4_SAI1_BASE+STM32L4_SAI_BIM_OFFSET) -#define STM32L4_SAI1_BSR (STM32L4_SAI1_BASE+STM32L4_SAI_BSR_OFFSET) -#define STM32L4_SAI1_BCLRFR (STM32L4_SAI1_BASE+STM32L4_SAI_BCLRFR_OFFSET) -#define STM32L4_SAI1_BDR (STM32L4_SAI1_BASE+STM32L4_SAI_BDR_OFFSET) +#define STM32L4_SAI1_ACR1 (STM32L4_SAI1_A_BASE+STM32L4_SAI_ACR1_OFFSET) +#define STM32L4_SAI1_ACR2 (STM32L4_SAI1_A_BASE+STM32L4_SAI_ACR2_OFFSET) +#define STM32L4_SAI1_AFRCR (STM32L4_SAI1_A_BASE+STM32L4_SAI_AFRCR_OFFSET) +#define STM32L4_SAI1_ASLOTR (STM32L4_SAI1_A_BASE+STM32L4_SAI_ASLOTR_OFFSET) +#define STM32L4_SAI1_AIM (STM32L4_SAI1_A_BASE+STM32L4_SAI_AIM_OFFSET) +#define STM32L4_SAI1_ASR (STM32L4_SAI1_A_BASE+STM32L4_SAI_ASR_OFFSET) +#define STM32L4_SAI1_ACLRFR (STM32L4_SAI1_A_BASE+STM32L4_SAI_ACLRFR_OFFSET) +#define STM32L4_SAI1_ADR (STM32L4_SAI1_A_BASE+STM32L4_SAI_ADR_OFFSET) + +#define STM32L4_SAI1_BCR1 (STM32L4_SAI1_B_BASE+STM32L4_SAI_BCR1_OFFSET) +#define STM32L4_SAI1_BCR2 (STM32L4_SAI1_B_BASE+STM32L4_SAI_BCR2_OFFSET) +#define STM32L4_SAI1_BFRCR (STM32L4_SAI1_B_BASE+STM32L4_SAI_BFRCR_OFFSET) +#define STM32L4_SAI1_BSLOTR (STM32L4_SAI1_B_BASE+STM32L4_SAI_BSLOTR_OFFSET) +#define STM32L4_SAI1_BIM (STM32L4_SAI1_B_BASE+STM32L4_SAI_BIM_OFFSET) +#define STM32L4_SAI1_BSR (STM32L4_SAI1_B_BASE+STM32L4_SAI_BSR_OFFSET) +#define STM32L4_SAI1_BCLRFR (STM32L4_SAI1_B_BASE+STM32L4_SAI_BCLRFR_OFFSET) +#define STM32L4_SAI1_BDR (STM32L4_SAI1_B_BASE+STM32L4_SAI_BDR_OFFSET) #define STM32L4_SAI2_GCR (STM32L4_SAI2_BASE+STM32L4_SAI_GCR_OFFSET) -#define STM32L4_SAI2_ACR1 (STM32L4_SAI2_BASE+STM32L4_SAI_ACR1_OFFSET) -#define STM32L4_SAI2_ACR2 (STM32L4_SAI2_BASE+STM32L4_SAI_ACR2_OFFSET) -#define STM32L4_SAI2_AFRCR (STM32L4_SAI2_BASE+STM32L4_SAI_AFRCR_OFFSET) -#define STM32L4_SAI2_ASLOTR (STM32L4_SAI2_BASE+STM32L4_SAI_ASLOTR_OFFSET) -#define STM32L4_SAI2_AIM (STM32L4_SAI2_BASE+STM32L4_SAI_AIM_OFFSET) -#define STM32L4_SAI2_ASR (STM32L4_SAI2_BASE+STM32L4_SAI_ASR_OFFSET) -#define STM32L4_SAI2_ACLRFR (STM32L4_SAI2_BASE+STM32L4_SAI_ACLRFR_OFFSET) -#define STM32L4_SAI2_ADR (STM32L4_SAI2_BASE+STM32L4_SAI_ADR_OFFSET) +#define STM32L4_SAI2_A_BASE (STM32L4_SAI2_BASE+STM32L4_SAI_A_OFFSET) +#define STM32L4_SAI2_B_BASE (STM32L4_SAI2_BASE+STM32L4_SAI_B_OFFSET) -#define STM32L4_SAI2_BCR1 (STM32L4_SAI2_BASE+STM32L4_SAI_BCR1_OFFSET) -#define STM32L4_SAI2_BCR2 (STM32L4_SAI2_BASE+STM32L4_SAI_BCR2_OFFSET) -#define STM32L4_SAI2_BFRCR (STM32L4_SAI2_BASE+STM32L4_SAI_BFRCR_OFFSET) -#define STM32L4_SAI2_BSLOTR (STM32L4_SAI2_BASE+STM32L4_SAI_BSLOTR_OFFSET) -#define STM32L4_SAI2_BIM (STM32L4_SAI2_BASE+STM32L4_SAI_BIM_OFFSET) -#define STM32L4_SAI2_BSR (STM32L4_SAI2_BASE+STM32L4_SAI_BSR_OFFSET) -#define STM32L4_SAI2_BCLRFR (STM32L4_SAI2_BASE+STM32L4_SAI_BCLRFR_OFFSET) -#define STM32L4_SAI2_BDR (STM32L4_SAI2_BASE+STM32L4_SAI_BDR_OFFSET) +#define STM32L4_SAI2_ACR1 (STM32L4_SAI2_A_BASE+STM32L4_SAI_ACR1_OFFSET) +#define STM32L4_SAI2_ACR2 (STM32L4_SAI2_A_BASE+STM32L4_SAI_ACR2_OFFSET) +#define STM32L4_SAI2_AFRCR (STM32L4_SAI2_A_BASE+STM32L4_SAI_AFRCR_OFFSET) +#define STM32L4_SAI2_ASLOTR (STM32L4_SAI2_A_BASE+STM32L4_SAI_ASLOTR_OFFSET) +#define STM32L4_SAI2_AIM (STM32L4_SAI2_A_BASE+STM32L4_SAI_AIM_OFFSET) +#define STM32L4_SAI2_ASR (STM32L4_SAI2_A_BASE+STM32L4_SAI_ASR_OFFSET) +#define STM32L4_SAI2_ACLRFR (STM32L4_SAI2_A_BASE+STM32L4_SAI_ACLRFR_OFFSET) +#define STM32L4_SAI2_ADR (STM32L4_SAI2_A_BASE+STM32L4_SAI_ADR_OFFSET) + +#define STM32L4_SAI2_BCR1 (STM32L4_SAI2_B_BASE+STM32L4_SAI_BCR1_OFFSET) +#define STM32L4_SAI2_BCR2 (STM32L4_SAI2_B_BASE+STM32L4_SAI_BCR2_OFFSET) +#define STM32L4_SAI2_BFRCR (STM32L4_SAI2_B_BASE+STM32L4_SAI_BFRCR_OFFSET) +#define STM32L4_SAI2_BSLOTR (STM32L4_SAI2_B_BASE+STM32L4_SAI_BSLOTR_OFFSET) +#define STM32L4_SAI2_BIM (STM32L4_SAI2_B_BASE+STM32L4_SAI_BIM_OFFSET) +#define STM32L4_SAI2_BSR (STM32L4_SAI2_B_BASE+STM32L4_SAI_BSR_OFFSET) +#define STM32L4_SAI2_BCLRFR (STM32L4_SAI2_B_BASE+STM32L4_SAI_BCLRFR_OFFSET) +#define STM32L4_SAI2_BDR (STM32L4_SAI2_B_BASE+STM32L4_SAI_BDR_OFFSET) /* Register Bitfield Definitions ****************************************************/ @@ -128,10 +128,10 @@ #define SAI_CR1_MODE_SHIFT (0) /* Bits 0-1: SAI audio block mode */ #define SAI_CR1_MODE_MASK (3 << SAI_CR1_MODE_SHIFT) -# define SAI_CR1_MODE_MXMIT (0 << SAI_CR1_MODE_SHIFT) /* Master transmitter */ -# define SAI_CR1_MODE_MRECV (1 << SAI_CR1_MODE_SHIFT) /* Master receiver */ -# define SAI_CR1_MODE_SXMIT (2 << SAI_CR1_MODE_SHIFT) /* Slave transmitter */ -# define SAI_CR1_MODE_SRECV (3 << SAI_CR1_MODE_SHIFT) /* Slave receiver */ +# define SAI_CR1_MODE_MASTER_TX (0 << SAI_CR1_MODE_SHIFT) /* Master transmitter */ +# define SAI_CR1_MODE_MASTER_RX (1 << SAI_CR1_MODE_SHIFT) /* Master receiver */ +# define SAI_CR1_MODE_SLAVE_TX (2 << SAI_CR1_MODE_SHIFT) /* Slave transmitter */ +# define SAI_CR1_MODE_SLAVE_RX (3 << SAI_CR1_MODE_SHIFT) /* Slave receiver */ #define SAI_CR1_PRTCFG_SHIFT (2) /* Bits 2-3: Protocol configuration */ #define SAI_CR1_PRTCFG_MASK (3 << SAI_CR1_PRTCFG_SHIFT) # define SAI_CR1_PRTCFG_FREE (0 << SAI_CR1_PRTCFG_SHIFT) /* Free protocol */ @@ -167,12 +167,12 @@ /* SAI Configuration Register 2 */ -#define SAI_CR2_FTH_SHIFT (x0x) /* Bits 0-2: FIFO threshold */ +#define SAI_CR2_FTH_SHIFT (0) /* Bits 0-2: FIFO threshold */ #define SAI_CR2_FTH_MASK (7 << SAI_CR2_FTH_SHIFT) # define SAI_CR2_FTH_EMPTY (0 << SAI_CR2_FTH_SHIFT) /* FIFO empty */ -# define SAI_CR2_FTH_25PCT (1 << SAI_CR2_FTH_SHIFT) /* 25% FIFO */ -# define SAI_CR2_FTH_50PCT (2 << SAI_CR2_FTH_SHIFT) /* 50% FIFO */ -# define SAI_CR2_FTH_75PCT (3 << SAI_CR2_FTH_SHIFT) /* 75% FIFO */ +# define SAI_CR2_FTH_1QF (1 << SAI_CR2_FTH_SHIFT) /* 1/4 FIFO */ +# define SAI_CR2_FTH_HF (2 << SAI_CR2_FTH_SHIFT) /* 1/2 FIFO */ +# define SAI_CR2_FTH_3QF (3 << SAI_CR2_FTH_SHIFT) /* 3/4 FIFO */ # define SAI_CR2_FTH_FULL (4 << SAI_CR2_FTH_SHIFT) /* FIFO full */ #define SAI_CR2_FFLUSH (1 << 3) /* Bit 3: FIFO flush */ #define SAI_CR2_TRIS (1 << 4) /* Bit 4: Tristate management on data line */ @@ -193,13 +193,19 @@ #define SAI_FRCR_FRL_SHIFT (0) /* Bits 0-7: Frame length */ #define SAI_FRCR_FRL_MASK (0xff << SAI_FRCR_FRL_SHIFT) -# define SAI_FRCR_FRL(n) ((uint32_t)(n) << SAI_FRCR_FRL_SHIFT) +# define SAI_FRCR_FRL(n) ((uint32_t)((n) - 1) << SAI_FRCR_FRL_SHIFT) #define SAI_FRCR_FSALL_SHIFT (8) /* Bits 8-14: Frame synchronization active level length */ #define SAI_FRCR_FSALL_MASK (0x7f << SAI_FRCR_FSALL_SHIFT) -# define SAI_FRCR_FSALL(n) ((uint32_t)(n) << SAI_FRCR_FSALL_SHIFT) +# define SAI_FRCR_FSALL(n) ((uint32_t)((n) - 1) << SAI_FRCR_FSALL_SHIFT) #define SAI_FRCR_FSDEF (1 << 16) /* Bit 16: Frame synchronization definition */ +# define SAI_FRCR_FSDEF_SF (0) /* FS signal is a start frame signal */ +# define SAI_FRCR_FSDEF_CHID SAI_FRCR_FSDEF /* FS signal is a start of frame + channel side ID */ #define SAI_FRCR_FSPOL (1 << 17) /* Bit 17: Frame synchronization polarity */ +# define SAI_FRCR_FSPOL_LOW (0) /* FS is active low */ +# define SAI_FRCR_FSPOL_HIGH SAI_FRCR_FSPOL /* FS is active high */ #define SAI_FRCR_FSOFF (1 << 18) /* Bit 18: Frame synchronization offset */ +# define SAI_FRCR_FSOFF_FB (0) /* FS on first bit of slot 0 */ +# define SAI_FRCR_FSOFF_BFB SAI_FRCR_FSOFF /* FS one bit before first bit of slot 0 */ /* Bits 19-31: Reserved */ /* SAI Slot Register */ @@ -215,11 +221,27 @@ # define SAI_SLOTR_SLOTSZ_32BIT (2 << SAI_SLOTR_SLOTSZ_SHIFT) /* 32-bit */ #define SAI_SLOTR_NBSLOT_SHIFT (0) /* Bits 0-3: Number of slots in an audio frame */ #define SAI_SLOTR_NBSLOT_MASK (15 << SAI_SLOTR_NBSLOT_SHIFT) -# define SAI_SLOTR_NBSLOT(n) ((uint32_t)(n) << SAI_SLOTR_NBSLOT_SHIFT) +# define SAI_SLOTR_NBSLOT(n) ((uint32_t)((n) - 1) << SAI_SLOTR_NBSLOT_SHIFT) /* Bits 12-15: Reserved */ #define SAI_SLOTR_SLOTEN_SHIFT (16) /* Bits 16-31: Slot enable */ #define SAI_SLOTR_SLOTEN_MASK (0xffff << SAI_SLOTR_SLOTEN_SHIFT) # define SAI_SLOTR_SLOTEN(n) ((uint32_t)(n) << SAI_SLOTR_SLOTEN_SHIFT) +# define SAI_SLOTR_SLOTEN_0 (1 << 16) /* Bit 16: Slot 0 Enabled */ +# define SAI_SLOTR_SLOTEN_1 (1 << 17) /* Bit 17: Slot 1 Enabled */ +# define SAI_SLOTR_SLOTEN_2 (1 << 18) /* Bit 18: Slot 2 Enabled */ +# define SAI_SLOTR_SLOTEN_3 (1 << 19) /* Bit 19: Slot 3 Enabled */ +# define SAI_SLOTR_SLOTEN_4 (1 << 20) /* Bit 20: Slot 4 Enabled */ +# define SAI_SLOTR_SLOTEN_5 (1 << 21) /* Bit 21: Slot 5 Enabled */ +# define SAI_SLOTR_SLOTEN_6 (1 << 22) /* Bit 22: Slot 6 Enabled */ +# define SAI_SLOTR_SLOTEN_7 (1 << 23) /* Bit 23: Slot 7 Enabled */ +# define SAI_SLOTR_SLOTEN_8 (1 << 24) /* Bit 24: Slot 8 Enabled */ +# define SAI_SLOTR_SLOTEN_9 (1 << 25) /* Bit 25: Slot 9 Enabled */ +# define SAI_SLOTR_SLOTEN_10 (1 << 26) /* Bit 26: Slot 10 Enabled */ +# define SAI_SLOTR_SLOTEN_11 (1 << 27) /* Bit 27: Slot 11 Enabled */ +# define SAI_SLOTR_SLOTEN_12 (1 << 28) /* Bit 28: Slot 12 Enabled */ +# define SAI_SLOTR_SLOTEN_13 (1 << 29) /* Bit 29: Slot 13 Enabled */ +# define SAI_SLOTR_SLOTEN_14 (1 << 30) /* Bit 30: Slot 14 Enabled */ +# define SAI_SLOTR_SLOTEN_15 (1 << 31) /* Bit 31: Slot 15 Enabled */ /* SAI Interrupt Mask Register 2, SAI Status Register, and SAI Clear Flag Register */ diff --git a/arch/arm/src/stm32l4/stm32l4_sai.c b/arch/arm/src/stm32l4/stm32l4_sai.c new file mode 100644 index 0000000000..f57f2ee593 --- /dev/null +++ b/arch/arm/src/stm32l4/stm32l4_sai.c @@ -0,0 +1,1450 @@ +/**************************************************************************** + * arch/arm/src/stm32l4/stm32l4_sai.c + * + * Copyright (C) 2013-2014, 2017 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt + * Copyright (c) 2016 Motorola Mobility, LLC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "stm32l4_dma.h" +#include "stm32l4_gpio.h" +#include "stm32l4_sai.h" + +#ifdef CONFIG_STM32L4_SAI + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif + +#ifndef CONFIG_AUDIO +# error CONFIG_AUDIO required by this driver +#endif + +#ifndef CONFIG_I2S +# error CONFIG_I2S required by this driver +#endif + +#ifdef CONFIG_STM32L4_SAI_POLLING +# error "Polling SAI not yet supported" +#endif + +#ifdef CONFIG_STM32L4_SAI_INTERRUPTS +# error "Interrupt driven SAI not yet supported" +#endif + +#ifndef CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE +# define CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE (48000) +#endif + +#ifndef CONFIG_STM32L4_SAI_DEFAULT_DATALEN +# define CONFIG_STM32L4_SAI_DEFAULT_DATALEN (16) +#endif + +#ifndef CONFIG_STM32L4_SAI_MAXINFLIGHT +# define CONFIG_STM32L4_SAI_MAXINFLIGHT (16) +#endif + +#ifdef CONFIG_STM32L4_SAI_DMA +/* SAI DMA priority */ + +# if defined(CONFIG_STM32L4_SAI_DMAPRIO) +# define SAI_DMA_PRIO CONFIG_STM32L4_SAI_DMAPRIO +# else +# define SAI_DMA_PRIO DMA_CCR_PRIMED +# endif + +# if (SAI_DMA_PRIO & ~DMA_CCR_PL_MASK) != 0 +# error "Illegal value for CONFIG_STM32L4_SAI_DMAPRIO" +# endif + +/* DMA channel configuration */ + +# define SAI_RXDMA8_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS |DMA_CCR_MINC ) +# define SAI_RXDMA16_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_16BITS|DMA_CCR_PSIZE_16BITS|DMA_CCR_MINC ) +# define SAI_RXDMA32_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_32BITS|DMA_CCR_PSIZE_32BITS|DMA_CCR_MINC ) +# define SAI_TXDMA8_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS |DMA_CCR_MINC|DMA_CCR_DIR) +# define SAI_TXDMA16_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_16BITS|DMA_CCR_PSIZE_16BITS|DMA_CCR_MINC|DMA_CCR_DIR) +# define SAI_TXDMA32_CONFIG (SAI_DMA_PRIO|DMA_CCR_MSIZE_32BITS|DMA_CCR_PSIZE_32BITS|DMA_CCR_MINC|DMA_CCR_DIR) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* I2S buffer container */ + +struct sai_buffer_s +{ + struct sai_buffer_s *flink; /* Supports a singly linked list */ + i2s_callback_t callback; /* Function to call when the transfer completes */ + uint32_t timeout; /* The timeout value to use with transfers */ + void *arg; /* The argument to be returned with the callback */ + struct ap_buffer_s *apb; /* The audio buffer */ + int result; /* The result of the transfer */ +}; + +/* The state of the one SAI peripheral */ + +struct stm32l4_sai_s +{ + struct i2s_dev_s dev; /* Externally visible I2S interface */ + uintptr_t base; /* SAI block register base address */ + sem_t exclsem; /* Assures mutually exclusive acess to SAI */ + uint32_t frequency; /* SAI clock frequency */ + uint32_t syncen; /* Synchronization setting */ +#ifdef CONFIG_STM32L4_SAI_DMA + uint16_t dma_ch; /* DMA channel number */ + DMA_HANDLE dma; /* DMA channel handle */ + uint32_t dma_ccr; /* DMA control register */ +#endif + uint8_t datalen; /* Data width */ + uint32_t samplerate; /* Data sample rate */ + uint8_t rxenab:1; /* True: RX transfers enabled */ + uint8_t txenab:1; /* True: TX transfers enabled */ + WDOG_ID dog; /* Watchdog that handles timeouts */ + sq_queue_t pend; /* A queue of pending transfers */ + sq_queue_t act; /* A queue of active transfers */ + sq_queue_t done; /* A queue of completed transfers */ + struct work_s work; /* Supports worker thread operations */ + + /* Pre-allocated pool of buffer containers */ + + sem_t bufsem; /* Buffer wait semaphore */ + struct sai_buffer_s *freelist; /* A list a free buffer containers */ + struct sai_buffer_s containers[CONFIG_STM32L4_SAI_MAXINFLIGHT]; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_I2S_INFO +static void sai_dump_regs(struct stm32l4_sai_s *priv, const char *msg); +#else +# define sai_dump_regs(s,m) +#endif + +/* Semaphore helpers */ + +static void sai_exclsem_take(struct stm32l4_sai_s *priv); +#define sai_exclsem_give(priv) sem_post(&priv->exclsem) + +static void sai_bufsem_take(struct stm32l4_sai_s *priv); +#define sai_bufsem_give(priv) sem_post(&priv->bufsem) + +/* Buffer container helpers */ + +static struct sai_buffer_s * + sai_buf_allocate(struct stm32l4_sai_s *priv); +static void sai_buf_free(struct stm32l4_sai_s *priv, + struct sai_buffer_s *bfcontainer); +static void sai_buf_initialize(struct stm32l4_sai_s *priv); + +/* DMA support */ + +#ifdef CONFIG_STM32L4_SAI_DMA +static void sai_schedule(struct stm32l4_sai_s *priv, int result); +static void sai_dma_callback(DMA_HANDLE handle, uint8_t isr, void *arg); +#endif + +/* I2S methods */ + +static uint32_t sai_samplerate(struct i2s_dev_s *dev, uint32_t rate); +static uint32_t sai_datawidth(struct i2s_dev_s *dev, int bits); +static int sai_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, uint32_t timeout); +static int sai_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, + uint32_t timeout); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* I2S device operations */ + +static const struct i2s_ops_s g_i2sops = +{ + /* Receiver methods */ + + .i2s_rxsamplerate = sai_samplerate, + .i2s_rxdatawidth = sai_datawidth, + .i2s_receive = sai_receive, + + /* Transmitter methods */ + + .i2s_txsamplerate = sai_samplerate, + .i2s_txdatawidth = sai_datawidth, + .i2s_send = sai_send, +}; + +/* SAI1 state */ + +#ifdef CONFIG_STM32L4_SAI1_A +static struct stm32l4_sai_s g_sai1a_priv = +{ + .dev.ops = &g_i2sops, + .base = STM32L4_SAI1_A_BASE, + .frequency = STM32L4_SAI1_FREQUENCY, +#ifdef CONFIG_STM32L4_SAI1_A_SYNC_WITH_B + .syncen = SAI_CR1_SYNCEN_SYNC_INT, +#else + .syncen = SAI_CR1_SYNCEN_ASYNC, +#endif +#ifdef CONFIG_STM32L4_SAI_DMA + .dma_ch = DMACHAN_SAI1_A, +#endif + .datalen = CONFIG_STM32L4_SAI_DEFAULT_DATALEN, + .samplerate = CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE, +}; +#endif + +#ifdef CONFIG_STM32L4_SAI1_B +static struct stm32l4_sai_s g_sai1b_priv = +{ + .dev.ops = &g_i2sops, + .base = STM32L4_SAI1_B_BASE, + .frequency = STM32L4_SAI1_FREQUENCY, +#ifdef CONFIG_STM32L4_SAI1_B_SYNC_WITH_A + .syncen = SAI_CR1_SYNCEN_SYNC_INT, +#else + .syncen = SAI_CR1_SYNCEN_ASYNC, +#endif +#ifdef CONFIG_STM32L4_SAI_DMA + .dma_ch = DMACHAN_SAI1_B, +#endif + .datalen = CONFIG_STM32L4_SAI_DEFAULT_DATALEN, + .samplerate = CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE, +}; +#endif + +/* SAI2 state */ + +#ifdef CONFIG_STM32L4_SAI2_A +static struct stm32l4_sai_s g_sai2a_priv = +{ + .dev.ops = &g_i2sops, + .base = STM32L4_SAI2_A_BASE, + .frequency = STM32L4_SAI2_FREQUENCY, +#ifdef CONFIG_STM32L4_SAI2_A_SYNC_WITH_B + .syncen = SAI_CR1_SYNCEN_SYNC_INT, +#else + .syncen = SAI_CR1_SYNCEN_ASYNC, +#endif +#ifdef CONFIG_STM32L4_SAI_DMA + .dma_ch = DMACHAN_SAI2_A, +#endif + .datalen = CONFIG_STM32L4_SAI_DEFAULT_DATALEN, + .samplerate = CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE, +}; +#endif + +#ifdef CONFIG_STM32L4_SAI2_B +static struct stm32l4_sai_s g_sai2b_priv = +{ + .dev.ops = &g_i2sops, + .base = STM32L4_SAI2_B_BASE, + .frequency = STM32L4_SAI2_FREQUENCY, +#ifdef CONFIG_STM32L4_SAI2_B_SYNC_WITH_A + .syncen = SAI_CR1_SYNCEN_SYNC_INT, +#else + .syncen = SAI_CR1_SYNCEN_ASYNC, +#endif +#ifdef CONFIG_STM32L4_SAI_DMA + .dma_ch = DMACHAN_SAI2_B, +#endif + .datalen = CONFIG_STM32L4_SAI_DEFAULT_DATALEN, + .samplerate = CONFIG_STM32L4_SAI_DEFAULT_SAMPLERATE, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sai_getbitrate + * + * Description: + * Get the currently configured bitrate + * + * Input Parameters: + * priv - private SAI device structure + * + * Returned Value: + * The current bitrate + * + ****************************************************************************/ + +static inline uint32_t sai_getbitrate(struct stm32l4_sai_s *priv) +{ + /* Calculate the bitrate in Hz */ + + return priv->samplerate * priv->datalen; +} + +/**************************************************************************** + * Name: sai_getreg + * + * Description: + * Get the contents of the SAI register at offset + * + * Input Parameters: + * priv - private SAI device structure + * offset - offset to the register of interest + * + * Returned Value: + * The contents of the 32-bit register + * + ****************************************************************************/ + +static inline uint32_t sai_getreg(struct stm32l4_sai_s *priv, uint8_t offset) +{ + return getreg32(priv->base + offset); +} + +/**************************************************************************** + * Name: sai_putreg + * + * Description: + * Write a 16-bit value to the SAI register at offset + * + * Input Parameters: + * priv - private SAI device structure + * offset - offset to the register of interest + * value - the 32-bit value to be written + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void sai_putreg(struct stm32l4_sai_s *priv, uint8_t offset, + uint32_t value) +{ + putreg32(value, priv->base + offset); +} + +/************************************************************************************ + * Name: sai_modifyreg + * + * Description: + * Clear and set bits in the SAI register at offset + * + * Input Parameters: + * priv - private SAI device structure + * offset - offset to the register of interest + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ************************************************************************************/ + +static void sai_modifyreg(struct stm32l4_sai_s *priv, uint8_t offset, + uint32_t clrbits, uint32_t setbits) +{ + uint32_t regval; + + regval = sai_getreg(priv, offset); + regval &= ~clrbits; + regval |= setbits; + sai_putreg(priv, offset, regval); +} + +/**************************************************************************** + * Name: sai_dump_regs + * + * Description: + * Dump the contents of all SAI block registers + * + * Input Parameters: + * priv - The SAI block controller to dump + * msg - Message to print before the register data + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_I2S_INFO +static void sai_dump_regs(struct stm32l4_sai_s *priv, const char *msg) +{ + if (msg) + i2sinfo("%s\n", msg); + + i2sinfo("CR1:%08x CR2:%08x FRCR:%08x SLOTR:%08x\n", + sai_getreg(priv, STM32L4_SAI_CR1_OFFSET), + sai_getreg(priv, STM32L4_SAI_CR2_OFFSET), + sai_getreg(priv, STM32L4_SAI_FRCR_OFFSET), + sai_getreg(priv, STM32L4_SAI_SLOTR_OFFSET)); + i2sinfo(" IM:%08x SR:%08x CLRFR:%08x\n", + sai_getreg(priv, STM32L4_SAI_IM_OFFSET), + sai_getreg(priv, STM32L4_SAI_SR_OFFSET), + sai_getreg(priv, STM32L4_SAI_CLRFR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: sai_exclsem_take + * + * Description: + * Take the exclusive access semaphore handling any exceptional conditions + * + * Input Parameters: + * priv - A reference to the SAI peripheral state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void sai_exclsem_take(struct stm32l4_sai_s *priv) +{ + int ret; + + /* Wait until we successfully get the semaphore. EINTR is the only + * expected 'failure' (meaning that the wait for the semaphore was + * interrupted by a signal). + */ + + do + { + ret = sem_wait(&priv->exclsem); + DEBUGASSERT(ret == 0 || errno == EINTR); + } + while (ret < 0); +} + +/**************************************************************************** + * Name: sai_mckdivider + * + * Description: + * Setup the master clock divider based on the currently selected data width + * and the sample rate + * + * Input Parameter: + * priv - SAI device structure (only the sample rate and frequency are + * needed at this point). + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void sai_mckdivider(struct stm32l4_sai_s *priv) +{ + uint8_t mckdiv; + + DEBUGASSERT(priv && priv->samplerate > 0 && priv->frequency > 0); + + /* Configure Master Clock using the following formula: + * MCLK_x = SAI_CK_x / (MCKDIV[3:0] * 2) with MCLK_x = 256 * FS + * FS = SAI_CK_x / (MCKDIV[3:0] * 2) * 256 + * MCKDIV[3:0] = SAI_CK_x / FS * 512 + */ + + mckdiv = priv->frequency / (priv->samplerate * 2 * 256); + + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, SAI_CR1_MCKDIV_MASK, + mckdiv << SAI_CR1_MCKDIV_SHIFT); +} + +/**************************************************************************** + * Name: sai_timeout + * + * Description: + * The watchdog timeout without completion of the transfer. + * + * Input Parameters: + * argc - The number of arguments (should be 1) + * arg - The argument (state structure reference cast to uint32_t) + * + * Returned Value: + * None + * + * Assumptions: + * Always called from the interrupt level with interrupts disabled. + * + ****************************************************************************/ + +static void sai_timeout(int argc, uint32_t arg) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)arg; + DEBUGASSERT(priv != NULL); + +#ifdef CONFIG_STM32L4_SAI_DMA + /* Cancel the DMA */ + + stm32l4_dmastop(priv->dma); +#endif + + /* Then schedule completion of the transfer to occur on the worker thread. */ + + sai_schedule(priv, -ETIMEDOUT); +} + +/**************************************************************************** + * Name: sai_dma_setup + * + * Description: + * Setup and initiate the next DMA transfer + * + * Input Parameters: + * priv - SAI state instance + * + * Returned Value: + * OK on success; a negated errno value on failure + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef CONFIG_STM32L4_SAI_DMA +static int sai_dma_setup(struct stm32l4_sai_s *priv) +{ + struct sai_buffer_s *bfcontainer; + struct ap_buffer_s *apb; + uintptr_t samp; + apb_samp_t nbytes; + size_t ntransfers = 0; + int ret; + + /* If there is already an active transmission in progress, then bail + * returning success. + */ + + if (!sq_empty(&priv->act)) + { + return OK; + } + + /* If there are no pending transfer, then bail returning success */ + + if (sq_empty(&priv->pend)) + { + priv->txenab = priv->rxenab = false; + return OK; + } + + /* Remove the pending transfer at the head of the pending queue. */ + + bfcontainer = (struct sai_buffer_s *)sq_remfirst(&priv->pend); + DEBUGASSERT(bfcontainer && bfcontainer->apb); + + apb = bfcontainer->apb; + + /* Get the transfer information, accounting for any data offset */ + + samp = (uintptr_t)&apb->samp[apb->curbyte]; + + /* Configure the DMA */ + + if (priv->txenab) + { + nbytes = apb->nbytes - apb->curbyte; + + switch (priv->datalen) + { + case 8: + priv->dma_ccr = SAI_TXDMA8_CONFIG; + ntransfers = nbytes; + break; + + case 16: + priv->dma_ccr = SAI_TXDMA16_CONFIG; + DEBUGASSERT((nbytes & 0x1) == 0); + ntransfers = nbytes >> 1; + break; + + case 32: + priv->dma_ccr = SAI_TXDMA32_CONFIG; + DEBUGASSERT((nbytes & 0x3) == 0); + ntransfers = nbytes >> 2; + break; + } + } + else if (priv->rxenab) + { + nbytes = apb->nmaxbytes - apb->curbyte; + + switch (priv->datalen) + { + case 8: + priv->dma_ccr = SAI_RXDMA8_CONFIG; + ntransfers = nbytes; + break; + + case 16: + priv->dma_ccr = SAI_RXDMA16_CONFIG; + DEBUGASSERT((nbytes & 0x1) == 0); + ntransfers = nbytes >> 1; + break; + + case 32: + priv->dma_ccr = SAI_RXDMA32_CONFIG; + DEBUGASSERT((nbytes & 0x3) == 0); + ntransfers = nbytes >> 2; + break; + } + } + + DEBUGASSERT(ntransfers > 0); + + stm32l4_dmasetup(priv->dma, priv->base + STM32L4_SAI_DR_OFFSET, + samp, ntransfers, priv->dma_ccr); + + /* Add the container to the list of active DMAs */ + + sq_addlast((sq_entry_t *)bfcontainer, &priv->act); + + /* Start the DMA, saving the container as the current active transfer */ + + stm32l4_dmastart(priv->dma, sai_dma_callback, priv, false); + + /* Enable the transmitter */ + + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, 0, SAI_CR1_SAIEN); + + /* Start a watchdog to catch DMA timeouts */ + + if (bfcontainer->timeout > 0) + { + ret = wd_start(priv->dog, bfcontainer->timeout, (wdentry_t)sai_timeout, + 1, (uint32_t)priv); + + /* Check if we have successfully started the watchdog timer. Note + * that we do nothing in the case of failure to start the timer. We + * are already committed to the DMA anyway. Let's just hope that the + * DMA does not hang. + */ + + if (ret < 0) + { + i2serr("ERROR: wd_start failed: %d\n", ret); + } + } + + return OK; +} +#endif + +/**************************************************************************** + * Name: sai_worker + * + * Description: + * Transfer done worker + * + * Input Parameters: + * arg - the SAI device instance cast to void* + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void sai_worker(void *arg) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)arg; + struct sai_buffer_s *bfcontainer; + irqstate_t flags; + + DEBUGASSERT(priv); + + /* When the transfer was started, the active buffer containers were removed + * from the pend queue and saved in the act queue. We get here when the + * transfer is finished... either successfully, with an error, or with a + * timeout. + * + * In any case, the buffer containers in act will be moved to the end + * of the done queue and act will be emptied before this worker is + * started. + */ + + i2sinfo("act.head=%p done.head=%p\n", priv->act.head, priv->done.head); + + /* Check if IDLE */ + + if (sq_empty(&priv->act)) + { + /* Then start the next transfer. This must be done with interrupts + * disabled. + */ + + flags = enter_critical_section(); +#ifdef CONFIG_STM32L4_SAI_DMA + (void)sai_dma_setup(priv); +#endif + leave_critical_section(flags); + } + + /* Process each buffer in the done queue */ + + while (sq_peek(&priv->done) != NULL) + { + /* Remove the buffer container from the done queue. NOTE that + * interupts must be enabled to do this because the done queue is + * also modified from the interrupt level. + */ + + flags = enter_critical_section(); + bfcontainer = (struct sai_buffer_s *)sq_remfirst(&priv->done); + leave_critical_section(flags); + + /* Perform the transfer done callback */ + + DEBUGASSERT(bfcontainer && bfcontainer->callback); + bfcontainer->callback(&priv->dev, bfcontainer->apb, + bfcontainer->arg, bfcontainer->result); + + /* Release our reference on the audio buffer. This may very likely + * cause the audio buffer to be freed. + */ + + apb_free(bfcontainer->apb); + + /* And release the buffer container */ + + sai_buf_free(priv, bfcontainer); + } +} + +/**************************************************************************** + * Name: sai_schedule + * + * Description: + * An transfer completion or timeout has occurred. Schedule processing on + * the working thread. + * + * Input Parameters: + * priv - SAI state instance + * result - The result of the transfer + * + * Returned Value: + * None + * + * Assumptions: + * - Interrupts are disabled + * - The timeout has been canceled. + * + ****************************************************************************/ + +static void sai_schedule(struct stm32l4_sai_s *priv, int result) +{ + struct sai_buffer_s *bfcontainer; + int ret; + + /* Move all entries from the act queue to the done queue */ + + while (!sq_empty(&priv->act)) + { + /* Remove the next buffer container from the act list */ + + bfcontainer = (struct sai_buffer_s *)sq_remfirst(&priv->act); + + /* Report the result of the transfer */ + + bfcontainer->result = result; + + /* Add the completed buffer container to the tail of the done queue */ + + sq_addlast((sq_entry_t *)bfcontainer, &priv->done); + } + + /* If the worker has completed running, then reschedule the working thread. + * REVISIT: There may be a race condition here. So we do nothing is the + * worker is not available. + */ + + if (work_available(&priv->work)) + { + /* Schedule the done processing to occur on the worker thread. */ + + ret = work_queue(HPWORK, &priv->work, sai_worker, priv, 0); + if (ret != 0) + { + i2serr("ERROR: Failed to queue work: %d\n", ret); + } + } +} + +/**************************************************************************** + * Name: sai_dma_callback + * + * Description: + * This callback function is invoked at the completion of the SAI DMA. + * + * Input Parameters: + * handle - The DMA handler + * isr - The interrupt status of the DMA transfer + * arg - A pointer to the SAI state instance + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32L4_SAI_DMA +static void sai_dma_callback(DMA_HANDLE handle, uint8_t isr, void *arg) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)arg; + DEBUGASSERT(priv); + + /* Cancel the watchdog timeout */ + + (void)wd_cancel(priv->dog); + + /* Then schedule completion of the transfer to occur on the worker thread */ + + sai_schedule(priv, (isr & DMA_CHAN_TEIF_BIT) ? -EIO : OK); +} +#endif + +/**************************************************************************** + * Name: sai_samplerate + * + * Description: + * Set the I2S RX/TX sample rate. + * + * Input Parameters: + * dev - Device-specific state data + * rate - The I2S sample rate in samples (not bits) per second + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t sai_samplerate(struct i2s_dev_s *dev, uint32_t rate) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)dev; + + DEBUGASSERT(priv && rate > 0); + + /* Save the new sample rate and update the divider */ + + priv->samplerate = rate; + sai_mckdivider(priv); + + return sai_getbitrate(priv); +} + +/**************************************************************************** + * Name: sai_datawidth + * + * Description: + * Set the I2S data width. The bitrate is determined by + * sample_rate * data_width. + * + * Input Parameters: + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t sai_datawidth(struct i2s_dev_s *dev, int bits) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)dev; + uint32_t setbits; + + DEBUGASSERT(priv && bits >= 8); + + switch (bits) + { + case 8: + setbits = SAI_CR1_DS_8BITS; + break; + + case 16: + setbits = SAI_CR1_DS_16BITS; + break; + + case 32: + setbits = SAI_CR1_DS_32BITS; + break; + + default: + i2serr("ERROR: Unsupported or invalid data width: %d\n", bits); + return 0; + } + + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, SAI_CR1_DS_MASK, setbits); + + sai_modifyreg(priv, STM32L4_SAI_FRCR_OFFSET, + SAI_FRCR_FSALL_MASK | SAI_FRCR_FRL_MASK, + SAI_FRCR_FSALL(bits) | SAI_FRCR_FRL(bits * 2)); + + /* Save the new data width */ + + priv->datalen = bits; + + return sai_getbitrate(priv); +} + +/**************************************************************************** + * Name: sai_receive + * + * Description: + * Receive a block of data from I2S. + * + * Input Parameters: + * dev - Device-specific state data + * apb - A pointer to the audio buffer in which to recieve data + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. + * + * Returned Value: + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. + * + ****************************************************************************/ + +static int sai_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, uint32_t timeout) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)dev; + struct sai_buffer_s *bfcontainer; + uint32_t mode; + irqstate_t flags; + int ret; + + DEBUGASSERT(priv && apb); + i2sinfo("apb=%p nbytes=%d arg=%p timeout=%d\n", + apb, apb->nbytes - apb->curbyte, arg, timeout); + + /* Allocate a buffer container in advance */ + + bfcontainer = sai_buf_allocate(priv); + DEBUGASSERT(bfcontainer); + + /* Get exclusive access to the SAI driver data */ + + sai_exclsem_take(priv); + + /* Verify not already TX'ing */ + + if (priv->txenab) + { + i2serr("ERROR: SAI has no receiver\n"); + ret = -EAGAIN; + goto errout_with_exclsem; + } + + mode = priv->syncen ? SAI_CR1_MODE_SLAVE_RX : SAI_CR1_MODE_MASTER_RX; + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, SAI_CR1_MODE_MASK, mode); + priv->rxenab = true; + + /* Add a reference to the audio buffer */ + + apb_reference(apb); + + /* Initialize the buffer container structure */ + + bfcontainer->callback = (void *)callback; + bfcontainer->timeout = timeout; + bfcontainer->arg = arg; + bfcontainer->apb = apb; + bfcontainer->result = -EBUSY; + + /* Add the buffer container to the end of the pending queue */ + + flags = enter_critical_section(); + sq_addlast((sq_entry_t *)bfcontainer, &priv->pend); + + /* Then start the next transfer. If there is already a transfer in progess, + * then this will do nothing. + */ + +#ifdef CONFIG_STM32L4_SAI_DMA + ret = sai_dma_setup(priv); +#endif + DEBUGASSERT(ret == OK); + leave_critical_section(flags); + sai_exclsem_give(priv); + return OK; + +errout_with_exclsem: + sai_exclsem_give(priv); + sai_buf_free(priv, bfcontainer); + return ret; +} + +/**************************************************************************** + * Name: sai_send + * + * Description: + * Send a block of data on I2S. + * + * Input Parameters: + * dev - Device-specific state data + * apb - A pointer to the audio buffer from which to send data + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. + * + * Returned Value: + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. + * + ****************************************************************************/ + +static int sai_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, uint32_t timeout) +{ + struct stm32l4_sai_s *priv = (struct stm32l4_sai_s *)dev; + struct sai_buffer_s *bfcontainer; + uint32_t mode; + irqstate_t flags; + int ret; + + DEBUGASSERT(priv && apb); + i2sinfo("apb=%p nbytes=%d arg=%p timeout=%d\n", + apb, apb->nbytes - apb->curbyte, arg, timeout); + + /* Allocate a buffer container in advance */ + + bfcontainer = sai_buf_allocate(priv); + DEBUGASSERT(bfcontainer); + + /* Get exclusive access to the SAI driver data */ + + sai_exclsem_take(priv); + + /* Verify not already RX'ing */ + + if (priv->rxenab) + { + i2serr("ERROR: SAI has no transmitter\n"); + ret = -EAGAIN; + goto errout_with_exclsem; + } + + mode = priv->syncen ? SAI_CR1_MODE_SLAVE_TX : SAI_CR1_MODE_MASTER_TX; + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, SAI_CR1_MODE_MASK, mode); + priv->txenab = true; + + /* Add a reference to the audio buffer */ + + apb_reference(apb); + + /* Initialize the buffer container structure */ + + bfcontainer->callback = (void *)callback; + bfcontainer->timeout = timeout; + bfcontainer->arg = arg; + bfcontainer->apb = apb; + bfcontainer->result = -EBUSY; + + /* Add the buffer container to the end of the pending queue */ + + flags = enter_critical_section(); + sq_addlast((sq_entry_t *)bfcontainer, &priv->pend); + + /* Then start the next transfer. If there is already a transfer in progess, + * then this will do nothing. + */ + +#ifdef CONFIG_STM32L4_SAI_DMA + ret = sai_dma_setup(priv); +#endif + DEBUGASSERT(ret == OK); + leave_critical_section(flags); + sai_exclsem_give(priv); + return OK; + +errout_with_exclsem: + sai_exclsem_give(priv); + sai_buf_free(priv, bfcontainer); + return ret; +} + +/**************************************************************************** + * Name: sai_bufsem_take + * + * Description: + * Take the buffer semaphore handling any exceptional conditions + * + * Input Parameters: + * priv - A reference to the SAI peripheral state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void sai_bufsem_take(struct stm32l4_sai_s *priv) +{ + int ret; + + /* Wait until we successfully get the semaphore. EINTR is the only + * expected 'failure' (meaning that the wait for the semaphore was + * interrupted by a signal). + */ + + do + { + ret = sem_wait(&priv->bufsem); + DEBUGASSERT(ret == 0 || errno == EINTR); + } + while (ret < 0); +} + +/**************************************************************************** + * Name: sai_buf_allocate + * + * Description: + * Allocate a buffer container by removing the one at the head of the + * free list + * + * Input Parameters: + * priv - SAI state instance + * + * Returned Value: + * A non-NULL pointer to the allocate buffer container on success; NULL if + * there are no available buffer containers. + * + * Assumptions: + * The caller does NOT have exclusive access to the SAI state structure. + * That would result in a deadlock! + * + ****************************************************************************/ + +static struct sai_buffer_s *sai_buf_allocate(struct stm32l4_sai_s *priv) +{ + struct sai_buffer_s *bfcontainer; + irqstate_t flags; + + /* Set aside a buffer container. By doing this, we guarantee that we will + * have at least one free buffer container. + */ + + sai_bufsem_take(priv); + + /* Get the buffer from the head of the free list */ + + flags = enter_critical_section(); + bfcontainer = priv->freelist; + ASSERT(bfcontainer); + + /* Unlink the buffer from the freelist */ + + priv->freelist = bfcontainer->flink; + leave_critical_section(flags); + return bfcontainer; +} + +/**************************************************************************** + * Name: sai_buf_free + * + * Description: + * Free buffer container by adding it to the head of the free list + * + * Input Parameters: + * priv - SAI state instance + * bfcontainer - The buffer container to be freed + * + * Returned Value: + * None + * + * Assumptions: + * The caller has exclusive access to the SAI state structure + * + ****************************************************************************/ + +static void sai_buf_free(struct stm32l4_sai_s *priv, struct sai_buffer_s *bfcontainer) +{ + irqstate_t flags; + + /* Put the buffer container back on the free list */ + + flags = enter_critical_section(); + bfcontainer->flink = priv->freelist; + priv->freelist = bfcontainer; + leave_critical_section(flags); + + /* Wake up any threads waiting for a buffer container */ + + sai_bufsem_give(priv); +} + +/**************************************************************************** + * Name: sai_buf_initialize + * + * Description: + * Initialize the buffer container allocator by adding all of the + * pre-allocated buffer containers to the free list + * + * Input Parameters: + * priv - SAI state instance + * + * Returned Value: + * None + * + * Assumptions: + * Called early in SAI initialization so that there are no issues with + * concurrency. + * + ****************************************************************************/ + +static void sai_buf_initialize(struct stm32l4_sai_s *priv) +{ + int i; + + priv->freelist = NULL; + sem_init(&priv->bufsem, 0, CONFIG_STM32L4_SAI_MAXINFLIGHT); + + for (i = 0; i < CONFIG_STM32L4_SAI_MAXINFLIGHT; i++) + { + sai_buf_free(priv, &priv->containers[i]); + } +} + +/**************************************************************************** + * Name: sai_portinitialize + * + * Description: + * Initialize the selected SAI port in its default state + * + * Input Parameter: + * priv - private SAI device structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void sai_portinitialize(struct stm32l4_sai_s *priv) +{ + sai_dump_regs(priv, "Before initialization"); + + sem_init(&priv->exclsem, 0, 1); + + /* Create a watchdog timer to catch transfer timeouts */ + + priv->dog = wd_create(); + ASSERT(priv->dog); + + /* Initialize buffering */ + + sai_buf_initialize(priv); + + /* Configure the master clock divider */ + + sai_mckdivider(priv); + + /* Configure the data width */ + + sai_datawidth((struct i2s_dev_s *)priv, CONFIG_STM32L4_SAI_DEFAULT_DATALEN); + +#ifdef CONFIG_STM32L4_SAI_DMA + /* Get DMA channel */ + + priv->dma = stm32l4_dmachannel(priv->dma_ch); + DEBUGASSERT(priv->dma); + + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, 0, SAI_CR1_DMAEN); +#endif + + sai_modifyreg(priv, STM32L4_SAI_CR1_OFFSET, SAI_CR1_SYNCEN_MASK, priv->syncen); + + sai_modifyreg(priv, STM32L4_SAI_CR2_OFFSET, SAI_CR2_FTH_MASK, SAI_CR2_FTH_1QF); + + sai_modifyreg(priv, STM32L4_SAI_FRCR_OFFSET, + SAI_FRCR_FSDEF | SAI_FRCR_FSPOL | SAI_FRCR_FSOFF, + SAI_FRCR_FSDEF_CHID | SAI_FRCR_FSPOL_LOW | SAI_FRCR_FSOFF_BFB); + + sai_modifyreg(priv, STM32L4_SAI_SLOTR_OFFSET, + SAI_SLOTR_NBSLOT_MASK | SAI_SLOTR_SLOTEN_MASK, + SAI_SLOTR_NBSLOT(2) | SAI_SLOTR_SLOTEN_0 | SAI_SLOTR_SLOTEN_1); + + sai_dump_regs(priv, "After initialization"); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32l4_sai_initialize + * + * Description: + * Initialize the selected SAI block + * + * Input Parameter: + * intf - I2S interface number (identifying the "logical" SAI interface) + * + * Returned Value: + * Valid I2S device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct i2s_dev_s *stm32l4_sai_initialize(int intf) +{ + struct stm32l4_sai_s *priv; + irqstate_t flags; + + flags = enter_critical_section(); + + switch (intf) + { +#ifdef CONFIG_STM32L4_SAI1_A + case SAI1_BLOCK_A: + { + i2sinfo("SAI1 Block A Selected\n"); + priv = &g_sai1a_priv; + + stm32l4_configgpio(GPIO_SAI1_SD_A); +# ifndef CONFIG_STM32L4_SAI1_A_SYNC_WITH_B + stm32l4_configgpio(GPIO_SAI1_FS_A); + stm32l4_configgpio(GPIO_SAI1_SCK_A); + stm32l4_configgpio(GPIO_SAI1_MCLK_A); +# endif + break; + } +#endif +#ifdef CONFIG_STM32L4_SAI1_B + case SAI1_BLOCK_B: + { + i2sinfo("SAI1 Block B Selected\n"); + priv = &g_sai1b_priv; + + stm32l4_configgpio(GPIO_SAI1_SD_B); +# ifndef CONFIG_STM32L4_SAI1_B_SYNC_WITH_A + stm32l4_configgpio(GPIO_SAI1_FS_B); + stm32l4_configgpio(GPIO_SAI1_SCK_B); + stm32l4_configgpio(GPIO_SAI1_MCLK_B); +# endif + break; + } +#endif +#ifdef CONFIG_STM32L4_SAI2_A + case SAI2_BLOCK_A: + { + i2sinfo("SAI2 Block A Selected\n"); + priv = &g_sai2a_priv; + + stm32l4_configgpio(GPIO_SAI2_SD_A); +# ifndef CONFIG_STM32L4_SAI2_A_SYNC_WITH_B + stm32l4_configgpio(GPIO_SAI2_FS_A); + stm32l4_configgpio(GPIO_SAI2_SCK_A); + stm32l4_configgpio(GPIO_SAI2_MCLK_A); +# endif + break; + } +#endif +#ifdef CONFIG_STM32L4_SAI2_B + case SAI2_BLOCK_B: + { + i2sinfo("SAI2 Block B Selected\n"); + priv = &g_sai2b_priv; + + stm32l4_configgpio(GPIO_SAI2_SD_B); +# ifndef CONFIG_STM32L4_SAI2_B_SYNC_WITH_A + stm32l4_configgpio(GPIO_SAI2_FS_B); + stm32l4_configgpio(GPIO_SAI2_SCK_B); + stm32l4_configgpio(GPIO_SAI2_MCLK_B); +# endif + break; + } +#endif + default: + { + i2sinfo("No SAI interface defined\n"); + goto err; + } + } + + sai_portinitialize(priv); + leave_critical_section(flags); + + return &priv->dev; + +err: + leave_critical_section(flags); + return NULL; +} + +#endif diff --git a/arch/arm/src/stm32l4/stm32l4_sai.h b/arch/arm/src/stm32l4/stm32l4_sai.h new file mode 100644 index 0000000000..4fe1827c0a --- /dev/null +++ b/arch/arm/src/stm32l4/stm32l4_sai.h @@ -0,0 +1,95 @@ +/****************************************************************************** + * arch/arm/src/stm32l4/stm32l4_sai.h + * + * Copyright (C) 2017 Gregory Nutt. All rights reserved. + * Copyright (c) 2016 Motorola Mobility, LLC. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32L4_STM32L4_SAI_H +#define __ARCH_ARM_SRC_STM32L4_STM32L4_SAI_H + +/****************************************************************************** + * Included Files + ******************************************************************************/ + +#include + +#include "chip.h" +#include "chip/stm32l4_sai.h" + +#include + +/****************************************************************************** + * Pre-processor definitions + ******************************************************************************/ + +#define SAI1_BLOCK_A 0 +#define SAI1_BLOCK_B 1 +#define SAI2_BLOCK_A 2 +#define SAI2_BLOCK_B 3 + +/****************************************************************************** + * Public Function Prototypes + ******************************************************************************/ + +#ifndef __ASSEMBLY__ +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: stm32l4_sai_initialize + * + * Description: + * Initialize the selected SAI block + * + * Input Parameters: + * intf - I2S interface number (identifying the "logical" SAI interface) + * + * Returned Value: + * Valid I2S device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct i2s_dev_s *stm32l4_sai_initialize(int intf); + +#undef EXTERN +#ifdef __cplusplus +} +#endif +#endif /* __ASSEMBLY__ */ + +#endif /* __ARCH_ARM_SRC_STM32L4_STM32L4_SAI_H */