1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-01 11:58:28 +00:00

feat(core): introduce stwlc38 driver

[no changelog]
This commit is contained in:
cepetr 2024-11-18 09:08:25 +01:00
parent b186c0d307
commit 962afee7fe
5 changed files with 556 additions and 1 deletions

View File

@ -19,6 +19,8 @@
#define NPM1300_I2C_INSTANCE 0
#define STWLC38_I2C_INSTANCE 1
#define I2C_COUNT 4
#define I2C_INSTANCE_0 I2C1

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <trezor_bsp.h>
#include <trezor_rtl.h>
#include <io/i2c_bus.h>
#include <sys/irq.h>
#include <sys/systimer.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef TREZORHAL_STWLC38_H
#define TREZORHAL_STWLC38_H
#include <trezor_types.h>
// 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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