diff --git a/src/machine/machine_esp32c6_adc.go b/src/machine/machine_esp32c6_adc.go index 295144d67f..95c8666c0b 100644 --- a/src/machine/machine_esp32c6_adc.go +++ b/src/machine/machine_esp32c6_adc.go @@ -21,16 +21,27 @@ const ( atten11dB = 3 ) -// InitADC initialises the APB_SARADC peripheral on ESP32-C6. +const ( + c6PwDetConfigReg = uintptr(0x600A0810) // see soc/esp32c6/register/soc/reg_base.h + c6PwDetSarPowerForceBit = uint32(1 << 24) // bits are defined in hal/esp32c6/include/hal/sar_ctrl_ll.h + c6PwDetSarPowerControlBit = uint32(1 << 23) +) + +var c6PwDetConfReg = (*volatile.Register32)(unsafe.Pointer(c6AnaConfigReg)) + +// InitADC initialises the APB_SARADC and Modem/ADC peripheral on ESP32-C6. // On C6 the clock/reset gating moved to PCR (not SYSTEM as on C3), and the // SARADC CLKM divider configuration also lives in PCR. func InitADC() { // Reset and enable the SARADC bus clock via PCR. + esp.PCR.SetSARADC_CONF_SARADC_REG_CLK_EN(1) + esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_EN(1) esp.PCR.SetSARADC_CONF_SARADC_RST_EN(1) - esp.PCR.SetSARADC_CONF_SARADC_CLK_EN(1) esp.PCR.SetSARADC_CONF_SARADC_RST_EN(0) + esp.PCR.SetSARADC_CONF_SARADC_REG_RST_EN(1) + esp.PCR.SetSARADC_CONF_SARADC_REG_RST_EN(0) - // Select clock source 2 (PLL_F80M), divider = 1, no fractional. + // Select clock source 2 (XTAL), divider = 1, no fractional. esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_SEL(2) esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_NUM(1) esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_B(0) @@ -43,6 +54,20 @@ func InitADC() { esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8) esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100) + modemClockModuleEnableForADC() + + // Enable REG_I2C: Enter regi2c reset mode + esp.PMU.SetRF_PWC_PERIF_I2C_RSTB(0) + // Enable REGI2C for SAR_ADC and TSENS + esp.PMU.SetRF_PWC_XPD_PERIF_I2C(1) + // Release regi2c reset mode, enter work mode + esp.PMU.SetRF_PWC_PERIF_I2C_RSTB(1) + + // Enable PWDET see hal at: sar_ctrl_ll_set_power_mode_from_pwdet(SAR_CTRL_LL_POWER_ON); + c6PwDetCfg := (*volatile.Register32)(unsafe.Pointer(c6PwDetConfReg)) + c6PwDetCfg.SetBits(c6PwDetSarPowerForceBit) + c6PwDetCfg.SetBits(c6PwDetSarPowerControlBit) + adcSelfCalibrate() } @@ -62,17 +87,31 @@ func (a ADC) Get() uint16 { if a.Pin > 6 { return 0 } - esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + + // Clear stale DONE esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) - esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + + // Disable oneshot conversion trigger for ADC unit + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin)) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + + // Select ADC1 one-time mode esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) + + // Trigger one-time conversion + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + // No delay needed here because adc_ctrl_clk is fast (>= APB_CLK_FREQ/8). + // ESP-IDF evaluates this condition and calls esp_rom_delay_us(0). esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 { } raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + return uint16(raw&0xfff) << 4 } @@ -390,3 +429,118 @@ func c6ApplyADC1Code(reg regI2C, code uint32) { reg.setCalibrationParam(0, code) reg.calibrationFinish(0) } + +// ── Modem Clock for ADC ────────────────────────────────────────────────────── + +// Enable the clock for the shared ADC and Front-End (FE) controller logic. +// ESP-IDF initializes this during ADC setup with: +// modem_clock_module_enable(PERIPH_MODEM_ADC_COMMON_FE_MODULE); +// (see esp_hw_support/port/esp32c6/sar_periph_ctrl.c). +func modemClockModuleEnableForADC() { + initModemClocks() + + // Enable Modem Clk - ADC ( => see modem_clock_device_enable(ctx, 1)) + esp.MODEM_SYSCON.SetCLK_CONF1_CLK_FE_APB_EN(1) + esp.MODEM_SYSCON.SetCLK_CONF1_CLK_FE_80M_EN(1) +} + +type c6ModemClockDomain int + +const ( + modemClockDomainModemAPB c6ModemClockDomain = iota // see hal/include/hal/modem_clock_types.h + modemClockDomainModemPeriph + modemClockDomainWiFi + modemClockDomainBT + modemClockDomainModemFE + modemClockDomainIEEE802154 + modemClockDomainLPAPB + modemClockDomainI2CMaster + modemClockDomainCoex + modemClockDomainWiFiPwr + modemClockDomainMax +) + +const ( + pmuHpIcgModemCodeSleep = 0 // see esp_hw_support/include/esp_private/esp_pmu.h + pmuHpIcgModemCodeModem = 1 + pmuHpIcgModemCodeActive = 2 +) + +// The ICG code's bit 0, 1 and 2 indicates the ICG state +// of pmu SLEEP, MODEM and ACTIVE mode respectively +const ( + icgNogatingActive = 1 << pmuHpIcgModemCodeActive // see esp_hw_support/modem_clock.c + icgNogatingSleep = 1 << pmuHpIcgModemCodeSleep + icgNogatingModem = 1 << pmuHpIcgModemCodeModem +) + +// Set default ICG (Internal Clock Gating) bitmaps for modem modules (ADC, WiFi, BT, 802.15.4). +// Ported from modem_clock_module_icg_map_init_all() +// TODO: Consider moving this code into a global ESP32-C6 peripheral setup. +func initModemClocks() { + for domain := modemClockDomainModemAPB; domain < modemClockDomainMax; domain++ { + bitmap := getInitialGatingMode(domain) + modemClockSetClockDomainICGBitmap(domain, bitmap) + } +} + +// getInitialGatingMode returns the baseline gating configuration for a given modem clock domain. +// Replaces ESP-IDF's static DRAM array `initial_gating_mode` with a switch statement to avoid +// allocating DRAM in embedded environments. +// See: esp_hw_support/modem_clock.c, void modem_clock_module_icg_map_init_all() +func getInitialGatingMode(domain c6ModemClockDomain) uint32 { + switch domain { + case modemClockDomainModemAPB: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_MODEM_APB_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainModemPeriph: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_MODEM_PERI_ST_MAP() | icgNogatingActive + case modemClockDomainWiFi: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_WIFI_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainBT: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_BT_ST_MAP() | icgNogatingActive + case modemClockDomainModemFE: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_FE_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainIEEE802154: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_ZB_ST_MAP() | icgNogatingActive + case modemClockDomainLPAPB: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_LP_APB_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainI2CMaster: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_I2C_MST_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainCoex: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_COEX_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainWiFiPwr: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_WIFIPWR_ST_MAP() | icgNogatingActive | icgNogatingModem + } + return 0 +} + +// see hal/esp32c6/modem_clock_hal.c +// see soc/esp32c6/include/modem/modem_syscon_struct.h for SysconDev +// see hal/esp32c6/include/hal/modem_lpcon_ll.h for LPConDev +// see hal/esp32c6/include/hal/modem_syscon_ll.h for definition of modem_syscon_ll_get_modem_apb_icg_bitmap, ... +func modemClockSetClockDomainICGBitmap(domain c6ModemClockDomain, bitmap uint32) { + switch domain { + case modemClockDomainModemAPB: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_MODEM_APB_ST_MAP(bitmap) + case modemClockDomainModemPeriph: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_MODEM_PERI_ST_MAP(bitmap) + case modemClockDomainWiFi: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_WIFI_ST_MAP(bitmap) + case modemClockDomainBT: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_BT_ST_MAP(bitmap) + case modemClockDomainModemFE: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_FE_ST_MAP(bitmap) + case modemClockDomainIEEE802154: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_ZB_ST_MAP(bitmap) + case modemClockDomainLPAPB: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_LP_APB_ST_MAP(bitmap) + case modemClockDomainI2CMaster: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_I2C_MST_ST_MAP(bitmap) + case modemClockDomainCoex: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_COEX_ST_MAP(bitmap) + case modemClockDomainWiFiPwr: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_WIFIPWR_ST_MAP(bitmap) + } +} + +// ── Modem Clock for ADC END ──────────────────────────────────────────────────