From 962afee7fef7cc6655f91610f53334b9429b5792 Mon Sep 17 00:00:00 2001 From: cepetr Date: Mon, 18 Nov 2024 09:08:25 +0100 Subject: [PATCH] feat(core): introduce stwlc38 driver [no changelog] --- .../models/T3W1/boards/trezor_t3w1_revA.h | 2 + core/embed/sys/powerctl/stwlc38/stwlc38.c | 430 ++++++++++++++++++ core/embed/sys/powerctl/stwlc38/stwlc38.h | 73 +++ .../embed/sys/powerctl/stwlc38/stwlc38_defs.h | 47 ++ .../models/T3W1/trezor_t3w1_revA.py | 5 +- 5 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 core/embed/sys/powerctl/stwlc38/stwlc38.c create mode 100644 core/embed/sys/powerctl/stwlc38/stwlc38.h create mode 100644 core/embed/sys/powerctl/stwlc38/stwlc38_defs.h diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h index f34f248aca..2d4be7a488 100644 --- a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h +++ b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h @@ -19,6 +19,8 @@ #define NPM1300_I2C_INSTANCE 0 +#define STWLC38_I2C_INSTANCE 1 + #define I2C_COUNT 4 #define I2C_INSTANCE_0 I2C1 diff --git a/core/embed/sys/powerctl/stwlc38/stwlc38.c b/core/embed/sys/powerctl/stwlc38/stwlc38.c new file mode 100644 index 0000000000..7153ae6227 --- /dev/null +++ b/core/embed/sys/powerctl/stwlc38/stwlc38.c @@ -0,0 +1,430 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include "stwlc38.h" +#include "stwlc38_defs.h" + +#ifdef KERNEL_MODE + +// !@# TODO: put following constants the board file +#define STWLC38_INT_PIN GPIO_PIN_15 +#define STWLC38_INT_PORT GPIOG +#define STWLC38_INT_PIN_CLK_ENA __HAL_RCC_GPIOG_CLK_ENABLE +#define STWLC38_EXTI_INTERRUPT_GPIOSEL EXTI_GPIOG +#define STWLC38_EXTI_INTERRUPT_LINE EXTI_LINE_15 +#define STWLC38_EXTI_INTERRUPT_NUM EXTI15_IRQn +#define STWLC38_EXTI_INTERRUPT_HANDLER EXTI15_IRQHandler +#define STWLC38_ENB_PIN GPIO_PIN_3 +#define STWLC37_ENB_PORT GPIOD +#define STWLC38_ENB_PIN_CLK_ENA __HAL_RCC_GPIOD_CLK_ENABLE + +// Period of the status readout [ms] +#define STWLC38_STATUS_READOUT_INTERVAL_MS 500 + +// STWLC38 FSM states +typedef enum { + STWLC38_STATE_POWER_DOWN = 0, + STWLC38_STATE_IDLE, + STWLC38_STATE_VOUT_ENABLE, + STWLC38_STATE_VOUT_DISABLE, + STWLC38_STATE_STATUS_READOUT, +} stwlc38_fsm_state_t; + +typedef struct { + // Rectified voltage [mV] + uint16_t vrect; + // Main LDO voltage output [mV] + uint16_t vout; + // Output current [mA] + uint16_t icur; + // Chip temperature [°C * 10] + uint16_t tmeas; + // Operating frequency [kHz] + uint16_t opfreq; + // NTC Temperature [°C * 10] + uint16_t ntc; + // RX Int Status 0 + uint8_t status0; + +} stwlc38_status_regs_t; + +// STWLC38 driver state +typedef struct { + // Set if the driver is initialized + bool initialized; + + // I2C bus where the STWLC38 is connected + i2c_bus_t* i2c_bus; + // Storage for the pending I2C packet + i2c_packet_t pending_i2c_packet; + // Status register (global buffer used for state readout) + stwlc38_status_regs_t status_regs; + // Timer used for periodic status readout + systimer_t* timer; + + // Main LDO output current state + bool vout_enabled; + // Main LDO output requested state + bool vout_enabled_requested; + // Flags set if status readout is scheduled + bool status_readout_requested; + + // Current status + stwlc38_status_t status; + // Current state of the FSM + stwlc38_fsm_state_t state; + +} stwlc38_driver_t; + +// STWLC38 driver instance +static stwlc38_driver_t g_stwlc38_driver = { + .initialized = false, +}; + +// forward declarations +static void stwlc38_timer_callback(void* context); +static void stwlc38_i2c_callback(void* context, i2c_packet_t* packet); +static void stwlc38_fsm_continue(stwlc38_driver_t* drv); + +void stwlc38_deinit(void) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + i2c_bus_close(drv->i2c_bus); + systimer_delete(drv->timer); + memset(drv, 0, sizeof(stwlc38_driver_t)); +} + +bool stwlc38_init(void) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + if (drv->initialized) { + return true; + } + + memset(drv, 0, sizeof(stwlc38_driver_t)); + + drv->state = STWLC38_STATE_POWER_DOWN; + + // Main LDO output is enabled by default + drv->vout_enabled = true; + drv->vout_enabled_requested = true; + + drv->i2c_bus = i2c_bus_open(STWLC38_I2C_INSTANCE); + if (drv->i2c_bus == NULL) { + goto cleanup; + } + + drv->timer = systimer_create(stwlc38_timer_callback, drv); + if (drv->timer == NULL) { + goto cleanup; + } + + STWLC38_INT_PIN_CLK_ENA(); + STWLC38_ENB_PIN_CLK_ENA(); + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // INT pin, active low, external pull-up + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_PULLUP; // NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = STWLC38_INT_PIN; + HAL_GPIO_Init(STWLC38_INT_PORT, &GPIO_InitStructure); + + // ENB pin, active low, external pull-down + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Pin = STWLC38_ENB_PIN; + HAL_GPIO_WritePin(STWLC37_ENB_PORT, STWLC38_ENB_PIN, GPIO_PIN_RESET); + HAL_GPIO_Init(STWLC37_ENB_PORT, &GPIO_InitStructure); + + // Setup interrupt line for the STWLC38 + EXTI_HandleTypeDef EXTI_Handle = {0}; + EXTI_ConfigTypeDef EXTI_Config = {0}; + EXTI_Config.GPIOSel = STWLC38_EXTI_INTERRUPT_GPIOSEL; + EXTI_Config.Line = STWLC38_EXTI_INTERRUPT_LINE; + EXTI_Config.Mode = EXTI_MODE_INTERRUPT; + EXTI_Config.Trigger = EXTI_TRIGGER_FALLING; + HAL_EXTI_SetConfigLine(&EXTI_Handle, &EXTI_Config); + NVIC_SetPriority(STWLC38_EXTI_INTERRUPT_NUM, IRQ_PRI_NORMAL); + __HAL_GPIO_EXTI_CLEAR_FLAG(STWLC38_INT_PIN); + NVIC_EnableIRQ(STWLC38_EXTI_INTERRUPT_NUM); + + drv->initialized = true; + + // Try to readout stwlc38 status, it may be already powered up + irq_key_t irq_key = irq_lock(); + drv->status_readout_requested = true; + stwlc38_fsm_continue(drv); + irq_unlock(irq_key); + + return true; + +cleanup: + stwlc38_deinit(); + return false; +} + +bool stwlc38_enable(bool enable) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + if (enable) { + HAL_GPIO_WritePin(STWLC37_ENB_PORT, STWLC38_ENB_PIN, GPIO_PIN_RESET); + } else { + HAL_GPIO_WritePin(STWLC37_ENB_PORT, STWLC38_ENB_PIN, GPIO_PIN_SET); + } + + return true; +} + +bool stwlc38_enable_vout(bool enable) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + + if (drv->vout_enabled_requested != enable) { + drv->vout_enabled_requested = enable; + stwlc38_fsm_continue(drv); + } + + irq_unlock(irq_key); + + return true; +} + +// I2C operation for writing 8-bit constant value to the STWLC38 register +#define STWLC_WRITE_CONST8(reg, value) \ + { \ + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, .size = 3, \ + .data = {(reg) >> 8, (reg) & 0xFF, (value)}, \ + } + +// I2C operations for reading 16-bit STWLC38 register into the +// specified field in `g_stwlc38_driver` structure +#define STWLC_READ_FIELD16(reg, field) \ + { \ + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, \ + .size = 2, \ + .data = {(reg) >> 8, (reg) & 0xFF}, \ + }, \ + { \ + .flags = I2C_FLAG_RX, .size = 2, .ptr = &g_stwlc38_driver.field, \ + } + +// I2C operations for reading 8-bit STWLC38 register into the +// specified field in `g_stwlc38_driver` structure +#define STWLC_READ_FIELD8(reg, field) \ + { \ + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, \ + .size = 2, \ + .data = {(reg) >> 8, (reg) & 0xFF}, \ + }, \ + { \ + .flags = I2C_FLAG_RX, .size = 1, .ptr = &g_stwlc38_driver.field, \ + } + +// I2C operations for readout of the current state into the +// `g_stwlc38.state` structure +static const i2c_op_t stwlc38_ops_status_readout[] = { + STWLC_READ_FIELD16(STWLC38_REG_VRECT, status_regs.vrect), + STWLC_READ_FIELD16(STWLC38_REG_VOUT, status_regs.vout), + STWLC_READ_FIELD16(STWLC38_REG_ICUR, status_regs.icur), + STWLC_READ_FIELD16(STWLC38_REG_TMEAS, status_regs.tmeas), + STWLC_READ_FIELD16(STWLC38_REG_OPFREQ, status_regs.opfreq), + STWLC_READ_FIELD16(STWLC38_REG_NTC, status_regs.ntc), + STWLC_READ_FIELD8(STWLC38_REG_RXINT_STATUS0, status_regs.status0), +}; + +// I2C operations for enabling of the main LDO +static const i2c_op_t stwlc38_ops_vout_enable[] = { + STWLC_WRITE_CONST8(STWLC38_RX_COMMAND, 0x01), // RX VOUT ON +}; + +// I2C operations for disabling of the main LDO +static const i2c_op_t stwlc38_ops_vout_disable[] = { + STWLC_WRITE_CONST8(STWLC38_RX_COMMAND, 0x02), // RX VOUT OFF +}; + +#define stwlc38_i2c_submit(drv, ops) \ + _stwlc38_i2c_submit(drv, ops, ARRAY_LENGTH(ops)) + +// helper function for submitting I2C operations +static void _stwlc38_i2c_submit(stwlc38_driver_t* drv, const i2c_op_t* ops, + size_t op_count) { + i2c_packet_t* pkt = &drv->pending_i2c_packet; + + memset(pkt, 0, sizeof(i2c_packet_t)); + pkt->address = STWLC38_I2C_ADDRESS; + pkt->context = drv; + pkt->callback = stwlc38_i2c_callback; + pkt->timeout = 0; + pkt->ops = (i2c_op_t*)ops; + pkt->op_count = op_count; + + i2c_status_t status = i2c_bus_submit(drv->i2c_bus, pkt); + + if (status != I2C_STATUS_OK) { + // This should never happen + error_shutdown("STWLC38 I2C submit error"); + } +} + +bool stwlc38_get_status(stwlc38_status_t* status) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + *status = drv->status; + irq_unlock(irq_key); + + return true; +} + +static void stwlc38_timer_callback(void* context) { + stwlc38_driver_t* drv = (stwlc38_driver_t*)context; + + // Schedule the status readout + drv->status_readout_requested = true; + stwlc38_fsm_continue(drv); +} + +static void stwlc38_i2c_callback(void* context, i2c_packet_t* packet) { + stwlc38_driver_t* drv = (stwlc38_driver_t*)context; + + if (packet->status != I2C_STATUS_OK) { + memset(&drv->status, 0, sizeof(stwlc38_status_t)); + // Kill periodic timer + systimer_unset(drv->timer); + // !@# retry on error????? + drv->state = STWLC38_STATE_POWER_DOWN; + drv->status_readout_requested = false; + + return; + } + + switch (drv->state) { + case STWLC38_STATE_STATUS_READOUT: + drv->status_readout_requested = false; + + bool was_ready = drv->status.ready; + + // Status registers readout completed + memset(&drv->status, 0, sizeof(stwlc38_status_t)); + drv->status.ready = true; + drv->status.vout_ready = drv->status_regs.status0 & 0x40; + drv->status.vrect = drv->status_regs.vrect; + drv->status.vout = drv->status_regs.vout; + drv->status.icur = drv->status_regs.icur; + drv->status.tmeas = drv->status_regs.tmeas; + drv->status.opfreq = drv->status_regs.opfreq; + drv->status.ntc = drv->status_regs.ntc; + + // Just powered-up ? + if (!was_ready) { + // After power-up, ensure that the main LDO is in the requested state + drv->vout_enabled = !drv->vout_enabled_requested; + // Start the periodic timer + systimer_set_periodic(drv->timer, STWLC38_STATUS_READOUT_INTERVAL_MS); + } + + break; + + case STWLC38_STATE_VOUT_ENABLE: + // Main LDO output enabled + drv->vout_enabled = true; + break; + + case STWLC38_STATE_VOUT_DISABLE: + // Main LDO output disabled + drv->vout_enabled = false; + break; + + default: + // This should never happen + break; + } + + drv->state = STWLC38_STATE_IDLE; + stwlc38_fsm_continue(drv); +} + +void STWLC38_EXTI_INTERRUPT_HANDLER(void) { + stwlc38_driver_t* drv = &g_stwlc38_driver; + + // Clear the EXTI line pending bit + __HAL_GPIO_EXTI_CLEAR_FLAG(STWLC38_INT_PIN); + + if (drv->state == STWLC38_STATE_POWER_DOWN) { + // Inform the powerctl module about the WPC + // wakeup_flags_set(WAKEUP_FLAGS_WPC); + drv->status_readout_requested = true; + stwlc38_fsm_continue(drv); + } +} + +static void stwlc38_fsm_continue(stwlc38_driver_t* drv) { + // The order of the following conditions defines the priority + + if (drv->state == STWLC38_STATE_POWER_DOWN && drv->status_readout_requested) { + // Check if the i2c interface is ready + stwlc38_i2c_submit(drv, stwlc38_ops_status_readout); + drv->state = STWLC38_STATE_STATUS_READOUT; + return; + } + + if (drv->state != STWLC38_STATE_IDLE) { + return; + } + + if (drv->vout_enabled != drv->vout_enabled_requested) { + // Enable/Disable the main LDO output + if (drv->vout_enabled_requested) { + stwlc38_i2c_submit(drv, stwlc38_ops_vout_enable); + drv->state = STWLC38_STATE_VOUT_ENABLE; + } else { + stwlc38_i2c_submit(drv, stwlc38_ops_vout_disable); + drv->state = STWLC38_STATE_VOUT_DISABLE; + } + } else if (drv->status_readout_requested) { + // Read status registers + stwlc38_i2c_submit(drv, stwlc38_ops_status_readout); + drv->state = STWLC38_STATE_STATUS_READOUT; + } +} + +#endif // KERNEL_MODE diff --git a/core/embed/sys/powerctl/stwlc38/stwlc38.h b/core/embed/sys/powerctl/stwlc38/stwlc38.h new file mode 100644 index 0000000000..713c2121eb --- /dev/null +++ b/core/embed/sys/powerctl/stwlc38/stwlc38.h @@ -0,0 +1,73 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_STWLC38_H +#define TREZORHAL_STWLC38_H + +#include + +// Initializes STWLC38 driver +// +// After initialization, the STWLC38 is enabled by default. +bool stwlc38_init(void); + +// Deinitializes STWLC38 driver +void stwlc38_deinit(void); + +// Enables or disables the STWLC38. This can be used to enable/disable +// wireless charging functionality. +// +// If the STWLC38 is disabled, it's not self-powered and is unable to +// communicate over I2C. STWLC38 is disabled by default after initialization. +// +// Returns true if the STWLC38 was successfully enabled or disabled. +bool stwlc38_enable(bool enable); + +// Enables or disables the main LDO output. +// +// Main LDO output is enabled by default after initialization. +// +// Returns true if the main LDO output was successfully enabled or disabled. +bool stwlc38_enable_vout(bool enable); + +typedef struct { + // Powered-up and initialized + bool ready; + // Providing power to the system + bool vout_ready; + + // Rectified voltage [mV] + uint16_t vrect; + // Main LDO voltage output [mV] + uint16_t vout; + // Output current [mA] + uint16_t icur; + // Chip temperature [°C * 10] + uint16_t tmeas; + // Operating frequency [kHz] + uint16_t opfreq; + // NTC Temperature [°C * 10] + uint16_t ntc; + +} stwlc38_status_t; + +// Gets the current state of the STWLC38 +bool stwlc38_get_status(stwlc38_status_t* status); + +#endif // TREZORHAL_STWLC38_H diff --git a/core/embed/sys/powerctl/stwlc38/stwlc38_defs.h b/core/embed/sys/powerctl/stwlc38/stwlc38_defs.h new file mode 100644 index 0000000000..a537362f46 --- /dev/null +++ b/core/embed/sys/powerctl/stwlc38/stwlc38_defs.h @@ -0,0 +1,47 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_STWLC38_DEFS_H +#define TREZORHAL_STWLC38_DEFS_H + +// I2C address of the STWLC38 on the I2C bus. +#define STWLC38_I2C_ADDRESS 0x61 + +// RX Command register +#define STWLC38_RX_COMMAND 0x90 + +// Rectified voltage [mV/16-bit] +#define STWLC38_REG_VRECT 0x92 +// Main LDO voltage output [mV/16-bit] +#define STWLC38_REG_VOUT 0x94 +// Output current [mA] +#define STWLC38_REG_ICUR 0x96 +// Chip temperature [°C * 10] +#define STWLC38_REG_TMEAS 0x98 +// Operating frequency [kHz] +#define STWLC38_REG_OPFREQ 0x9A +// NTC Temperature [°C * 10] +#define STWLC38_REG_NTC 0x9C + +// 3-byte status register +#define STWLC38_REG_RXINT_STATUS0 0x8C +#define STWLC38_REG_RXINT_STATUS1 0x8D +#define STWLC38_REG_RXINT_STATUS2 0x8E + +#endif // TREZORHAL_STWLC38_DEFS_H diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py index 2790c6f62b..f0c5460b79 100644 --- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py +++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py @@ -117,7 +117,10 @@ def configure( "USE_RESET_TO_BOOT=1", ] - sources += ["embed/sys/powerctl/npm1300/npm1300.c"] + sources += [ + "embed/sys/powerctl/npm1300/npm1300.c", + "embed/sys/powerctl/stwlc38/stwlc38.c" + ] env.get("ENV")["LINKER_SCRIPT"] = linker_script