diff --git a/boards/stm32_common/stm32f746disco/audio.adb b/boards/stm32_common/stm32f746disco/audio.adb index fa481d98b..414fe4cd3 100644 --- a/boards/stm32_common/stm32f746disco/audio.adb +++ b/boards/stm32_common/stm32f746disco/audio.adb @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2016, AdaCore -- +-- Copyright (C) 2016-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -32,7 +32,6 @@ -- @author MCD Application Team -- ------------------------------------------------------------------------------ -with HAL; use HAL; with STM32; use STM32; with STM32.Device; use STM32.Device; with STM32.Board; use STM32.Board; @@ -50,25 +49,107 @@ package body Audio is SAI2_SD_B : GPIO_Point renames PG10; SAI2_FS_A : GPIO_Point renames PI7; SAI_Pins : constant GPIO_Points := - (SAI2_MCLK_A, SAI2_SCK_A, SAI2_SD_A, SAI2_SD_B, - SAI2_FS_A); + (SAI2_MCLK_A, SAI2_SCK_A, SAI2_SD_A, SAI2_SD_B, SAI2_FS_A); SAI_Pins_AF : GPIO_Alternate_Function renames GPIO_AF_SAI2_10; - -- SAI in/out conf SAI_Out_Block : SAI_Block renames Block_A; --- SAI_In_Block : SAI_Block renames Block_B; procedure Set_Audio_Clock (Freq : Audio_Frequency); + procedure Initialize_Audio_Out_Pins; - procedure Initialize_SAI_Out (Freq : Audio_Frequency); + + procedure Initialize_Audio_DMA; + -- Configure the DMA channel to the SAI peripheral + + procedure Initialize_SAI_Out (Freq : Audio_Frequency; Sink : Audio_Outputs); + procedure Initialize_Audio_I2C; + -- Initialize the I2C Port to send commands to the driver + + function As_Device_Volume (Input : Audio_Volume) return WM8994.Volume_Level; + -- Converts the percentage input value to the device-specific range + + ---------------- + -- Initialize -- + ---------------- + + procedure Initialize + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume; + Frequency : Audio_Frequency; + Bit_Width : Audio_Bit_Width; + Sink : Audio_Outputs) + is + begin + STM32.SAI.Deinitialize (Audio_SAI, SAI_Out_Block); + + Set_Audio_Clock (Frequency); + + Initialize_Audio_Out_Pins; + Initialize_Audio_DMA; + Initialize_SAI_Out (Frequency, Sink); + Initialize_Audio_I2C; + + if This.Device.Chip_ID /= WM8994.WM8994_ID then + raise Program_Error with "Invalid Chip ID received from the Audio Codec"; + end if; + + This.Device.Reset; + This.Device.Initialize + (Input => WM8994.No_Input, + Output => WM8994.Output_Device (Sink), + Volume => As_Device_Volume (Volume), + Frequency => WM8994.Audio_Frequency (Frequency), + Bit_Width => WM8994.Audio_Sample_Width (Bit_Width)); + + This.Sink := WM8994.Output_Device (Sink); + -- For sake of any individual calls to Set_Frequency, assuming the STM + -- code is correct that we also need to set the clocks when setting the + -- frequency + end Initialize; + + ---------------- + -- Set_Volume -- + ---------------- + + procedure Set_Volume + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume) + is + begin + This.Device.Set_Volume (As_Device_Volume (Volume)); + end Set_Volume; + + ------------------- + -- Set_Frequency -- + ------------------- + + procedure Set_Frequency + (This : in out WM8994_Audio_CODEC; + Frequency : Audio_Frequency) + is + use type WM8994.Output_Device; + begin + if This.Sink = WM8994.No_Output then + raise Constraint_Error with "No prior call to Initialize"; + end if; + + -- The following is per the example source file from STM, + -- STM32746G-Disco_example\board_drivers\stm32746g_discovery_audio.c, + -- whereas the WM8894 driver just has a specific function that writes + -- the necessary configuration bits to the device register without any + -- clock configuration too. + Set_Audio_Clock (Frequency); + STM32.SAI.Disable (Audio_SAI, SAI_Out_Block); + Initialize_SAI_Out (Frequency, Audio_Outputs (This.Sink)); + STM32.SAI.Enable (Audio_SAI, SAI_Out_Block); + end Set_Frequency; --------------------- -- Set_Audio_Clock -- --------------------- - procedure Set_Audio_Clock (Freq : Audio_Frequency) - is + procedure Set_Audio_Clock (Freq : Audio_Frequency) is begin -- Two groups of frequencies: the 44kHz family and the 48kHz family -- The Actual audio frequency is calculated then with the following @@ -86,37 +167,32 @@ package body Audio is PLLI2SQ => 2, -- SAI Clk(First level) = 214.5 MHz PLLI2SDIVQ => 19); -- I2S Clk = 215.4 / 19 = 11.289 MHz - when Audio_Freq_8kHz | Audio_Freq_16kHz | - Audio_Freq_48kHz | Audio_Freq_96kHz => + when Audio_Freq_8kHz | Audio_Freq_12kHz | Audio_Freq_16kHz | + Audio_Freq_24kHz | Audio_Freq_48kHz | Audio_Freq_96kHz => Configure_SAI_I2S_Clock (Audio_SAI, PLLI2SN => 344, -- VCO Output = 344MHz PLLI2SQ => 7, -- SAI Clk(First level) = 49.142 MHz - PLLI2SDIVQ => 1); -- I2S Clk = 49.142 MHz + PLLI2SDIVQ => 1); -- I2S Clk = 49.142 MHz + + when Audio_Freq_88kHz => + -- Best rational approx of 88200*256 Hz = 22.5792 MHz from 1 MHz VCO; + -- 271/4/3 = 22.583 MHz, MCKDIV=0 -> FS = 88216 Hz (0.018% error) + Configure_SAI_I2S_Clock + (Audio_SAI, + PLLI2SN => 271, -- VCO Output = 271MHz + PLLI2SQ => 4, -- SAI Clk(First level) = 67.75 MHz + PLLI2SDIVQ => 3); -- SAI Clk = 22.583 MHz end case; end Set_Audio_Clock; - ------------------------------- - -- Initialize_Audio_Out_Pins -- - ------------------------------- + -------------------------- + -- Initialize_Audio_DMA -- + -------------------------- - procedure Initialize_Audio_Out_Pins - is + procedure Initialize_Audio_DMA is begin - Enable_Clock (Audio_SAI); - Enable_Clock (SAI_Pins); - - Configure_IO - (SAI_Pins, - (Mode => Mode_AF, - AF => SAI_Pins_AF, - AF_Output_Type => Push_Pull, - AF_Speed => Speed_High, - Resistors => Floating)); - Enable_Clock (Audio_DMA); - - -- Configure the DMA channel to the SAI peripheral Disable (Audio_DMA, Audio_DMA_Out_Stream); Configure (Audio_DMA, @@ -134,16 +210,38 @@ package body Audio is Memory_Burst_Size => Memory_Burst_Single, Peripheral_Burst_Size => Peripheral_Burst_Single)); Clear_All_Status (Audio_DMA, Audio_DMA_Out_Stream); + end Initialize_Audio_DMA; + + ------------------------------- + -- Initialize_Audio_Out_Pins -- + ------------------------------- + + procedure Initialize_Audio_Out_Pins is + begin + Enable_Clock (Audio_SAI); + Enable_Clock (SAI_Pins); + + Configure_IO + (SAI_Pins, + (Mode => Mode_AF, + AF => SAI_Pins_AF, + AF_Output_Type => Push_Pull, + AF_Speed => Speed_High, + Resistors => Floating)); end Initialize_Audio_Out_Pins; ------------------------ -- Initialize_SAI_Out -- ------------------------ - procedure Initialize_SAI_Out (Freq : Audio_Frequency) + procedure Initialize_SAI_Out + (Freq : Audio_Frequency; + Sink : Audio_Outputs) is + Active_Slots : SAI_Slots; begin STM32.SAI.Disable (Audio_SAI, SAI_Out_Block); + STM32.SAI.Configure_Audio_Block (Audio_SAI, SAI_Out_Block, @@ -158,6 +256,7 @@ package body Audio is Synchronization => Asynchronous_Mode, Output_Drive => Drive_Immediate, FIFO_Threshold => FIFO_1_Quarter_Full); + STM32.SAI.Configure_Block_Frame (Audio_SAI, SAI_Out_Block, @@ -166,13 +265,24 @@ package body Audio is Frame_Sync => FS_Frame_And_Channel_Identification, FS_Polarity => FS_Active_Low, FS_Offset => Before_First_Bit); + + case Sink is + when Headphone => + Active_Slots := Slot_0 or Slot_2; + when Speaker => + Active_Slots := Slot_1 or Slot_3; + when Both => + Active_Slots := Slot_0 or Slot_1 or Slot_2 or Slot_3; + end case; + STM32.SAI.Configure_Block_Slot (Audio_SAI, SAI_Out_Block, First_Bit_Offset => 0, Slot_Size => Data_Size, Number_Of_Slots => 4, - Enabled_Slots => Slot_0 or Slot_2); + Enabled_Slots => Active_Slots); + STM32.SAI.Enable (Audio_SAI, SAI_Out_Block); end Initialize_SAI_Out; @@ -180,8 +290,7 @@ package body Audio is -- Initialize_Audio_I2C -- -------------------------- - procedure Initialize_Audio_I2C - is + procedure Initialize_Audio_I2C is begin STM32.Setup.Setup_I2C_Master (Port => Audio_I2C, SDA => Audio_I2C_SDA, @@ -191,47 +300,12 @@ package body Audio is Clock_Speed => 100_000); end Initialize_Audio_I2C; - ---------------- - -- Initialize -- - ---------------- - - procedure Initialize_Audio_Out - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume; - Frequency : Audio_Frequency) - is - begin - STM32.SAI.Deinitialize (Audio_SAI, SAI_Out_Block); - - Set_Audio_Clock (Frequency); - - -- Initialize the SAI - Initialize_Audio_Out_Pins; - Initialize_SAI_Out (Frequency); - - -- Initialize the I2C Port to send commands to the driver - Initialize_Audio_I2C; - - if This.Device.Read_ID /= WM8994.WM8994_ID then - raise Constraint_Error with "Invalid ID received from the Audio Code"; - end if; - - This.Device.Reset; - This.Device.Init - (Input => WM8994.No_Input, - Output => WM8994.Auto, - Volume => UInt8 (Volume), - Frequency => - WM8994.Audio_Frequency'Enum_Val - (Audio_Frequency'Enum_Rep (Frequency))); - end Initialize_Audio_Out; - ---------- -- Play -- ---------- procedure Play - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Buffer : Audio_Buffer) is begin @@ -258,7 +332,7 @@ package body Audio is -- Pause -- ----------- - procedure Pause (This : in out WM8994_Audio_Device) is + procedure Pause (This : in out WM8994_Audio_CODEC) is begin This.Device.Pause; DMA_Pause (Audio_SAI, SAI_Out_Block); @@ -268,8 +342,7 @@ package body Audio is -- Resume -- ------------ - procedure Resume (This : in out WM8994_Audio_Device) - is + procedure Resume (This : in out WM8994_Audio_CODEC) is begin This.Device.Resume; DMA_Resume (Audio_SAI, SAI_Out_Block); @@ -279,8 +352,7 @@ package body Audio is -- Stop -- ---------- - procedure Stop (This : in out WM8994_Audio_Device) - is + procedure Stop (This : in out WM8994_Audio_CODEC) is begin This.Device.Stop (WM8994.Stop_Power_Down_Sw); DMA_Stop (Audio_SAI, SAI_Out_Block); @@ -289,32 +361,12 @@ package body Audio is STM32.DMA.Clear_All_Status (Audio_DMA, Audio_DMA_Out_Stream); end Stop; - ---------------- - -- Set_Volume -- - ---------------- - - procedure Set_Volume - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume) - is - begin - This.Device.Set_Volume (UInt8 (Volume)); - end Set_Volume; - - ------------------- - -- Set_Frequency -- - ------------------- + ---------------------- + -- As_Device_Volume -- + ---------------------- - procedure Set_Frequency - (This : in out WM8994_Audio_Device; - Frequency : Audio_Frequency) - is - pragma Unreferenced (This); - begin - Set_Audio_Clock (Frequency); - STM32.SAI.Disable (Audio_SAI, SAI_Out_Block); - Initialize_SAI_Out (Frequency); - STM32.SAI.Enable (Audio_SAI, SAI_Out_Block); - end Set_Frequency; + function As_Device_Volume (Input : Audio_Volume) return WM8994.Volume_Level is + (if Input = 100 then WM8994.Max_Volume + else UInt16 (Input) * WM8994.Max_Volume / 100); end Audio; diff --git a/boards/stm32_common/stm32f746disco/audio.ads b/boards/stm32_common/stm32f746disco/audio.ads index 04655c87d..a6e1159c5 100644 --- a/boards/stm32_common/stm32f746disco/audio.ads +++ b/boards/stm32_common/stm32f746disco/audio.ads @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2016, AdaCore -- +-- Copyright (C) 2016-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -32,50 +32,99 @@ -- @author MCD Application Team -- ------------------------------------------------------------------------------ -with HAL.Audio; use HAL.Audio; -with HAL.I2C; use HAL.I2C; -with Ravenscar_Time; +with HAL; use HAL; +with HAL.I2C; use HAL.I2C; +with Interfaces; use Interfaces; +with WM8994; -private with WM8994; +private with Ravenscar_Time; package Audio is - type WM8994_Audio_Device (Port : not null Any_I2C_Port) is + type WM8994_Audio_CODEC (Port : not null Any_I2C_Port) is tagged limited private; - procedure Initialize_Audio_Out - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume; - Frequency : Audio_Frequency); + type Audio_Outputs is new WM8994.Output_Device with + Static_Predicate => Audio_Outputs in + Headphone | Speaker | Both; - procedure Set_Volume - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume); + type Audio_Frequency is new WM8994.Audio_Frequency; - procedure Set_Frequency - (This : in out WM8994_Audio_Device; - Frequency : Audio_Frequency); + type Audio_Bit_Width is new WM8994.Audio_Sample_Width; + + type Audio_Volume is range 0 .. 100; -- a percentage + + type Audio_Buffer is array (Natural range <>) of Integer_16 + with Component_Size => 16, Alignment => 2; + -- TODO: change the component to a signed 32-bit quantity, so that any + -- sample bit width up to 32 bits could be supported. Otherwise we can + -- only support 16-bit samples. See procedure Initialize below. + + procedure Initialize + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume; + Frequency : Audio_Frequency; + Bit_Width : Audio_Bit_Width; -- must be 16 bits due to Audio_Buffer component size + Sink : Audio_Outputs); + -- This routine initializes the hardware and configures the volume, sampling + -- frequency, output device (the sink), and the sampling bit width. This + -- routine must be called, before any others. procedure Play - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Buffer : Audio_Buffer); + -- Start playing content from the specified buffer. The effect is to tell + -- the underlying WM8994 CODEC where the buffer to be played is located, + -- and cause the CODEC to start playing the contents. + -- + -- NB: playing continues after the call returns. An additional mechanism, + -- outside this package, updates the content of the buffer while the CODEC + -- is playing it. That update/play process continues until either there is + -- no more music to be played, or Stop or Pause is called. procedure Pause - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- After calling Pause, only Resume should be called for resuming play (do + -- not call Start_Playing again). procedure Resume - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- Procedure Resume should be called only when the audio is playing or + -- paused (not stopped). procedure Stop - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- Stops the hardware and update/play process. Once called, you must call + -- Start_Playing again if you want to restart the output. + + procedure Set_Volume + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume); + + procedure Set_Frequency + (This : in out WM8994_Audio_CODEC; + Frequency : Audio_Frequency); private Audio_I2C_Addr : constant I2C_Address := 16#34#; - type WM8994_Audio_Device (Port : not null Any_I2C_Port) is - tagged limited record - Device : WM8994.WM8994_Device (Port, Audio_I2C_Addr, Ravenscar_Time.Delays); + type WM8994_Audio_CODEC + (Port : not null Any_I2C_Port) + is tagged limited record + Device : WM8994.Audio_CODEC (Port, Audio_I2C_Addr, Ravenscar_Time.Delays); + Sink : WM8994.Output_Device := WM8994.No_Output; + -- The initial value of Sink is overwritten by Initialize. The value + -- No_Output will trigger a C_E if ever referenced, so it is used as a + -- check that Initialize has been called. We need the component Sink + -- itself for the sake of a clean parameter profile for Set_Frequency, + -- otherwise clients would have to pass another parameter to specify + -- the output device selection again (after having done so when calling + -- Initialize). That's because Set_Frequency needs to do enough hardware + -- re-initialization to accommodate the new frequency, but doing so + -- requires the output device selection so that the re-init can select + -- the active slots. Just activating all slots (as is done in the STM + -- C code) doesn't work (at least in the current Ada code). end record; end Audio; diff --git a/boards/stm32_common/stm32f746disco/stm32-board.ads b/boards/stm32_common/stm32f746disco/stm32-board.ads index c74527d0e..3dd3b9fe0 100644 --- a/boards/stm32_common/stm32f746disco/stm32-board.ads +++ b/boards/stm32_common/stm32f746disco/stm32-board.ads @@ -160,7 +160,7 @@ package STM32.Board is Audio_DMA_Out_Stream : DMA_Stream_Selector renames Stream_4; Audio_DMA_Out_Channel : DMA_Channel_Selector renames Channel_3; - Audio_Device : aliased Audio.WM8994_Audio_Device (Audio_I2C'Access); + Audio_Device : Audio.WM8994_Audio_CODEC (Audio_I2C'Access); -------------------------- -- micro SD card reader -- diff --git a/boards/stm32_common/stm32f769disco/audio.adb b/boards/stm32_common/stm32f769disco/audio.adb index 8d4be7299..e8c914043 100644 --- a/boards/stm32_common/stm32f769disco/audio.adb +++ b/boards/stm32_common/stm32f769disco/audio.adb @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2016, AdaCore -- +-- Copyright (C) 2016-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -32,7 +32,6 @@ -- @author MCD Application Team -- ------------------------------------------------------------------------------ -with HAL; use HAL; with STM32; use STM32; with STM32.Device; use STM32.Device; with STM32.Board; use STM32.Board; @@ -62,23 +61,28 @@ package body Audio is -- SAI_In_Block : SAI_Block renames Block_B; procedure Set_Audio_Clock (Freq : Audio_Frequency); + procedure Initialize_Audio_Out_Pins; - procedure Initialize_SAI_Out (Freq : Audio_Frequency); + + procedure Initialize_SAI_Out (Freq : Audio_Frequency; Sink : Audio_Outputs); + + function As_Device_Volume (Input : Audio_Volume) return WM8994.Volume_Level; + -- Converts the percentage input value to the WM8994-specific range + procedure Initialize_Audio_I2C; --------------------- -- Set_Audio_Clock -- --------------------- - procedure Set_Audio_Clock (Freq : Audio_Frequency) - is + procedure Set_Audio_Clock (Freq : Audio_Frequency) is begin - -- Two groups of frequencies: the 44kHz family and the 48kHz family - -- The Actual audio frequency is calculated then with the following + -- Two groups of frequencies: the 44kHz family and the 48kHz family. + -- The actual audio frequency is calculated with the following -- formula: -- Master_Clock = 256 * FS = SAI_CK / Master_Clock_Divider -- We need to find a value of SAI_CK that allows such integer master - -- clock divider + -- clock divider. case Freq is when Audio_Freq_11kHz | Audio_Freq_22kHz | Audio_Freq_32kHz | Audio_Freq_44kHz => @@ -89,13 +93,22 @@ package body Audio is PLLI2SQ => 2, -- SAI Clk(First level) = 214.5 MHz PLLI2SDIVQ => 19); -- I2S Clk = 215.4 / 19 = 11.289 MHz - when Audio_Freq_8kHz | Audio_Freq_16kHz | - Audio_Freq_48kHz | Audio_Freq_96kHz => + when Audio_Freq_8kHz | Audio_Freq_12kHz | Audio_Freq_16kHz | + Audio_Freq_24kHz | Audio_Freq_48kHz | Audio_Freq_96kHz => Configure_SAI_I2S_Clock (Audio_SAI, PLLI2SN => 344, -- VCO Output = 344MHz PLLI2SQ => 7, -- SAI Clk(First level) = 49.142 MHz - PLLI2SDIVQ => 1); -- I2S Clk = 49.142 MHz + PLLI2SDIVQ => 1); -- I2S Clk = 49.142 MHz + + when Audio_Freq_88kHz => + -- Best rational approx of 88200*256 Hz = 22.5792 MHz from 1 MHz VCO; + -- 271/4/3 = 22.583 MHz, MCKDIV=0 -> FS = 88216 Hz (0.018% error) + Configure_SAI_I2S_Clock + (Audio_SAI, + PLLI2SN => 271, -- VCO Output = 271MHz + PLLI2SQ => 4, -- SAI Clk(First level) = 67.75 MHz + PLLI2SDIVQ => 3); -- SAI Clk = 22.583 MHz end case; end Set_Audio_Clock; @@ -103,8 +116,7 @@ package body Audio is -- Initialize_Audio_Out_Pins -- ------------------------------- - procedure Initialize_Audio_Out_Pins - is + procedure Initialize_Audio_Out_Pins is begin Enable_Clock (Audio_SAI); Enable_Clock (SAI_Pins); @@ -143,10 +155,14 @@ package body Audio is -- Initialize_SAI_Out -- ------------------------ - procedure Initialize_SAI_Out (Freq : Audio_Frequency) + procedure Initialize_SAI_Out + (Freq : Audio_Frequency; + Sink : Audio_Outputs) is + Active_Slots : SAI_Slots; begin STM32.SAI.Disable (Audio_SAI, SAI_Out_Block); + STM32.SAI.Configure_Audio_Block (Audio_SAI, SAI_Out_Block, @@ -161,6 +177,7 @@ package body Audio is Synchronization => Asynchronous_Mode, Output_Drive => Drive_Immediate, FIFO_Threshold => FIFO_1_Quarter_Full); + STM32.SAI.Configure_Block_Frame (Audio_SAI, SAI_Out_Block, @@ -169,13 +186,24 @@ package body Audio is Frame_Sync => FS_Frame_And_Channel_Identification, FS_Polarity => FS_Active_Low, FS_Offset => Before_First_Bit); + + case Sink is + when Headphone => + Active_Slots := Slot_0 or Slot_2; + when Speaker => + Active_Slots := Slot_1 or Slot_3; + when Both => + Active_Slots := Slot_0 or Slot_1 or Slot_2 or Slot_3; + end case; + STM32.SAI.Configure_Block_Slot (Audio_SAI, SAI_Out_Block, First_Bit_Offset => 0, Slot_Size => Data_Size, Number_Of_Slots => 4, - Enabled_Slots => Slot_0 or Slot_2); + Enabled_Slots => Active_Slots); + STM32.SAI.Enable (Audio_SAI, SAI_Out_Block); end Initialize_SAI_Out; @@ -183,8 +211,7 @@ package body Audio is -- Initialize_Audio_I2C -- -------------------------- - procedure Initialize_Audio_I2C - is + procedure Initialize_Audio_I2C is begin if not STM32.I2C.Is_Configured (Audio_I2C) then STM32.Setup.Setup_I2C_Master (Port => Audio_I2C, @@ -200,10 +227,12 @@ package body Audio is -- Initialize -- ---------------- - procedure Initialize_Audio_Out - (This : in out WM8994_Audio_Device; + procedure Initialize + (This : in out WM8994_Audio_CODEC; Volume : Audio_Volume; - Frequency : Audio_Frequency) + Frequency : Audio_Frequency; + Bit_Width : Audio_Bit_Width; -- must be 16 bits due to Audio_Buffer component size + Sink : Audio_Outputs) is begin STM32.SAI.Deinitialize (Audio_SAI, SAI_Out_Block); @@ -212,31 +241,36 @@ package body Audio is -- Initialize the SAI Initialize_Audio_Out_Pins; - Initialize_SAI_Out (Frequency); + Initialize_SAI_Out (Frequency, Sink); -- Initialize the I2C Port to send commands to the driver Initialize_Audio_I2C; - if This.Device.Read_ID /= WM8994.WM8994_ID then - raise Constraint_Error with "Invalid ID received from the Audio Code"; + if This.Device.Chip_ID /= WM8994.WM8994_ID then + raise Constraint_Error with "Invalid ID received from the Audio CODEC"; end if; This.Device.Reset; - This.Device.Init + This.Device.Initialize (Input => WM8994.No_Input, Output => WM8994.Auto, - Volume => UInt8 (Volume), - Frequency => - WM8994.Audio_Frequency'Enum_Val - (Audio_Frequency'Enum_Rep (Frequency))); - end Initialize_Audio_Out; + Volume => As_Device_Volume (Volume), + Frequency => WM8994.Audio_Frequency (Frequency), + Bit_Width => WM8994.Audio_Sample_Width (Bit_Width)); + + This.Sink := WM8994.Output_Device (Sink); + -- For sake of any individual calls to Set_Frequency, assuming the STM + -- code is correct that we also need to set the clocks when setting the + -- frequency + + end Initialize; ---------- -- Play -- ---------- procedure Play - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Buffer : Audio_Buffer) is begin @@ -263,7 +297,7 @@ package body Audio is -- Pause -- ----------- - procedure Pause (This : in out WM8994_Audio_Device) is + procedure Pause (This : in out WM8994_Audio_CODEC) is begin This.Device.Pause; DMA_Pause (Audio_SAI, SAI_Out_Block); @@ -273,8 +307,7 @@ package body Audio is -- Resume -- ------------ - procedure Resume (This : in out WM8994_Audio_Device) - is + procedure Resume (This : in out WM8994_Audio_CODEC) is begin This.Device.Resume; DMA_Resume (Audio_SAI, SAI_Out_Block); @@ -284,8 +317,7 @@ package body Audio is -- Stop -- ---------- - procedure Stop (This : in out WM8994_Audio_Device) - is + procedure Stop (This : in out WM8994_Audio_CODEC) is begin This.Device.Stop (WM8994.Stop_Power_Down_Sw); DMA_Stop (Audio_SAI, SAI_Out_Block); @@ -299,11 +331,11 @@ package body Audio is ---------------- procedure Set_Volume - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Volume : Audio_Volume) is begin - This.Device.Set_Volume (UInt8 (Volume)); + This.Device.Set_Volume (As_Device_Volume (Volume)); end Set_Volume; ------------------- @@ -311,15 +343,26 @@ package body Audio is ------------------- procedure Set_Frequency - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Frequency : Audio_Frequency) is - pragma Unreferenced (This); + use type WM8994.Output_Device; begin + if This.Sink = WM8994.No_Output then + raise Constraint_Error with "No prior call to Initialize"; + end if; Set_Audio_Clock (Frequency); STM32.SAI.Disable (Audio_SAI, SAI_Out_Block); - Initialize_SAI_Out (Frequency); + Initialize_SAI_Out (Frequency, Audio_Outputs (This.Sink)); STM32.SAI.Enable (Audio_SAI, SAI_Out_Block); end Set_Frequency; + ---------------------- + -- As_Device_Volume -- + ---------------------- + + function As_Device_Volume (Input : Audio_Volume) return WM8994.Volume_Level is + (if Input = 100 then WM8994.Max_Volume + else UInt16 (Input) * WM8994.Max_Volume / 100); + end Audio; diff --git a/boards/stm32_common/stm32f769disco/audio.ads b/boards/stm32_common/stm32f769disco/audio.ads index 286abddf9..a6e1159c5 100644 --- a/boards/stm32_common/stm32f769disco/audio.ads +++ b/boards/stm32_common/stm32f769disco/audio.ads @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2016, AdaCore -- +-- Copyright (C) 2016-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -32,50 +32,99 @@ -- @author MCD Application Team -- ------------------------------------------------------------------------------ -with HAL.Audio; use HAL.Audio; -with HAL.I2C; use HAL.I2C; -with Ravenscar_Time; +with HAL; use HAL; +with HAL.I2C; use HAL.I2C; +with Interfaces; use Interfaces; +with WM8994; -private with WM8994; +private with Ravenscar_Time; package Audio is - type WM8994_Audio_Device (Port : not null Any_I2C_Port) is + type WM8994_Audio_CODEC (Port : not null Any_I2C_Port) is tagged limited private; - procedure Initialize_Audio_Out - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume; - Frequency : Audio_Frequency); + type Audio_Outputs is new WM8994.Output_Device with + Static_Predicate => Audio_Outputs in + Headphone | Speaker | Both; - procedure Set_Volume - (This : in out WM8994_Audio_Device; - Volume : Audio_Volume); + type Audio_Frequency is new WM8994.Audio_Frequency; - procedure Set_Frequency - (This : in out WM8994_Audio_Device; - Frequency : Audio_Frequency); + type Audio_Bit_Width is new WM8994.Audio_Sample_Width; + + type Audio_Volume is range 0 .. 100; -- a percentage + + type Audio_Buffer is array (Natural range <>) of Integer_16 + with Component_Size => 16, Alignment => 2; + -- TODO: change the component to a signed 32-bit quantity, so that any + -- sample bit width up to 32 bits could be supported. Otherwise we can + -- only support 16-bit samples. See procedure Initialize below. + + procedure Initialize + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume; + Frequency : Audio_Frequency; + Bit_Width : Audio_Bit_Width; -- must be 16 bits due to Audio_Buffer component size + Sink : Audio_Outputs); + -- This routine initializes the hardware and configures the volume, sampling + -- frequency, output device (the sink), and the sampling bit width. This + -- routine must be called, before any others. procedure Play - (This : in out WM8994_Audio_Device; + (This : in out WM8994_Audio_CODEC; Buffer : Audio_Buffer); + -- Start playing content from the specified buffer. The effect is to tell + -- the underlying WM8994 CODEC where the buffer to be played is located, + -- and cause the CODEC to start playing the contents. + -- + -- NB: playing continues after the call returns. An additional mechanism, + -- outside this package, updates the content of the buffer while the CODEC + -- is playing it. That update/play process continues until either there is + -- no more music to be played, or Stop or Pause is called. procedure Pause - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- After calling Pause, only Resume should be called for resuming play (do + -- not call Start_Playing again). procedure Resume - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- Procedure Resume should be called only when the audio is playing or + -- paused (not stopped). procedure Stop - (This : in out WM8994_Audio_Device); + (This : in out WM8994_Audio_CODEC); + -- Stops the hardware and update/play process. Once called, you must call + -- Start_Playing again if you want to restart the output. + + procedure Set_Volume + (This : in out WM8994_Audio_CODEC; + Volume : Audio_Volume); + + procedure Set_Frequency + (This : in out WM8994_Audio_CODEC; + Frequency : Audio_Frequency); private Audio_I2C_Addr : constant I2C_Address := 16#34#; - type WM8994_Audio_Device (Port : not null Any_I2C_Port) is - tagged limited record - Device : WM8994.WM8994_Device (Port, Audio_I2C_Addr, Ravenscar_Time.Delays); + type WM8994_Audio_CODEC + (Port : not null Any_I2C_Port) + is tagged limited record + Device : WM8994.Audio_CODEC (Port, Audio_I2C_Addr, Ravenscar_Time.Delays); + Sink : WM8994.Output_Device := WM8994.No_Output; + -- The initial value of Sink is overwritten by Initialize. The value + -- No_Output will trigger a C_E if ever referenced, so it is used as a + -- check that Initialize has been called. We need the component Sink + -- itself for the sake of a clean parameter profile for Set_Frequency, + -- otherwise clients would have to pass another parameter to specify + -- the output device selection again (after having done so when calling + -- Initialize). That's because Set_Frequency needs to do enough hardware + -- re-initialization to accommodate the new frequency, but doing so + -- requires the output device selection so that the re-init can select + -- the active slots. Just activating all slots (as is done in the STM + -- C code) doesn't work (at least in the current Ada code). end record; end Audio; diff --git a/boards/stm32_common/stm32f769disco/stm32-board.ads b/boards/stm32_common/stm32f769disco/stm32-board.ads index 71883e7c8..81fce689f 100644 --- a/boards/stm32_common/stm32f769disco/stm32-board.ads +++ b/boards/stm32_common/stm32f769disco/stm32-board.ads @@ -186,7 +186,7 @@ package STM32.Board is Audio_DMA_In_Sream : DMA_Stream_Selector renames Stream_7; Audio_DMA_In_Channel : DMA_Channel_Selector renames Channel_0; - Audio_Device : Audio.WM8994_Audio_Device (Audio_I2C'Access); + Audio_Device : Audio.WM8994_Audio_CODEC (Audio_I2C'Access); ------------- -- SD Card -- diff --git a/components/src/audio/W8994/wm8994-io.adb b/components/src/audio/W8994/wm8994-io.adb new file mode 100644 index 000000000..893ffefa3 --- /dev/null +++ b/components/src/audio/W8994/wm8994-io.adb @@ -0,0 +1,81 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2026, AdaCore -- +-- -- +-- 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 of the copyright holder 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 -- +-- HOLDER 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. -- +-- -- +------------------------------------------------------------------------------ + +package body WM8994.IO is + + --------------- + -- I2C_Write -- + --------------- + + procedure I2C_Write + (This : in out Audio_CODEC; + Register : Register_Address; + Value : UInt16) + is + Status : I2C_Status; + Data : I2C_Data (1 .. 2); + begin + -- Device is MSB first + Data (1) := UInt8 (Shift_Right (Value and 16#FF00#, 8)); + Data (2) := UInt8 (Value and 16#FF#); + + This.Port.Mem_Write + (Addr => This.I2C_Addr, + Mem_Addr => UInt16 (Register), + Mem_Addr_Size => Memory_Size_16b, + Data => Data, + Status => Status); + end I2C_Write; + + -------------- + -- I2C_Read -- + -------------- + + function I2C_Read + (This : in out Audio_CODEC; + Register : Register_Address) + return UInt16 + is + Status : I2C_Status; + Data : I2C_Data (1 .. 2); + Result : UInt16; + begin + This.Port.Mem_Read + (Addr => This.I2C_Addr, + Mem_Addr => UInt16 (Register), + Mem_Addr_Size => Memory_Size_16b, + Data => Data, + Status => Status); + Result := Shift_Left (UInt16 (Data (1)), 8) or UInt16 (Data (2)); + return Result; + end I2C_Read; + +end WM8994.IO; diff --git a/components/src/audio/W8994/wm8994-io.ads b/components/src/audio/W8994/wm8994-io.ads new file mode 100644 index 000000000..5901cd273 --- /dev/null +++ b/components/src/audio/W8994/wm8994-io.ads @@ -0,0 +1,281 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2026, AdaCore -- +-- -- +-- 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 of the copyright holder 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 -- +-- HOLDER 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. -- +-- -- +------------------------------------------------------------------------------ + +package WM8994.IO is + + type Register_Address is new UInt16; + + procedure I2C_Write + (This : in out Audio_CODEC; + Register : Register_Address; + Value : UInt16); + + function I2C_Read + (This : in out Audio_CODEC; + Register : Register_Address) + return UInt16; + + WM8994_SW_Reset : constant Register_Address := 16#0000#; + WM8994_PWR_Management_1 : constant Register_Address := 16#0001#; + WM8994_PWR_Management_2 : constant Register_Address := 16#0002#; + WM8994_PWR_Management_3 : constant Register_Address := 16#0003#; + WM8994_PWR_Management_4 : constant Register_Address := 16#0004#; + WM8994_PWR_Management_5 : constant Register_Address := 16#0005#; + WM8994_PWR_Management_6 : constant Register_Address := 16#0006#; + WM8994_Input_Mixer_1 : constant Register_Address := 16#0015#; + WM8994_Left_Line_In12_Vol : constant Register_Address := 16#0018#; + WM8994_Left_Line_In34_Vol : constant Register_Address := 16#0019#; + WM8994_Right_Line_In12_Vol : constant Register_Address := 16#001A#; + WM8994_Right_Line_In34_Vol : constant Register_Address := 16#001B#; + WM8994_Left_Output_Vol : constant Register_Address := 16#001C#; + WM8994_Right_Output_Vol : constant Register_Address := 16#001D#; + WM8994_Line_Output_Vol : constant Register_Address := 16#001E#; + WM8994_Output2_Vol : constant Register_Address := 16#001F#; + WM8994_Left_OPGA_Vol : constant Register_Address := 16#0020#; + WM8994_Right_OPGA_Vol : constant Register_Address := 16#0021#; + WM8994_SPKMIXL_ATT : constant Register_Address := 16#0022#; + WM8994_SPKMIXR_ATT : constant Register_Address := 16#0023#; + WM8994_Output_Mixer : constant Register_Address := 16#0024#; + WM8994_CLASS_D : constant Register_Address := 16#0025#; + WM8994_SPK_Left_Vol : constant Register_Address := 16#0026#; + WM8994_SPK_Right_Vol : constant Register_Address := 16#0027#; + WM8994_Input_Mixer_2 : constant Register_Address := 16#0028#; + WM8994_Input_Mixer_3 : constant Register_Address := 16#0029#; + WM8994_Input_Mixer_4 : constant Register_Address := 16#002A#; + WM8994_Input_Mixer_5 : constant Register_Address := 16#002B#; + WM8994_Input_Mixer_6 : constant Register_Address := 16#002C#; + WM8994_Output_Mixer_1 : constant Register_Address := 16#002D#; + WM8994_Output_Mixer_2 : constant Register_Address := 16#002E#; + WM8994_Output_Mixer_3 : constant Register_Address := 16#002F#; + WM8994_Output_Mixer_4 : constant Register_Address := 16#0030#; + WM8994_Output_Mixer_5 : constant Register_Address := 16#0031#; + WM8994_Output_Mixer_6 : constant Register_Address := 16#0032#; + WM8994_Output2_Mixer : constant Register_Address := 16#0033#; + WM8994_Line_Mixer_1 : constant Register_Address := 16#0034#; + WM8994_Line_Mixer_2 : constant Register_Address := 16#0035#; + WM8994_Speaker_Mixer : constant Register_Address := 16#0036#; + WM8994_Add_Control : constant Register_Address := 16#0037#; + WM8994_AntiPop1 : constant Register_Address := 16#0038#; + WM8994_AntiPop2 : constant Register_Address := 16#0039#; + WM8994_MicBias : constant Register_Address := 16#003A#; + WM8994_LDO1 : constant Register_Address := 16#003B#; + WM8994_LDO2 : constant Register_Address := 16#003C#; + WM8994_Charge_Pump1 : constant Register_Address := 16#004C#; + WM8994_Charge_Pump2 : constant Register_Address := 16#004D#; + WM8994_CLASS_W : constant Register_Address := 16#0051#; + WM8994_DC_Servo1 : constant Register_Address := 16#0054#; + WM8994_DC_Servo2 : constant Register_Address := 16#0055#; + WM8994_DC_Servo_Readback : constant Register_Address := 16#0058#; + WM8994_DC_Servo_Writeval : constant Register_Address := 16#0059#; + WM8994_Analog_HP : constant Register_Address := 16#0060#; + WM8994_Chip_Revision : constant Register_Address := 16#0100#; + WM8994_Control_Interface : constant Register_Address := 16#0101#; + WM8994_WRITE_SEQ_CTRL1 : constant Register_Address := 16#0110#; + WM8994_WRITE_SEQ_CTRL2 : constant Register_Address := 16#0111#; + WM8994_AIF1_Clocking1 : constant Register_Address := 16#0200#; + WM8994_AIF1_Clocking2 : constant Register_Address := 16#0201#; + WM8994_AIF2_Clocking1 : constant Register_Address := 16#0204#; + WM8994_AIF2_Clocking2 : constant Register_Address := 16#0205#; + WM8994_Clocking1 : constant Register_Address := 16#0208#; + WM8994_Clocking2 : constant Register_Address := 16#0209#; + WM8994_AIF1_Rate : constant Register_Address := 16#0210#; + WM8994_AIF2_Rate : constant Register_Address := 16#0211#; + WM8994_Rate_Status : constant Register_Address := 16#0212#; + WM8994_FLL1_Control1 : constant Register_Address := 16#0220#; + WM8994_FLL1_Control2 : constant Register_Address := 16#0221#; + WM8994_FLL1_Control3 : constant Register_Address := 16#0222#; + WM8994_FLL1_Control4 : constant Register_Address := 16#0223#; + WM8994_FLL1_Control5 : constant Register_Address := 16#0224#; + WM8994_FLL2_Control1 : constant Register_Address := 16#0240#; + WM8994_FLL2_Control2 : constant Register_Address := 16#0241#; + WM8994_FLL2_Control3 : constant Register_Address := 16#0242#; + WM8994_FLL2_Control4 : constant Register_Address := 16#0243#; + WM8994_FLL2_Control5 : constant Register_Address := 16#0244#; + WM8994_AIF1_Control1 : constant Register_Address := 16#0300#; + WM8994_AIF1_Control2 : constant Register_Address := 16#0301#; + WM8994_AIF1_Master_Slave : constant Register_Address := 16#0302#; + WM8994_AIF1_BCLK : constant Register_Address := 16#0303#; + WM8994_AIF1_ADC_LRCLK : constant Register_Address := 16#0304#; + WM8994_AIF1_DAC_LRCLK : constant Register_Address := 16#0305#; + WM8994_AIF1_DAC_DELTA : constant Register_Address := 16#0306#; + WM8994_AIF1_ADC_DELTA : constant Register_Address := 16#0307#; + WM8994_AIF2_Control1 : constant Register_Address := 16#0310#; + WM8994_AIF2_Control2 : constant Register_Address := 16#0311#; + WM8994_AIF2_Master_Slave : constant Register_Address := 16#0312#; + WM8994_AIF2_BCLK : constant Register_Address := 16#0313#; + WM8994_AIF2_ADC_LRCLK : constant Register_Address := 16#0314#; + WM8994_AIF2_DAC_LRCLK : constant Register_Address := 16#0315#; + WM8994_AIF2_DAC_DELTA : constant Register_Address := 16#0316#; + WM8994_AIF2_ADC_DELTA : constant Register_Address := 16#0317#; + WM8994_AIF1_ADC1_Left_Vol : constant Register_Address := 16#0400#; + WM8994_AIF1_ADC1_Right_Vol : constant Register_Address := 16#0401#; + WM8994_AIF1_DAC1_Left_Vol : constant Register_Address := 16#0402#; + WM8994_AIF1_DAC1_Right_Vol : constant Register_Address := 16#0403#; + WM8994_AIF1_ADC2_Left_Vol : constant Register_Address := 16#0404#; + WM8994_AIF1_ADC2_Right_Vol : constant Register_Address := 16#0405#; + WM8994_AIF1_DAC2_Left_Vol : constant Register_Address := 16#0406#; + WM8994_AIF1_DAC2_Right_Vol : constant Register_Address := 16#0407#; + WM8994_AIF1_ADC1_Filters : constant Register_Address := 16#0410#; + WM8994_AIF1_ADC2_Filters : constant Register_Address := 16#0411#; + WM8994_AIF1_DAC1_Filter1 : constant Register_Address := 16#0420#; + WM8994_AIF1_DAC1_Filter2 : constant Register_Address := 16#0421#; + WM8994_AIF1_DAC2_Filter1 : constant Register_Address := 16#0422#; + WM8994_AIF1_DAC2_Filter2 : constant Register_Address := 16#0423#; + WM8994_AIF1_DRC1 : constant Register_Address := 16#0440#; + WM8994_AIF1_DRC1_1 : constant Register_Address := 16#0441#; + WM8994_AIF1_DRC1_2 : constant Register_Address := 16#0442#; + WM8994_AIF1_DRC1_3 : constant Register_Address := 16#0443#; + WM8994_AIF1_DRC1_4 : constant Register_Address := 16#0444#; + WM8994_AIF1_DRC2 : constant Register_Address := 16#0450#; + WM8994_AIF1_DRC2_1 : constant Register_Address := 16#0451#; + WM8994_AIF1_DRC2_2 : constant Register_Address := 16#0452#; + WM8994_AIF1_DRC2_3 : constant Register_Address := 16#0453#; + WM8994_AIF1_DRC2_4 : constant Register_Address := 16#0454#; + WM8994_AIF1_DAC1_EQG_1 : constant Register_Address := 16#0480#; + WM8994_AIF1_DAC1_EQG_2 : constant Register_Address := 16#0481#; + WM8994_AIF1_DAC1_EQG_1A : constant Register_Address := 16#0482#; + WM8994_AIF1_DAC1_EQG_1B : constant Register_Address := 16#0483#; + WM8994_AIF1_DAC1_EQG_1PG : constant Register_Address := 16#0484#; + WM8994_AIF1_DAC1_EQG_2A : constant Register_Address := 16#0485#; + WM8994_AIF1_DAC1_EQG_2B : constant Register_Address := 16#0486#; + WM8994_AIF1_DAC1_EQG_2C : constant Register_Address := 16#0487#; + WM8994_AIF1_DAC1_EQG_2PG : constant Register_Address := 16#0488#; + WM8994_AIF1_DAC1_EQG_3A : constant Register_Address := 16#0489#; + WM8994_AIF1_DAC1_EQG_3B : constant Register_Address := 16#048A#; + WM8994_AIF1_DAC1_EQG_3C : constant Register_Address := 16#048B#; + WM8994_AIF1_DAC1_EQG_3PG : constant Register_Address := 16#048C#; + WM8994_AIF1_DAC1_EQG_4A : constant Register_Address := 16#048D#; + WM8994_AIF1_DAC1_EQG_4B : constant Register_Address := 16#048E#; + WM8994_AIF1_DAC1_EQG_4C : constant Register_Address := 16#048F#; + WM8994_AIF1_DAC1_EQG_4PG : constant Register_Address := 16#0490#; + WM8994_AIF1_DAC1_EQG_5A : constant Register_Address := 16#0491#; + WM8994_AIF1_DAC1_EQG_5B : constant Register_Address := 16#0492#; + WM8994_AIF1_DAC1_EQG_5PG : constant Register_Address := 16#0493#; + WM8994_AIF1_DAC2_EQG_1 : constant Register_Address := 16#04A0#; + WM8994_AIF1_DAC2_EQG_2 : constant Register_Address := 16#04A1#; + WM8994_AIF1_DAC2_EQG_1A : constant Register_Address := 16#04A2#; + WM8994_AIF1_DAC2_EQG_1B : constant Register_Address := 16#04A3#; + WM8994_AIF1_DAC2_EQG_1PG : constant Register_Address := 16#04A4#; + WM8994_AIF1_DAC2_EQG_2A : constant Register_Address := 16#04A5#; + WM8994_AIF1_DAC2_EQG_2B : constant Register_Address := 16#04A6#; + WM8994_AIF1_DAC2_EQG_2C : constant Register_Address := 16#04A7#; + WM8994_AIF1_DAC2_EQG_2PG : constant Register_Address := 16#04A8#; + WM8994_AIF1_DAC2_EQG_3A : constant Register_Address := 16#04A9#; + WM8994_AIF1_DAC2_EQG_3B : constant Register_Address := 16#04AA#; + WM8994_AIF1_DAC2_EQG_3C : constant Register_Address := 16#04AB#; + WM8994_AIF1_DAC2_EQG_3PG : constant Register_Address := 16#04AC#; + WM8994_AIF1_DAC2_EQG_4A : constant Register_Address := 16#04AD#; + WM8994_AIF1_DAC2_EQG_4B : constant Register_Address := 16#04AE#; + WM8994_AIF1_DAC2_EQG_4C : constant Register_Address := 16#04AF#; + WM8994_AIF1_DAC2_EQG_4PG : constant Register_Address := 16#04B0#; + WM8994_AIF1_DAC2_EQG_5A : constant Register_Address := 16#04B1#; + WM8994_AIF1_DAC2_EQG_5B : constant Register_Address := 16#04B2#; + WM8994_AIF1_DAC2_EQG_5PG : constant Register_Address := 16#04B3#; + WM8994_AIF2_ADC_Left_Vol : constant Register_Address := 16#0500#; + WM8994_AIF2_ADC_Right_Vol : constant Register_Address := 16#0501#; + WM8994_AIF2_DAC_Left_Vol : constant Register_Address := 16#0502#; + WM8994_AIF2_DAC_Right_Vol : constant Register_Address := 16#0503#; + WM8994_AIF2_ADC_Filters : constant Register_Address := 16#0510#; + WM8994_AIF2_DAC_Filter_1 : constant Register_Address := 16#0520#; + WM8994_AIF2_DAC_Filter_2 : constant Register_Address := 16#0521#; + WM8994_AIF2_DRC_1 : constant Register_Address := 16#0540#; + WM8994_AIF2_DRC_2 : constant Register_Address := 16#0541#; + WM8994_AIF2_DRC_3 : constant Register_Address := 16#0542#; + WM8994_AIF2_DRC_4 : constant Register_Address := 16#0543#; + WM8994_AIF2_DRC_5 : constant Register_Address := 16#0544#; + WM8994_AIF2_EQG_1 : constant Register_Address := 16#0580#; + WM8994_AIF2_EQG_2 : constant Register_Address := 16#0581#; + WM8994_AIF2_EQG_1A : constant Register_Address := 16#0582#; + WM8994_AIF2_EQG_1B : constant Register_Address := 16#0583#; + WM8994_AIF2_EQG_1PG : constant Register_Address := 16#0584#; + WM8994_AIF2_EQG_2A : constant Register_Address := 16#0585#; + WM8994_AIF2_EQG_2B : constant Register_Address := 16#0586#; + WM8994_AIF2_EQG_2C : constant Register_Address := 16#0587#; + WM8994_AIF2_EQG_2PG : constant Register_Address := 16#0588#; + WM8994_AIF2_EQG_3A : constant Register_Address := 16#0589#; + WM8994_AIF2_EQG_3B : constant Register_Address := 16#058A#; + WM8994_AIF2_EQG_3C : constant Register_Address := 16#058B#; + WM8994_AIF2_EQG_3PG : constant Register_Address := 16#058C#; + WM8994_AIF2_EQG_4A : constant Register_Address := 16#058D#; + WM8994_AIF2_EQG_4B : constant Register_Address := 16#058E#; + WM8994_AIF2_EQG_4C : constant Register_Address := 16#058F#; + WM8994_AIF2_EQG_4PG : constant Register_Address := 16#0590#; + WM8994_AIF2_EQG_5A : constant Register_Address := 16#0591#; + WM8994_AIF2_EQG_5B : constant Register_Address := 16#0592#; + WM8994_AIF2_EQG_5PG : constant Register_Address := 16#0593#; + WM8994_DAC1_Mixer_Vol : constant Register_Address := 16#0600#; + WM8994_AIF1_DAC1_LMR : constant Register_Address := 16#0601#; + WM8994_AIF1_DAC1_RMR : constant Register_Address := 16#0602#; + WM8994_DAC2_Mixer_Vol : constant Register_Address := 16#0603#; + WM8994_AIF1_DAC2_LMR : constant Register_Address := 16#0604#; + WM8994_AIF1_DAC2_RMR : constant Register_Address := 16#0605#; + WM8994_AIF1_ADC1_LMR : constant Register_Address := 16#0606#; + WM8994_AIF1_ADC1_RMR : constant Register_Address := 16#0607#; + WM8994_AIF1_ADC2_LMR : constant Register_Address := 16#0608#; + WM8994_AIF1_ADC2_RMR : constant Register_Address := 16#0609#; + WM8994_DAC1_Left_Vol : constant Register_Address := 16#0610#; + WM8994_DAC1_Right_Vol : constant Register_Address := 16#0611#; + WM8994_DAC2_Left_Vol : constant Register_Address := 16#0612#; + WM8994_DAC2_Right_Vol : constant Register_Address := 16#0613#; + WM8994_DAC_SoftMute : constant Register_Address := 16#0614#; + WM8994_Oversampling : constant Register_Address := 16#0620#; + WM8994_Sidetone : constant Register_Address := 16#0621#; + WM8994_GPIO1 : constant Register_Address := 16#0700#; + WM8994_GPIO2 : constant Register_Address := 16#0701#; + WM8994_GPIO3 : constant Register_Address := 16#0702#; + WM8994_GPIO4 : constant Register_Address := 16#0703#; + WM8994_GPIO5 : constant Register_Address := 16#0704#; + WM8994_GPIO6 : constant Register_Address := 16#0705#; + WM8994_GPIO7 : constant Register_Address := 16#0706#; + WM8994_GPIO8 : constant Register_Address := 16#0707#; + WM8994_GPIO9 : constant Register_Address := 16#0708#; + WM8994_GPIO10 : constant Register_Address := 16#0709#; + WM8994_GPIO11 : constant Register_Address := 16#070A#; + WM8994_PULL_Control_1 : constant Register_Address := 16#0720#; + WM8994_PULL_Control_2 : constant Register_Address := 16#0721#; + WM8994_INT_Status_1 : constant Register_Address := 16#0730#; + WM8994_INT_Status_2 : constant Register_Address := 16#0731#; + WM8994_INT_Raw_Status_2 : constant Register_Address := 16#0732#; + WM8994_INT_Status1_Mask : constant Register_Address := 16#0738#; + WM8994_INT_Status2_Mask : constant Register_Address := 16#0739#; + WM8994_INT_Control : constant Register_Address := 16#0740#; + WM8994_IRQ_Debounce : constant Register_Address := 16#0748#; + WM8994_WRITE_Sequencer0 : constant Register_Address := 16#3000#; + WM8994_WRITE_Sequencer1 : constant Register_Address := 16#3001#; + WM8994_WRITE_Sequencer2 : constant Register_Address := 16#3002#; + WM8994_WRITE_Sequencer3 : constant Register_Address := 16#3003#; + WM8994_WRITE_Sequencer4 : constant Register_Address := 16#3508#; + WM8994_WRITE_Sequencer5 : constant Register_Address := 16#3509#; + WM8994_WRITE_Sequencer6 : constant Register_Address := 16#3510#; + WM8994_WRITE_Sequencer7 : constant Register_Address := 16#3511#; + WM8994_SW_Reset_Mask : constant Register_Address := 16#FFFF#; + +end WM8994.IO; diff --git a/components/src/audio/W8994/wm8994.adb b/components/src/audio/W8994/wm8994.adb index 860fe601d..b337203d2 100644 --- a/components/src/audio/W8994/wm8994.adb +++ b/components/src/audio/W8994/wm8994.adb @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2015-2016, AdaCore -- +-- Copyright (C) 2015-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -29,295 +29,164 @@ -- -- ------------------------------------------------------------------------------ -package body WM8994 is - - WM8994_CHIPID_ADDR : constant := 16#00#; - - Output_Enabled : Boolean := False; - Input_Enabled : Boolean := False; - pragma Unreferenced (Input_Enabled); - - - --------------- - -- I2C_Write -- - --------------- +with WM8994.IO; use WM8994.IO; - procedure I2C_Write (This : in out WM8994_Device; - Reg : UInt16; - Value : UInt16) - is - Status : I2C_Status; - Data : I2C_Data (1 .. 2); - Check : UInt16 with Unreferenced; - begin - -- Device is MSB first - Data (1) := UInt8 (Shift_Right (Value and 16#FF00#, 8)); - Data (2) := UInt8 (Value and 16#FF#); - - This.Port.Mem_Write - (Addr => This.I2C_Addr, - Mem_Addr => Reg, - Mem_Addr_Size => Memory_Size_16b, - Data => Data, - Status => Status); - - if Reg /= 0 then - Check := I2C_Read (This, Reg); - end if; - end I2C_Write; - - -------------- - -- I2C_Read -- - -------------- - - function I2C_Read (This : in out WM8994_Device; - Reg : UInt16) - return UInt16 - is - Status : I2C_Status; - Data : I2C_Data (1 .. 2); - Ret : UInt16; - begin - This.Port.Mem_Read - (Addr => This.I2C_Addr, - Mem_Addr => Reg, - Mem_Addr_Size => Memory_Size_16b, - Data => Data, - Status => Status); - Ret := Shift_Left (UInt16 (Data (1)), 8) or UInt16 (Data (2)); +package body WM8994 is - return Ret; - end I2C_Read; + WM8994_CHIPID_ADDR : constant := 16#00#; + + -- The following four procedures encapsulate the analog output and input + -- enable sequences for Initialize. They are local to the package body and + -- must not be called outside of Initialize: each sequence depends on the + -- shared Power_Mgnt_Reg_1 accumulator being correct on entry, and on the + -- digital path (clocking, DAC routing) having already been configured by + -- Set_Output_Device and Set_Frequency before any of them are called. + + procedure Enable_Speaker_Output + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16); + + procedure Enable_Headphone_Output + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16; + Include_Speaker : Boolean); + -- Include_Speaker must be True when both outputs are being enabled + -- simultaneously (Output = Both), so that the PWR_Management_3 write + -- also preserves the speaker mixer bits set by Enable_Speaker_Output. + + procedure Enable_Microphone_Input + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16); + -- Must be called after Enable_Speaker_Output / Enable_Headphone_Output + -- so that Power_Mgnt_Reg_1 already contains any output power bits; the + -- microphone bias bits are OR-ed in and written as a single update. + + procedure Enable_Line_Input + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16); + -- Must be called after Enable_Speaker_Output / Enable_Headphone_Output + -- so that Power_Mgnt_Reg_1 already contains any output power bits; the + -- bias enable bits are OR-ed in and written as a single update. - ---------- - -- Init -- - ---------- + ---------------- + -- Initialize -- + ---------------- - procedure Init - (This : in out WM8994_Device; + procedure Initialize + (This : in out Audio_CODEC; Input : Input_Device; Output : Output_Device; - Volume : UInt8; - Frequency : Audio_Frequency) + Volume : Volume_Level; + Frequency : Audio_Frequency; + Bit_Width : Audio_Sample_Width) is Power_Mgnt_Reg_1 : UInt16 := 0; - begin - -- WM8994 Errata work-arounds + -- WM8994 Errata work-arounds. See + -- https://github.com/STMicroelectronics/stm32-wm8994/blob/main/wm8994.c + -- These registers are not documented, and the effects of these writes + -- are not documented anywhere either. Nobody seems to have written it + -- down anywhere. I2C_Write (This, 16#102#, 16#0003#); I2C_Write (This, 16#817#, 16#0000#); I2C_Write (This, 16#102#, 16#0000#); -- Enable VMID soft restart, Start-up Bias current enabled - I2C_Write (This, 16#39#, 16#006C#); + I2C_Write (This, WM8994_AntiPop2, 16#006C#); -- Enable BIAS generator, Enable VMID - I2C_Write (This, 16#01#, 16#0003#); + I2C_Write (This, WM8994_PWR_Management_1, 16#0003#); This.Time.Delay_Milliseconds (50); - Output_Enabled := Output /= No_Output; - Input_Enabled := Input /= No_Input; + This.Current_Output := Output; + This.Input_Enabled := Input /= No_Input; - This.Set_Output_Mode (Output); + This.Set_Output_Device (Output); case Input is when No_Input => null; when Microphone => - -- Enable AIF1ADC2 (Left), Enable AIF1ADC2 (Right) - -- Enable DMICDAT2 (Left), Enable DMICDAT2 (Right) - -- Enable Left ADC, Enable Right ADC - I2C_Write (This, 16#04#, 16#0C30#); - -- Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC2 Left/Right - -- Timeslot 1 - I2C_Write (This, 16#450#, 16#00DB#); - -- Disable IN1L, IN1R, IN2L, IN2R, Enable Thermal sensor & - -- shutdown - I2C_Write (This, 16#02#, 16#6000#); - -- Enable the DMIC2(Left) to AIF1 Timeslot 1 (Left) mixer path - I2C_Write (This, 16#608#, 16#0002#); - -- Enable the DMIC2(Right) to AIF1 Timeslot 1 (Right) mixer path - I2C_Write (This, 16#609#, 16#0002#); - -- GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC2 - -- signal detect - I2C_Write (This, 16#700#, 16#000E#); + -- Digital path configuration for DMIC2 via AIF1 Timeslot 1; + -- analog power for microphone bias is applied later, after any + -- output power bits have been accumulated into Power_Mgnt_Reg_1. + I2C_Write (This, WM8994_PWR_Management_4, 16#0C30#); + I2C_Write (This, WM8994_AIF1_DRC2, 16#00DB#); + I2C_Write (This, WM8994_PWR_Management_4, 16#6000#); + I2C_Write (This, WM8994_AIF1_ADC2_LMR, 16#0002#); + I2C_Write (This, WM8994_AIF1_ADC2_RMR, 16#0002#); + I2C_Write (This, WM8994_GPIO1, 16#000E#); when Input_Line => - -- Enable AIF1ADC1 (Left), Enable AIF1ADC1 (Right) - -- Enable Left ADC, Enable Right ADC - I2C_Write (This, 16#04#, 16#0303#); - -- Enable AIF1 DRC1 Signal Detect & DRC in AIF1ADC1 Left/Right - -- Timeslot 0 - I2C_Write (This, 16#440#, 16#00DB#); - -- Enable IN1L and IN1R, Disable IN2L and IN2R, Enable Thermal - -- sensor & shutdown - I2C_Write (This, 16#02#, 16#6350#); - -- Enable the ADCL(Left) to AIF1 Timeslot 0 (Left) mixer path - I2C_Write (This, 16#606#, 16#0002#); - -- Enable the ADCR(Right) to AIF1 Timeslot 0 (Right) mixer path - I2C_Write (This, 16#607#, 16#0002#); - -- GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC1 - -- signal detect - I2C_Write (This, 16#700#, 16#000D#); + -- Digital path configuration for IN1L/IN1R via AIF1 Timeslot 0; + -- analog power and PGA configuration is applied later, after any + -- output power bits have been accumulated into Power_Mgnt_Reg_1. + I2C_Write (This, WM8994_PWR_Management_4, 16#0303#); + I2C_Write (This, WM8994_AIF1_DRC1, 16#00DB#); + I2C_Write (This, WM8994_PWR_Management_4, 16#6350#); + I2C_Write (This, WM8994_AIF1_ADC1_LMR, 16#0002#); + I2C_Write (This, WM8994_AIF1_ADC1_RMR, 16#0002#); + I2C_Write (This, WM8994_GPIO1, 16#000D#); end case; This.Set_Frequency (Frequency); - -- AIF1 Word Length = 16-bits, AIF1 Format = I2S (Default Register - -- Value) - I2C_Write (This, 16#300#, 16#4010#); + This.Set_Sample_Width (Bit_Width); + -- slave mode - I2C_Write (This, 16#302#, 16#0000#); + I2C_Write (This, WM8994_AIF1_Master_Slave, 16#0000#); -- Enable the DSP processing clock for AIF1, Enable the core clock - I2C_Write (This, 16#208#, 16#000A#); + I2C_Write (This, WM8994_Clocking1, 16#000A#); -- Enable AIF1 Clock, AIF1 Clock Source = MCLK1 pin - I2C_Write (This, 16#200#, 16#0001#); - - if Output /= No_Output then - -- Analog Output Configuration - - -- Enable SPKRVOL PGA, Enable SPKMIXR, Enable SPKLVOL PGA, Enable - -- SPKMIXL - I2C_Write (This, 16#03#, 16#0300#); - - -- Left Speaker Mixer Volume = 0dB - I2C_Write (This, 16#22#, 16#0000#); - - -- Speaker output mode = Class D, Right Speaker Mixer Volume = 0dB - -- ((16#23#, 16#0100#) = class AB) - I2C_Write (This, 16#23#, 16#0000#); - - -- Unmute DAC2 (Left) to Left Speaker Mixer (SPKMIXL) path, - -- Unmute DAC2 (Right) to Right Speaker Mixer (SPKMIXR) path - I2C_Write (This, 16#36#, 16#0300#); - - -- Enable bias generator, Enable VMID, Enable SPKOUTL, Enable SPKOUTR - I2C_Write (This, 16#01#, 16#3003#); - - -- Headphone/Speaker Enable - - -- Enable Class W, Class W Envelope Tracking = AIF1 Timeslot 0 - I2C_Write (This, 16#51#, 16#0001#); - - -- Enable bias generator, Enable VMID, Enable HPOUT1 (Left) and - -- Enable HPOUT1 (Right) input stages idem for Speaker - Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0303# or 16#3003#; - I2C_Write (This, 16#01#, Power_Mgnt_Reg_1); - - -- Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate stages - I2C_Write (This, 16#60#, 16#0022#); - - -- Enable Charge Pump - I2C_Write (This, 16#4C#, 16#9F25#); - - -- Add Delay - This.Time.Delay_Milliseconds (15); - - -- Select DAC1 (Left) to Left Headphone Output PGA (HPOUT1LVOL) path - I2C_Write (This, 16#2D#, 16#0001#); - - -- Select DAC1 (Right) to Right Headphone Output PGA (HPOUT1RVOL) - -- path. - I2C_Write (This, 16#2E#, 16#0001#); - - -- Enable Left Output Mixer (MIXOUTL), Enable Right Output Mixer - -- (MIXOUTR) idem for SPKOUTL and SPKOUTR. - I2C_Write (This, 16#03#, 16#0030# or 16#0300#); - - -- Enable DC Servo and trigger start-up mode on left and right - -- channels. - I2C_Write (This, 16#54#, 16#0033#); - - -- Add Delay - This.Time.Delay_Milliseconds (250); - - -- Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate and output - -- stages. Remove clamps. - I2C_Write (This, 16#60#, 16#00EE#); - - -- Unmutes - - -- Unmute DAC 1 (Left) - I2C_Write (This, 16#610#, 16#00C0#); - - -- Unmute DAC 1 (Right) - I2C_Write (This, 16#611#, 16#00C0#); - - -- Unmute the AIF1 Timeslot 0 DAC path - I2C_Write (This, 16#420#, 16#0000#); - - -- Unmute DAC 2 (Left) - I2C_Write (This, 16#612#, 16#00C0#); - - -- Unmute DAC 2 (Right) - I2C_Write (This, 16#613#, 16#00C0#); + I2C_Write (This, WM8994_AIF1_Clocking1, 16#0001#); + + -- Analog output power sequencing: speaker first (if active), then + -- headphone (if active). Each call ORs its power bits into + -- Power_Mgnt_Reg_1 before writing PWR_Management_1, so that both + -- sets of bits are present if both outputs are enabled. + if Output in Speaker | Both then + Enable_Speaker_Output (This, Power_Mgnt_Reg_1); + end if; - -- Unmute the AIF1 Timeslot 1 DAC2 path - I2C_Write (This, 16#422#, 16#0000#); + if Output in Headphone | Auto | Both then + Enable_Headphone_Output (This, Power_Mgnt_Reg_1, + Include_Speaker => Output = Both); + end if; - -- Volume Control + if Output /= No_Output then This.Set_Volume (Volume); end if; - if Input /= No_Input then - if Input = Microphone then - -- Enable Microphone bias 1 generator, Enable VMID - Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0013#; - I2C_Write (This, 16#01#, Power_Mgnt_Reg_1); - - -- ADC oversample enable - I2C_Write (This, 16#620#, 16#0002#); - - -- AIF ADC2 HPF enable, HPF cut = voice mode 1 fc=127Hz at fs=8kHz - I2C_Write (This, 16#411#, 16#3800#); - - elsif Input = Input_Line then - -- Enable normal bias generator, Enable VMID - Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0003#; - I2C_Write (This, 16#01#, Power_Mgnt_Reg_1); - - -- Disable mute on IN1L, IN1L Volume = +0dB - I2C_Write (This, 16#18#, 16#000B#); - - -- Disable mute on IN1R, IN1R Volume = +0dB - I2C_Write (This, 16#1A#, 16#000B#); - - -- Disable mute on IN1L_TO_MIXINL, Gain = +0dB - I2C_Write (This, 16#29#, 16#0025#); - - -- Disable mute on IN1R_TO_MIXINL, Gain = +0dB - I2C_Write (This, 16#2A#, 16#0025#); - - -- IN1LN_TO_IN1L, IN1LP_TO_VMID, IN1RN_TO_IN1R, IN1RP_TO_VMID - I2C_Write (This, 16#28#, 16#0011#); - - -- AIF ADC1 HPF enable, HPF cut = hifi mode fc=4Hz at fs=48kHz - I2C_Write (This, 16#410#, 16#1800#); - end if; + -- Analog input power sequencing: output power bits are already + -- accumulated in Power_Mgnt_Reg_1, so the input enable writes a + -- single combined value to PWR_Management_1. + case Input is + when No_Input => null; + when Microphone => Enable_Microphone_Input (This, Power_Mgnt_Reg_1); + when Input_Line => Enable_Line_Input (This, Power_Mgnt_Reg_1); + end case; - -- Volume Control + if Input /= No_Input then This.Set_Volume (Volume); end if; - end Init; + end Initialize; ------------- - -- Read_ID -- + -- Chip_ID -- ------------- - function Read_ID (This : in out WM8994_Device) return UInt16 is - begin - return This.I2C_Read (WM8994_CHIPID_ADDR); - end Read_ID; + function Chip_ID (This : in out Audio_CODEC) return UInt16 is + (I2C_Read (This, WM8994_CHIPID_ADDR)); ---------- -- Play -- ---------- - procedure Play (This : in out WM8994_Device) is + procedure Play (This : in out Audio_CODEC) is begin This.Set_Mute (Mute_Off); end Play; @@ -326,20 +195,16 @@ package body WM8994 is -- Pause -- ----------- - procedure Pause (This : in out WM8994_Device) is + procedure Pause (This : in out Audio_CODEC) is begin - -- Pause the audio playing This.Set_Mute (Mute_On); - - -- CODEC in powersave mode - I2C_Write (This, 16#02#, 16#01#); end Pause; ------------ -- Resume -- ------------ - procedure Resume (This : in out WM8994_Device) is + procedure Resume (This : in out Audio_CODEC) is begin This.Set_Mute (Mute_Off); end Resume; @@ -348,35 +213,33 @@ package body WM8994 is -- Stop -- ---------- - procedure Stop (This : in out WM8994_Device; Cmd : Stop_Mode) is + procedure Stop + (This : in out Audio_CODEC; + Mode : Stop_Mode) + is begin - if Output_Enabled then - -- Mute the output first + if This.Current_Output /= No_Output then This.Set_Mute (Mute_On); - if Cmd = Stop_Power_Down_Sw then + if Mode = Stop_Power_Down_Sw then return; end if; - Output_Enabled := False; + This.Current_Output := No_Output; -- Mute the AIF1 Timeslot 0 DAC1 path - I2C_Write (This, 16#420#, 16#0200#); - + I2C_Write (This, WM8994_AIF1_DAC1_Filter1, 16#0200#); -- Mute the AIF1 Timeslot 1 DAC2 path - I2C_Write (This, 16#422#, 16#0200#); - + I2C_Write (This, WM8994_AIF1_DAC2_Filter1, 16#0200#); -- Disable DAC1L_TO_HPOUT1L - I2C_Write (This, 16#2D#, 16#0000#); - + I2C_Write (This, WM8994_Output_Mixer_1, 16#0000#); -- Disable DAC1R_TO_HPOUT1R - I2C_Write (This, 16#2E#, 16#0000#); - + I2C_Write (This, WM8994_Output_Mixer_2, 16#0000#); -- Disable DAC1 and DAC2 - I2C_Write (This, 16#05#, 16#0000#); + I2C_Write (This, WM8994_PWR_Management_5, 16#0000#); - -- Reset Codec by writing in 0x0000 address register - I2C_Write (This, 16#0000#, 16#0000#); + -- Reset Codec + I2C_Write (This, WM8994_SW_Reset, 16#0000#); end if; end Stop; @@ -384,31 +247,29 @@ package body WM8994 is -- Set_Volume -- ---------------- - procedure Set_Volume (This : in out WM8994_Device; Volume : Volume_Level) + procedure Set_Volume + (This : in out Audio_CODEC; + Volume : Volume_Level) is - -- Actual Volume in range 0 .. 16#3F# - Converted_Volume : constant UInt16 := - (if Volume = 100 then 63 - else UInt16 (Volume) * 63 / 100); - begin if Volume = 0 then - -- Mute the codec This.Set_Mute (Mute_On); else This.Set_Mute (Mute_Off); - -- Left Headphone Volume - I2C_Write (This, 16#1C#, Converted_Volume or 16#140#); - - -- Right Headphone volume - I2C_Write (This, 16#1D#, Converted_Volume or 16#140#); - - -- Left Speaker volume - I2C_Write (This, 16#26#, Converted_Volume or 16#140#); + if This.Current_Output in Headphone | Auto | Both then + -- Left Headphone Volume + I2C_Write (This, WM8994_Left_Output_Vol, Volume or 16#140#); + -- Right Headphone Volume + I2C_Write (This, WM8994_Right_Output_Vol, Volume or 16#140#); + end if; - -- Right Speaker volume - I2C_Write (This, 16#27#, Converted_Volume or 16#140#); + if This.Current_Output in Speaker | Both then + -- Left Speaker volume + I2C_Write (This, WM8994_SPK_Left_Vol, Volume or 16#140#); + -- Right Speaker volume + I2C_Write (This, WM8994_SPK_Right_Vol, Volume or 16#140#); + end if; end if; end Set_Volume; @@ -416,111 +277,153 @@ package body WM8994 is -- Set_Mute -- -------------- - procedure Set_Mute (This : in out WM8994_Device; Cmd : Mute) is + procedure Set_Mute + (This : in out Audio_CODEC; + Mode : Mute_Mode) + is begin - if Output_Enabled then - case Cmd is + if This.Current_Output /= No_Output then + case Mode is when Mute_On => - -- Soft Mute the AIF1 Timeslot 0 DAC1 path L&R - I2C_Write (This, 16#420#, 16#0200#); - -- Soft Mute the AIF1 Timeslot 1 DAC2 path L&R - I2C_Write (This, 16#422#, 16#0200#); + if This.Current_Output in Headphone | Auto | Both then + -- Soft Mute the AIF1 Timeslot 0 DAC1 path L&R + I2C_Write (This, WM8994_AIF1_DAC1_Filter1, 16#0200#); + end if; + if This.Current_Output in Speaker | Both then + -- Soft Mute the AIF1 Timeslot 1 DAC2 path L&R + I2C_Write (This, WM8994_AIF1_DAC2_Filter1, 16#0200#); + end if; when Mute_Off => - -- Unmute the AIF1 Timeslot 0 DAC1 path L&R - I2C_Write (This, 16#420#, 16#0000#); - -- Unmute the AIF1 Timeslot 1 DAC2 path L&R - I2C_Write (This, 16#422#, 16#0000#); + if This.Current_Output in Headphone | Auto | Both then + -- Unmute the AIF1 Timeslot 0 DAC1 path L&R + I2C_Write (This, WM8994_AIF1_DAC1_Filter1, 16#0000#); + end if; + if This.Current_Output in Speaker | Both then + -- Unmute the AIF1 Timeslot 1 DAC2 path L&R + I2C_Write (This, WM8994_AIF1_DAC2_Filter1, 16#0000#); + end if; end case; end if; end Set_Mute; - --------------------- - -- Set_Output_Mode -- - --------------------- + ----------------------- + -- Set_Output_Device -- + ----------------------- - procedure Set_Output_Mode (This : in out WM8994_Device; - Device : Output_Device) + procedure Set_Output_Device + (This : in out Audio_CODEC; + Device : Output_Device) is begin case Device is when No_Output => -- Disable DAC1 (left), DAC1 (Right) - I2C_Write (This, 16#05#, 16#0000#); + I2C_Write (This, WM8994_PWR_Management_5, 16#0000#); -- Mute the AIF1 Timeslot 0 DAC1 path - I2C_Write (This, 16#420#, 16#0200#); + I2C_Write (This, WM8994_AIF1_DAC1_Filter1, 16#0200#); -- Mute the AIF1 Timeslot 1 DAC2 path - I2C_Write (This, 16#422#, 16#0200#); + I2C_Write (This, WM8994_AIF1_DAC2_Filter1, 16#0200#); when Speaker => -- Enable DAC1 (left), DAC1 (Right) - I2C_Write (This, 16#05#, 16#0C0C#); - -- Enable the AIF1 Timeslot 0 (Left) to DAC1 (left) mixer path - I2C_Write (This, 16#601#, 16#0000#); - -- Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path - I2C_Write (This, 16#602#, 16#0000#); - -- Disable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path - I2C_Write (This, 16#604#, 16#0002#); - -- Disable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path - I2C_Write (This, 16#605#, 16#0002#); + I2C_Write (This, WM8994_PWR_Management_5, 16#0C0C#); + -- Disable the AIF1 Timeslot 0 (Left) to DAC1 (left) mixer path + I2C_Write (This, WM8994_AIF1_DAC1_LMR, 16#0000#); + -- Disable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path + I2C_Write (This, WM8994_AIF1_DAC1_RMR, 16#0000#); + -- Enable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path + I2C_Write (This, WM8994_AIF1_DAC2_LMR, 16#0002#); + -- Enable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path + I2C_Write (This, WM8994_AIF1_DAC2_RMR, 16#0002#); when Headphone | Auto => -- Disable DAC1 (left), DAC1 (Right) -- Enable DAC2 (left), DAC2 (Right) - I2C_Write (This, 16#05#, 16#0303#); + I2C_Write (This, WM8994_PWR_Management_5, 16#0303#); -- Enable the AIF1 Timeslot 0 (Left) to DAC1 (left) mixer path - I2C_Write (This, 16#601#, 16#0001#); + I2C_Write (This, WM8994_AIF1_DAC1_LMR, 16#0001#); -- Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path - I2C_Write (This, 16#602#, 16#0001#); + I2C_Write (This, WM8994_AIF1_DAC1_RMR, 16#0001#); -- Disable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path - I2C_Write (This, 16#604#, 16#0000#); + I2C_Write (This, WM8994_AIF1_DAC2_LMR, 16#0000#); -- Disable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path - I2C_Write (This, 16#605#, 16#0000#); + I2C_Write (This, WM8994_AIF1_DAC2_RMR, 16#0000#); when Both => -- Enable DAC1 (left), DAC1 (Right) -- Enable DAC2 (left), DAC2 (Right) - I2C_Write (This, 16#05#, 16#0303# or 16#0C0C#); + I2C_Write (This, WM8994_PWR_Management_5, 16#0303# or 16#0C0C#); -- Enable the AIF1 Timeslot 0 (Left) to DAC1 (left) mixer path - I2C_Write (This, 16#601#, 16#0001#); + I2C_Write (This, WM8994_AIF1_DAC1_LMR, 16#0001#); -- Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path - I2C_Write (This, 16#602#, 16#0001#); + I2C_Write (This, WM8994_AIF1_DAC1_RMR, 16#0001#); -- Enable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path - I2C_Write (This, 16#604#, 16#0002#); + I2C_Write (This, WM8994_AIF1_DAC2_LMR, 16#0002#); -- Enable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path - I2C_Write (This, 16#605#, 16#0002#); + I2C_Write (This, WM8994_AIF1_DAC2_RMR, 16#0002#); end case; - end Set_Output_Mode; + end Set_Output_Device; ------------------- -- Set_Frequency -- ------------------- - procedure Set_Frequency (This : in out WM8994_Device; - Freq : Audio_Frequency) + procedure Set_Frequency + (This : in out Audio_CODEC; + Freq : Audio_Frequency) is begin + -- In the following, the values written set both the AIF1_SR [3:0] bits + -- and the AIF1CLK_RATE [3:0] bits (the latter to set the ratio). The + -- ratio is always 256, which is indicated by the bit pattern 2#0011#, + -- so the lower digits in the values is always 3 in hex. + -- + -- See the table labeled "Register 0210h AIF1 Rate" pages 285 and 286 of + -- WM8994_Rev4.6 from Cirrus Logic case Freq is when Audio_Freq_8kHz => -- AIF1 Sample Rate = 8 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0003#); - when Audio_Freq_16kHz => - -- AIF1 Sample Rate = 16 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0033#); - when Audio_Freq_48kHz => - -- AIF1 Sample Rate = 48 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0083#); - when Audio_Freq_96kHz => - -- AIF1 Sample Rate = 96 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#00A3#); + I2C_Write (This, WM8994_AIF1_Rate, 16#0003#); + when Audio_Freq_11kHz => -- AIF1 Sample Rate = 11.025 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0013#); + I2C_Write (This, WM8994_AIF1_Rate, 16#0013#); + + when Audio_Freq_12kHz => + -- AIF1 Sample Rate = 12 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0023#); + + when Audio_Freq_16kHz => + -- AIF1 Sample Rate = 16 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0033#); + when Audio_Freq_22kHz => -- AIF1 Sample Rate = 22.050 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0043#); + I2C_Write (This, WM8994_AIF1_Rate, 16#0043#); + + when Audio_Freq_24kHz => + -- AIF1 Sample Rate = 24 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0053#); + + when Audio_Freq_32kHz => + -- AIF1 Sample Rate = 32 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0063#); + when Audio_Freq_44kHz => -- AIF1 Sample Rate = 44.1 (kHz), ratio=256 - I2C_Write (This, 16#210#, 16#0073#); + I2C_Write (This, WM8994_AIF1_Rate, 16#0073#); + + when Audio_Freq_48kHz => + -- AIF1 Sample Rate = 48 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0083#); + + when Audio_Freq_88kHz => + -- AIF1 Sample Rate = 88.2 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#0093#); + + when Audio_Freq_96kHz => + -- AIF1 Sample Rate = 96 (kHz), ratio=256 + I2C_Write (This, WM8994_AIF1_Rate, 16#00A3#); end case; end Set_Frequency; @@ -528,11 +431,217 @@ package body WM8994 is -- Reset -- ----------- - procedure Reset (This : in out WM8994_Device) is + procedure Reset (This : in out Audio_CODEC) is begin - I2C_Write (This, 16#0000#, 16#0000#); - Output_Enabled := False; - Input_Enabled := False; + I2C_Write (This, WM8994_SW_Reset, 16#0000#); + This.Current_Output := No_Output; + This.Input_Enabled := False; end Reset; + ---------------------------- + -- Enable_Speaker_Output -- + ---------------------------- + + procedure Enable_Speaker_Output + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16) + is + begin + -- Enable SPKRVOL PGA, Enable SPKMIXR, Enable SPKLVOL PGA, Enable SPKMIXL + I2C_Write (This, WM8994_PWR_Management_3, 16#0300#); + + -- Left Speaker Mixer Volume = 0dB + I2C_Write (This, WM8994_SPKMIXL_ATT, 16#0000#); + + -- Speaker output mode = Class D, Right Speaker Mixer Volume = 0dB + -- ((16#23#, 16#0100#) = class AB) + I2C_Write (This, WM8994_SPKMIXR_ATT, 16#0000#); + + -- Unmute DAC2 (Left) to Left Speaker Mixer (SPKMIXL) path, + -- Unmute DAC2 (Right) to Right Speaker Mixer (SPKMIXR) path + I2C_Write (This, WM8994_Speaker_Mixer, 16#0300#); + + -- Enable bias generator, Enable VMID, Enable SPKOUTL, Enable SPKOUTR + Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#3003#; + I2C_Write (This, WM8994_PWR_Management_1, Power_Mgnt_Reg_1); + + -- Unmute DAC 2 (Left) + I2C_Write (This, WM8994_DAC2_Left_Vol, 16#00C0#); + + -- Unmute DAC 2 (Right) + I2C_Write (This, WM8994_DAC2_Right_Vol, 16#00C0#); + + -- Unmute the AIF1 Timeslot 1 DAC2 path + I2C_Write (This, WM8994_AIF1_DAC2_Filter1, 16#0000#); + end Enable_Speaker_Output; + + ----------------------------- + -- Enable_Headphone_Output -- + ----------------------------- + + procedure Enable_Headphone_Output + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16; + Include_Speaker : Boolean) + is + begin + -- Enable Class W, Class W Envelope Tracking = AIF1 Timeslot 0 + I2C_Write (This, WM8994_CLASS_W, 16#0001#); + + -- Enable bias generator, Enable VMID, Enable HPOUT1 (Left) and + -- Enable HPOUT1 (Right) input stages + Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0303#; + I2C_Write (This, WM8994_PWR_Management_1, Power_Mgnt_Reg_1); + + -- Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate stages + I2C_Write (This, WM8994_Analog_HP, 16#0022#); + + -- Enable Charge Pump + I2C_Write (This, WM8994_Charge_Pump1, 16#9F25#); + + This.Time.Delay_Milliseconds (15); + + -- Select DAC1 (Left) to Left Headphone Output PGA (HPOUT1LVOL) path + I2C_Write (This, WM8994_Output_Mixer_1, 16#0001#); + + -- Select DAC1 (Right) to Right Headphone Output PGA (HPOUT1RVOL) path + I2C_Write (This, WM8994_Output_Mixer_2, 16#0001#); + + -- Enable Left Output Mixer (MIXOUTL), Enable Right Output Mixer + -- (MIXOUTR); when speaker is also active, preserve its mixer bits too + I2C_Write (This, WM8994_PWR_Management_3, + (if Include_Speaker then 16#0030# or 16#0300# + else 16#0030#)); + + -- Enable DC Servo and trigger start-up mode on left and right channels + I2C_Write (This, WM8994_DC_Servo1, 16#0033#); + + -- Add Delay: DC Servo requires up to 250ms to converge + This.Time.Delay_Milliseconds (250); + + -- Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate and output + -- stages. Remove clamps. + I2C_Write (This, WM8994_Analog_HP, 16#00EE#); + + -- Unmute DAC 1 (Left) + I2C_Write (This, WM8994_DAC1_Left_Vol, 16#00C0#); + + -- Unmute DAC 1 (Right) + I2C_Write (This, WM8994_DAC1_Right_Vol, 16#00C0#); + + -- Unmute the AIF1 Timeslot 0 DAC1 path + I2C_Write (This, WM8994_AIF1_DAC1_Filter1, 16#0000#); + end Enable_Headphone_Output; + + ------------------------------ + -- Enable_Microphone_Input -- + ------------------------------ + + procedure Enable_Microphone_Input + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16) + is + begin + -- Enable AIF1ADC2 (Left), Enable AIF1ADC2 (Right) + -- Enable DMICDAT2 (Left), Enable DMICDAT2 (Right) + -- Enable Left ADC, Enable Right ADC + I2C_Write (This, WM8994_PWR_Management_4, 16#0C30#); + -- Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC2 Left/Right + -- Timeslot 1 + I2C_Write (This, WM8994_AIF1_DRC2, 16#00DB#); + -- Disable IN1L, IN1R, IN2L, IN2R, Enable Thermal sensor & shutdown + I2C_Write (This, WM8994_PWR_Management_4, 16#6000#); + -- Enable the DMIC2(Left) to AIF1 Timeslot 1 (Left) mixer path + I2C_Write (This, WM8994_AIF1_ADC2_LMR, 16#0002#); + -- Enable the DMIC2(Right) to AIF1 Timeslot 1 (Right) mixer path + I2C_Write (This, WM8994_AIF1_ADC2_RMR, 16#0002#); + -- GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC2 + -- signal detect + I2C_Write (This, WM8994_GPIO1, 16#000E#); + + -- Enable Microphone bias 1 generator, Enable VMID + Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0013#; + I2C_Write (This, WM8994_PWR_Management_1, Power_Mgnt_Reg_1); + + -- ADC oversample enable + I2C_Write (This, WM8994_Oversampling, 16#0002#); + + -- AIF ADC2 HPF enable, HPF cut = voice mode 1 fc=127Hz at fs=8kHz + I2C_Write (This, WM8994_AIF1_ADC2_Filters, 16#3800#); + end Enable_Microphone_Input; + + ------------------------- + -- Enable_Line_Input -- + ------------------------- + + procedure Enable_Line_Input + (This : in out Audio_CODEC; + Power_Mgnt_Reg_1 : in out UInt16) + is + begin + -- Enable AIF1ADC1 (Left), Enable AIF1ADC1 (Right) + -- Enable Left ADC, Enable Right ADC + I2C_Write (This, WM8994_PWR_Management_4, 16#0303#); + -- Enable AIF1 DRC1 Signal Detect & DRC in AIF1ADC1 Left/Right + -- Timeslot 0 + I2C_Write (This, WM8994_AIF1_DRC1, 16#00DB#); + -- Enable IN1L and IN1R, Disable IN2L and IN2R, Enable Thermal + -- sensor & shutdown + I2C_Write (This, WM8994_PWR_Management_4, 16#6350#); + -- Enable the ADCL(Left) to AIF1 Timeslot 0 (Left) mixer path + I2C_Write (This, WM8994_AIF1_ADC1_LMR, 16#0002#); + -- Enable the ADCR(Right) to AIF1 Timeslot 0 (Right) mixer path + I2C_Write (This, WM8994_AIF1_ADC1_RMR, 16#0002#); + -- GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC1 + -- signal detect + I2C_Write (This, WM8994_GPIO1, 16#000D#); + + -- Enable normal bias generator, Enable VMID + Power_Mgnt_Reg_1 := Power_Mgnt_Reg_1 or 16#0003#; + I2C_Write (This, WM8994_PWR_Management_1, Power_Mgnt_Reg_1); + + -- Disable mute on IN1L, IN1L Volume = +0dB + I2C_Write (This, WM8994_Left_Line_In12_Vol, 16#000B#); + + -- Disable mute on IN1R, IN1R Volume = +0dB + I2C_Write (This, WM8994_Right_Line_In12_Vol, 16#000B#); + + -- Disable mute on IN1L_TO_MIXINL, Gain = +0dB + I2C_Write (This, WM8994_Input_Mixer_3, 16#0025#); + + -- Disable mute on IN1R_TO_MIXINL, Gain = +0dB + I2C_Write (This, WM8994_Input_Mixer_4, 16#0025#); + + -- IN1LN_TO_IN1L, IN1LP_TO_VMID, IN1RN_TO_IN1R, IN1RP_TO_VMID + I2C_Write (This, WM8994_Input_Mixer_2, 16#0011#); + + -- AIF ADC1 HPF enable, HPF cut = hifi mode fc=4Hz at fs=48kHz + I2C_Write (This, WM8994_AIF1_ADC1_Filters, 16#1800#); + end Enable_Line_Input; + + --------------------- + -- Set_Sample_Width -- + --------------------- + + procedure Set_Sample_Width + (This : in out Audio_CODEC; + Bit_Width : Audio_Sample_Width) + is + -- AIF1_WL[1:0] occupies bits 6:5 of AIF1_Control1 (Register 0300h). + -- All other bits (format, source select, boost, invert) are preserved + -- via read-modify-write. + -- Per WM8994_Rev4.6, p.177, Register R768 (0300h), bits 6:5: + -- 00 = 16 bits, 01 = 20 bits, 10 = 24 bits, 11 = 32 bits. + AIF1_WL_Mask : constant UInt16 := not 16#0060#; + WL_Bits : constant array (Audio_Sample_Width) of UInt16 := + (Audio_16_Bits => 16#0000#, + Audio_20_Bits => 16#0020#, + Audio_24_Bits => 16#0040#, + Audio_32_Bits => 16#0060#); + Current : constant UInt16 := I2C_Read (This, WM8994_AIF1_Control1); + begin + I2C_Write (This, WM8994_AIF1_Control1, + (Current and AIF1_WL_Mask) or WL_Bits (Bit_Width)); + end Set_Sample_Width; + end WM8994; diff --git a/components/src/audio/W8994/wm8994.ads b/components/src/audio/W8994/wm8994.ads index fe55d45b5..8da1a8cec 100644 --- a/components/src/audio/W8994/wm8994.ads +++ b/components/src/audio/W8994/wm8994.ads @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------ -- -- --- Copyright (C) 2015-2016, AdaCore -- +-- Copyright (C) 2015-2026, AdaCore -- -- -- -- Redistribution and use in source and binary forms, with or without -- -- modification, are permitted provided that the following conditions are -- @@ -29,7 +29,8 @@ -- -- ------------------------------------------------------------------------------ --- Driver for the WM8994 CODEC +-- This package provides a simple driver for the WM8994 CODEC. It does not +-- provide a full definition of the WM8994's functionality. with HAL; use HAL; with HAL.I2C; use HAL.I2C; @@ -37,90 +38,139 @@ with HAL.Time; package WM8994 is + type Audio_CODEC + (Port : not null Any_I2C_Port; + I2C_Addr : UInt10; + Time : not null HAL.Time.Any_Delays) + is tagged limited private; + type Output_Device is (No_Output, Speaker, Headphone, Both, Auto); + type Input_Device is (No_Input, Microphone, Input_Line); - WM8994_ID : constant := 16#8994#; - type Audio_Frequency is (Audio_Freq_8kHz, Audio_Freq_11kHz, + Audio_Freq_12kHz, Audio_Freq_16kHz, Audio_Freq_22kHz, + Audio_Freq_24kHz, + Audio_Freq_32kHz, Audio_Freq_44kHz, Audio_Freq_48kHz, + Audio_Freq_88kHz, Audio_Freq_96kHz) with Size => 32; + -- Sample rates from 8kHz to 96kHz are all supported, per Datasheet + -- WM8994_Rev4.6, pages 43 and 93, from Cirrus Logic. See all Tables 37 + -- and 41. + -- + -- Note that 88.2kHz and 96kHz modes are supported for AIF1 input (DAC + -- playback) only. + for Audio_Frequency use (Audio_Freq_8kHz => 8_000, Audio_Freq_11kHz => 11_025, + Audio_Freq_12kHz => 12_000, Audio_Freq_16kHz => 16_000, Audio_Freq_22kHz => 22_050, + Audio_Freq_24kHz => 24_000, + Audio_Freq_32kHz => 32_000, Audio_Freq_44kHz => 44_100, Audio_Freq_48kHz => 48_000, + Audio_Freq_88kHz => 88_200, Audio_Freq_96kHz => 96_000); - type Mute is + type Audio_Sample_Width is + (Audio_16_Bits, + Audio_20_Bits, + Audio_24_Bits, + Audio_32_Bits); + + Max_Volume : constant := 16#3F#; + + subtype Volume_Level is UInt16 range 0 .. Max_Volume; + + procedure Initialize + (This : in out Audio_CODEC; + Input : Input_Device; + Output : Output_Device; + Volume : Volume_Level; + Frequency : Audio_Frequency; + Bit_Width : Audio_Sample_Width); + + type Mute_Mode is (Mute_On, Mute_Off); type Stop_Mode is (Stop_Power_Down_Sw, + -- Stop_Power_Down_Sw only mutes the audio codec, it does not alter + -- hardware settings. When resuming from this mode the codec keeps the + -- previous initialization so there is no need to re-initialize the + -- codec registers. Stop_Power_Down_Hw); - -- Stop_Power_Down_Sw: - -- only mutes the audio codec. When resuming from this mode the codec - -- keeps the previous initialization (no need to re-Initialize the codec - -- registers). - -- Stop_Power_Down_Hw: - -- Physically power down the codec. When resuming from this mode, the codec - -- is set to default configuration (user should re-Initialize the codec in - -- order to play again the audio stream). - - subtype Volume_Level is UInt8 range 0 .. 100; - - type WM8994_Device - (Port : not null Any_I2C_Port; - I2C_Addr : UInt10; - Time : not null HAL.Time.Any_Delays) - is tagged limited private; + -- Stop_Power_Down_Hw physically powers down the codec hardware. When + -- resuming from this mode, the codec is set to default configuration + -- so users should re-initialize the codec. - procedure Init (This : in out WM8994_Device; - Input : Input_Device; - Output : Output_Device; - Volume : UInt8; - Frequency : Audio_Frequency); - - function Read_ID (This : in out WM8994_Device) return UInt16; - procedure Play (This : in out WM8994_Device); - procedure Pause (This : in out WM8994_Device); - procedure Resume (This : in out WM8994_Device); - procedure Stop (This : in out WM8994_Device; Cmd : Stop_Mode); - procedure Set_Volume (This : in out WM8994_Device; Volume : Volume_Level); - procedure Set_Mute (This : in out WM8994_Device; Cmd : Mute); - procedure Set_Output_Mode (This : in out WM8994_Device; - Device : Output_Device); - procedure Set_Frequency (This : in out WM8994_Device; - Freq : Audio_Frequency); - procedure Reset (This : in out WM8994_Device); + WM8994_ID : constant := 16#8994#; + -- The expected value returned by function Chip_Id + + function Chip_ID (This : in out Audio_CODEC) return UInt16; + + procedure Play (This : in out Audio_CODEC); + + procedure Pause (This : in out Audio_CODEC); + + procedure Resume (This : in out Audio_CODEC); + + procedure Stop + (This : in out Audio_CODEC; + Mode : Stop_Mode); + + procedure Set_Mute + (This : in out Audio_CODEC; + Mode : Mute_Mode); + + procedure Reset (This : in out Audio_CODEC); + + -- The following procedures allow changes during execution but are not + -- required because procedure Initialize sets their values. + + procedure Set_Volume + (This : in out Audio_CODEC; + Volume : Volume_Level); + + procedure Set_Output_Device + (This : in out Audio_CODEC; + Device : Output_Device); + + procedure Set_Frequency + (This : in out Audio_CODEC; + Freq : Audio_Frequency); + + procedure Set_Sample_Width + (This : in out Audio_CODEC; + Bit_Width : Audio_Sample_Width); private - type WM8994_Device (Port : not null Any_I2C_Port; - I2C_Addr : UInt10; - Time : not null HAL.Time.Any_Delays) is tagged limited null record; - - procedure I2C_Write (This : in out WM8994_Device; - Reg : UInt16; - Value : UInt16); - function I2C_Read (This : in out WM8994_Device; - Reg : UInt16) - return UInt16; + + type Audio_CODEC + (Port : not null Any_I2C_Port; + I2C_Addr : UInt10; + Time : not null HAL.Time.Any_Delays) + is tagged limited record + Current_Output : Output_Device := No_Output; + Input_Enabled : Boolean := False; + end record; end WM8994;