diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 439c8266..4bd15d0d 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -206,6 +206,7 @@ menu "Blockstream Jade" default 12 if BOARD_TYPE_M5_CORES3 default 21 if BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY default 42 if BOARD_TYPE_JADE_V2_ANY + default 5 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 10 if BOARD_TYPE_TTGO_TWATCHS3 default -1 config I2C_SCL @@ -215,6 +216,7 @@ menu "Blockstream Jade" default 14 if BOARD_TYPE_JADE_V1_1 default 22 if BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY default 41 if BOARD_TYPE_JADE_V2_ANY + default 6 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default -1 config I2C_MASTER_CLK int "I2C Master Clock Speed" @@ -464,6 +466,14 @@ menu "Blockstream Jade" default 16 if BOARD_TYPE_WS_TOUCH_LCD2 default 23 + config CAMERA_SCCB_EXISTING_I2C_PORT + int + default 0 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default -1 + help + I2C port already initialized by another subsystem for the + camera SCCB bus. Set to -1 for the camera to initialize it. + config CAMERA_RESET int "Camera RESET" default 3 if BOARD_TYPE_JADE_V2 diff --git a/main/camera.c b/main/camera.c index 5835dddb..5bc3d8c8 100644 --- a/main/camera.c +++ b/main/camera.c @@ -263,8 +263,9 @@ static void jade_camera_init(void) .pin_pclk = CONFIG_CAMERA_PCLK, .pin_vsync = CONFIG_CAMERA_VSYNC, .pin_href = CONFIG_CAMERA_HREF, - .pin_sscb_sda = CONFIG_CAMERA_SDA, - .pin_sscb_scl = CONFIG_CAMERA_SCL, + .pin_sccb_sda = CONFIG_CAMERA_SCCB_EXISTING_I2C_PORT >= 0 ? -1 : CONFIG_CAMERA_SDA, + .pin_sccb_scl = CONFIG_CAMERA_SCCB_EXISTING_I2C_PORT >= 0 ? -1 : CONFIG_CAMERA_SCL, + .sccb_i2c_port = CONFIG_CAMERA_SCCB_EXISTING_I2C_PORT, .pin_reset = CONFIG_CAMERA_RESET, .pin_pwdn = CONFIG_CAMERA_PWDN, diff --git a/main/power.c b/main/power.c index 1dd7624b..5eefa466 100644 --- a/main/power.c +++ b/main/power.c @@ -27,6 +27,9 @@ #include "power/ip5306.inc" #elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) #include "power/wslcdtouch2.inc" +#elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA) +// T-Display S3 Pro Camera has an SY6970 charger/power-path PMU. +#include "power/tdisplays3procamera.inc" #elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3) // T-Display S3 can read battery level but lacks hardware to read charging status #include "power/tdisplays3.inc" diff --git a/main/power/tdisplays3procamera.inc b/main/power/tdisplays3procamera.inc new file mode 100644 index 00000000..ccb60e54 --- /dev/null +++ b/main/power/tdisplays3procamera.inc @@ -0,0 +1,198 @@ +// T-Display S3 Pro Camera implementation using its SY6970 charger/power-path PMU. +// +// The PMU shares GPIO5/GPIO6 with the camera SCCB bus. It uses I2C0 because +// esp32-camera is configured to use this already-initialised port in camera.c. +// + +#include "power/i2c.inc" +#include + +#define SY6970_I2C_ADDRESS 0x6A + +#define SY6970_REG_ADC_CONTROL 0x02 +#define SY6970_REG_POWER_ON_CONFIG 0x03 +#define SY6970_REG_CHARGE_TIMER_CONTROL 0x07 +#define SY6970_REG_SYSTEM_STATUS 0x0B +#define SY6970_REG_BATTERY_VOLTAGE 0x0E + +#define SY6970_ADC_ENABLE 0x80 +#define SY6970_ADC_CONTINUOUS 0x40 +#define SY6970_OTG_ENABLE 0x20 +#define SY6970_WATCHDOG_TIMER_MASK 0x30 + +#define SY6970_VBUS_STATUS_MASK 0xE0 +#define SY6970_VBUS_STATUS_SHIFT 5 +#define SY6970_VBUS_STATUS_NONE 0 +#define SY6970_VBUS_STATUS_OTG 7 + +#define SY6970_CHARGE_STATUS_MASK 0x18 +#define SY6970_CHARGE_STATUS_SHIFT 3 +#define SY6970_CHARGE_STATUS_PRECHARGE 1 +#define SY6970_CHARGE_STATUS_FAST 2 + +#define SY6970_ADC_VALUE_MASK 0x7F +#define SY6970_BATTERY_VOLTAGE_BASE_MV 2304 +#define SY6970_BATTERY_VOLTAGE_STEP_MV 20 + +static i2c_master_dev_handle_t sy6970 = NULL; + +static esp_err_t sy6970_read_register(const uint8_t register_address, uint8_t* const value) +{ + JADE_ASSERT(value); + + return _power_master_read_slave(sy6970, register_address, value, sizeof(*value)); +} + +static esp_err_t sy6970_write_register(const uint8_t register_address, const uint8_t value) +{ + return _power_write_command(sy6970, register_address, value); +} + +static esp_err_t sy6970_read_register_locked(const uint8_t register_address, uint8_t* const value) +{ + JADE_ASSERT(i2c_mutex); + + JADE_SEMAPHORE_TAKE(i2c_mutex); + const esp_err_t ret = sy6970_read_register(register_address, value); + JADE_SEMAPHORE_GIVE(i2c_mutex); + + if (ret != ESP_OK) { + JADE_LOGE("SY6970 read 0x%02x failed: %d", register_address, ret); + } + return ret; +} + +static esp_err_t sy6970_update_bits(const uint8_t register_address, const uint8_t clear_mask, const uint8_t set_mask) +{ + JADE_ASSERT(i2c_mutex); + + JADE_SEMAPHORE_TAKE(i2c_mutex); + + uint8_t value; + esp_err_t ret = sy6970_read_register(register_address, &value); + if (ret == ESP_OK) { + value = (value & ~clear_mask) | set_mask; + ret = sy6970_write_register(register_address, value); + } + + JADE_SEMAPHORE_GIVE(i2c_mutex); + + if (ret != ESP_OK) { + JADE_LOGE("SY6970 update 0x%02x failed: %d", register_address, ret); + } + return ret; +} + +static esp_err_t sy6970_get_vbus_status(uint8_t* const vbus_status) +{ + JADE_ASSERT(vbus_status); + + uint8_t status; + const esp_err_t ret = sy6970_read_register_locked(SY6970_REG_SYSTEM_STATUS, &status); + if (ret == ESP_OK) { + *vbus_status = (status & SY6970_VBUS_STATUS_MASK) >> SY6970_VBUS_STATUS_SHIFT; + } + return ret; +} + +esp_err_t power_init(void) +{ + if (CONFIG_I2C_SDA < 0 || CONFIG_I2C_SCL < 0) { + JADE_LOGE("Invalid SY6970 I2C pins: %d/%d", CONFIG_I2C_SDA, CONFIG_I2C_SCL); + return ESP_ERR_INVALID_ARG; + } + + I2C_CHECK_RET(_power_i2c_init()); + I2C_CHECK_RET(_power_i2c_attach_device(SY6970_I2C_ADDRESS, &sy6970)); + + // The SY6970 does not expose battery ADC values until conversion is + // enabled. Preserve all factory charging settings. + I2C_CHECK_RET(sy6970_update_bits(SY6970_REG_ADC_CONTROL, 0, SY6970_ADC_ENABLE | SY6970_ADC_CONTINUOUS)); + I2C_CHECK_RET(sy6970_update_bits(SY6970_REG_CHARGE_TIMER_CONTROL, SY6970_WATCHDOG_TIMER_MASK, 0)); + + return ESP_OK; +} + +esp_err_t power_shutdown(void) +{ + esp_deep_sleep_start(); + return ESP_OK; +} + +esp_err_t power_screen_on(void) { return ESP_OK; } +esp_err_t power_backlight_on(const uint8_t brightness) { return ESP_OK; } +esp_err_t power_backlight_off(void) { return ESP_OK; } +esp_err_t power_camera_on(void) { return ESP_OK; } +esp_err_t power_camera_off(void) { return ESP_OK; } + +uint16_t power_get_vbat(void) +{ + uint8_t value; + if (sy6970_read_register_locked(SY6970_REG_BATTERY_VOLTAGE, &value) != ESP_OK) { + return 0; + } + return SY6970_BATTERY_VOLTAGE_BASE_MV + ((value & SY6970_ADC_VALUE_MASK) * SY6970_BATTERY_VOLTAGE_STEP_MV); +} + +uint8_t power_get_battery_status(void) +{ + const uint16_t vbat = power_get_vbat(); + if (vbat > 4000) { + return 5; + } else if (vbat > 3800) { + return 4; + } else if (vbat > 3600) { + return 3; + } else if (vbat > 3400) { + return 2; + } else if (vbat > 3200) { + return 1; + } + return 0; +} + +bool power_get_battery_charging(void) +{ + uint8_t status; + if (sy6970_read_register_locked(SY6970_REG_SYSTEM_STATUS, &status) != ESP_OK) { + return false; + } + + const uint8_t charge_status = (status & SY6970_CHARGE_STATUS_MASK) >> SY6970_CHARGE_STATUS_SHIFT; + return charge_status == SY6970_CHARGE_STATUS_PRECHARGE || charge_status == SY6970_CHARGE_STATUS_FAST; +} + +uint16_t power_get_ibat_charge(void) { return 0; } +uint16_t power_get_ibat_discharge(void) { return 0; } +uint16_t power_get_vusb(void) { return 0; } +uint16_t power_get_iusb(void) { return 0; } +uint16_t power_get_temp(void) { return 0; } + +void disable_usb_host(void) +{ + (void)sy6970_update_bits(SY6970_REG_POWER_ON_CONFIG, SY6970_OTG_ENABLE, 0); +} + +void enable_usb_host(void) +{ + uint8_t vbus_status; + if (sy6970_get_vbus_status(&vbus_status) != ESP_OK) { + return; + } + if (vbus_status != SY6970_VBUS_STATUS_NONE && vbus_status != SY6970_VBUS_STATUS_OTG) { + JADE_LOGW("Cannot enable USB host while VBUS is connected (status %u)", vbus_status); + return; + } + (void)sy6970_update_bits(SY6970_REG_POWER_ON_CONFIG, 0, SY6970_OTG_ENABLE); +} + +bool usb_is_powered(void) +{ + uint8_t vbus_status; + if (sy6970_get_vbus_status(&vbus_status) != ESP_OK) { + // A false negative could allow USB-host mode while external power is + // still connected, so fail closed when the PMU cannot be read. + return true; + } + return vbus_status != SY6970_VBUS_STATUS_NONE && vbus_status != SY6970_VBUS_STATUS_OTG; +}