@@ -266,44 +266,110 @@ bool I2sDma::init() {
266266
267267void I2sDma::configure_i2s_timing () {
268268 auto *dev = i2s_dev_;
269- uint32_t freq = static_cast <uint32_t >(config_.output_clock_speed );
270269
271270 // Sample rate configuration
272271 dev->sample_rate_conf .val = 0 ;
273272 dev->sample_rate_conf .rx_bits_mod = 16 ; // 16-bit parallel
274273 dev->sample_rate_conf .tx_bits_mod = 16 ;
275274
276275#if defined(CONFIG_IDF_TARGET_ESP32S2)
277- // ESP32-S2: Use PLL_160M
276+ // ESP32-S2: PLL_160M clock source
277+ // Output Frequency = 160MHz / clkm_div_num / (tx_bck_div_num * 2)
278+ // Reference: ESP32-S2 TRM v1.5, Section 12.5 (I2S Clock)
279+ // Constraints: clkm_div_num >= 2, tx_bck_div_num >= 2 (TRM Section 12.6)
278280 dev->clkm_conf .clk_sel = 2 ; // PLL_160M_CLK
279281 dev->clkm_conf .clkm_div_a = 1 ;
280282 dev->clkm_conf .clkm_div_b = 0 ;
281283
282- // Output Frequency = (160MHz / clkm_div_num) / (tx_bck_div_num*2)
283- unsigned int div_num = (freq > 8000000 ) ? 2 : 4 ; // 20MHz or 10MHz
284- dev->clkm_conf .clkm_div_num = div_num;
284+ unsigned int clkm_div;
285+ unsigned int actual_freq;
286+ switch (config_.output_clock_speed ) {
287+ case Hub75ClockSpeed::HZ_32M:
288+ // 32MHz not achievable on ESP32-S2 (max 20MHz), falling back
289+ ESP_LOGW (TAG, " 32MHz not achievable on ESP32-S2 (max 20MHz), falling back to 20MHz" );
290+ [[fallthrough]];
291+ case Hub75ClockSpeed::HZ_20M:
292+ clkm_div = 2 ; // 160/2/4 = 20MHz
293+ actual_freq = 20 ;
294+ break ;
295+ case Hub75ClockSpeed::HZ_16M:
296+ // 16MHz not achievable exactly with integer dividers, falling back to 10MHz
297+ ESP_LOGW (TAG, " 16MHz not achievable on ESP32-S2, falling back to 10MHz" );
298+ [[fallthrough]];
299+ case Hub75ClockSpeed::HZ_10M:
300+ clkm_div = 4 ; // 160/4/4 = 10MHz
301+ actual_freq = 10 ;
302+ break ;
303+ case Hub75ClockSpeed::HZ_8M:
304+ clkm_div = 5 ; // 160/5/4 = 8MHz
305+ actual_freq = 8 ;
306+ break ;
307+ default :
308+ __builtin_unreachable ();
309+ }
310+
311+ dev->clkm_conf .clkm_div_num = clkm_div;
285312 dev->clkm_conf .clk_en = 1 ;
286313
287- // BCK divider (must be >= 2 per TRM)
314+ // BCK divider (must be >= 2 per TRM Section 12.6 )
288315 dev->sample_rate_conf .rx_bck_div_num = 2 ;
289316 dev->sample_rate_conf .tx_bck_div_num = 2 ;
290317
291- ESP_LOGI (TAG, " ESP32-S2 I2S clock: 160MHz / %d / 4 = %d MHz" , div_num, 160 / div_num / 4 );
318+ ESP_LOGI (TAG, " ESP32-S2 I2S clock: 160MHz / %u / 4 = %u MHz" , clkm_div, actual_freq);
319+
292320#else
293- // ESP32: Use PLL_D2 (80MHz)
294- dev->clkm_conf .clka_en = 0 ; // Use PLL_D2_CLK (80MHz)
295- dev->clkm_conf .clkm_div_a = 1 ; // Denominator
296- dev->clkm_conf .clkm_div_b = 0 ; // Numerator
321+ // ESP32: PLL_D2_CLK clock source (80MHz)
322+ // Output Frequency = 80MHz / clkm_div_num / (tx_bck_div_num * 2)
323+ // Reference: ESP32 TRM v5.3, Section 12.5 (I2S Clock)
324+ // Constraints: clkm_div_num >= 2, tx_bck_div_num >= 2 (TRM Section 12.6)
325+ //
326+ // NOTE: Maximum achievable frequency is 10MHz with minimum dividers (2, 2).
327+ // Higher frequencies (16/20MHz) would require clkm_div_num < 2 which violates
328+ // TRM constraints. The TRM states: "I2S_CLKM_DIV_NUM: Integral I2S clock
329+ // divider value. fI2S = fCLK / I2S_CLKM_DIV_NUM (I2S_CLKM_DIV_NUM >= 2)"
330+ dev->clkm_conf .clka_en = 0 ; // PLL_D2_CLK (80MHz)
331+ dev->clkm_conf .clkm_div_a = 1 ;
332+ dev->clkm_conf .clkm_div_b = 0 ;
333+
334+ unsigned int clkm_div;
335+ unsigned int actual_freq;
336+ switch (config_.output_clock_speed ) {
337+ case Hub75ClockSpeed::HZ_32M:
338+ ESP_LOGW (TAG, " 32MHz not achievable on ESP32 (max 10MHz), falling back to 10MHz" );
339+ clkm_div = 2 ; // 80/2/4 = 10MHz
340+ actual_freq = 10 ;
341+ break ;
342+ case Hub75ClockSpeed::HZ_20M:
343+ ESP_LOGW (TAG, " 20MHz not achievable on ESP32 (max 10MHz), falling back to 10MHz" );
344+ clkm_div = 2 ; // 80/2/4 = 10MHz
345+ actual_freq = 10 ;
346+ break ;
347+ case Hub75ClockSpeed::HZ_16M:
348+ ESP_LOGW (TAG, " 16MHz not achievable on ESP32 (max 10MHz), falling back to 10MHz" );
349+ clkm_div = 2 ; // 80/2/4 = 10MHz
350+ actual_freq = 10 ;
351+ break ;
352+ case Hub75ClockSpeed::HZ_10M:
353+ clkm_div = 2 ; // 80/2/4 = 10MHz
354+ actual_freq = 10 ;
355+ break ;
356+ case Hub75ClockSpeed::HZ_8M:
357+ // 8MHz not achievable exactly, closest is 5MHz
358+ ESP_LOGW (TAG, " 8MHz not achievable on ESP32, falling back to 5MHz" );
359+ clkm_div = 4 ; // 80/4/4 = 5MHz
360+ actual_freq = 5 ;
361+ break ;
362+ default :
363+ __builtin_unreachable ();
364+ }
297365
298- // Calculate divider: 80MHz / clkm_div_num / tx_bck_div_num
299- unsigned int div_num = (freq > 8000000 ) ? 2 : 4 ; // 20MHz or 10MHz
300- dev->clkm_conf .clkm_div_num = div_num;
366+ dev->clkm_conf .clkm_div_num = clkm_div;
301367
302- // BCK divider (must be >= 2 per TRM)
368+ // BCK divider (must be >= 2 per TRM Section 12.6 )
303369 dev->sample_rate_conf .tx_bck_div_num = 2 ;
304370 dev->sample_rate_conf .rx_bck_div_num = 2 ;
305371
306- ESP_LOGI (TAG, " ESP32 I2S clock: 80MHz / %d / 4 = %d MHz" , div_num, 80 / div_num / 4 );
372+ ESP_LOGI (TAG, " ESP32 I2S clock: 80MHz / %u / 4 = %u MHz" , clkm_div, actual_freq );
307373#endif
308374}
309375
0 commit comments