diff --git a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
index d43f26fa4b..c7ab40e650 100644
--- a/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
+++ b/core/embed/models/T3W1/boards/trezor_t3w1_revA.h
@@ -22,6 +22,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 f97595febc..8f463d327e 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
@@ -134,7 +134,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