diff --git a/core/embed/sys/powerctl/npm1300/npm1300.c b/core/embed/sys/powerctl/npm1300/npm1300.c new file mode 100644 index 0000000000..b9b82c054e --- /dev/null +++ b/core/embed/sys/powerctl/npm1300/npm1300.c @@ -0,0 +1,792 @@ +/* + * 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 + +#include "npm1300.h" +#include "npm1300_defs.h" + +#ifdef KERNEL_MODE + +// Default timeout for all I2C operations +#define NPM1300_I2C_TIMEOUT 10 + +// Maximum number of consecutive I2C errors after we report a fatal error +#define NPM1300_I2C_ERROR_LIMIT 3 + +// Delay inserted between the ADC trigger and the readout [ms] +#define NPM1300_ADC_READOUT_DELAY 300 + +// NPM1300 FSM states +typedef enum { + NPM1300_STATE_IDLE = 0, + NPM1300_STATE_CHARGING_ENABLE, + NPM1300_STATE_CHARGING_DISABLE, + NPM1300_STATE_CHARGING_LIMIT, + NPM1300_STATE_ADC_TRIGGER, + NPM1300_STATE_ADC_WAIT, + NPM1300_STATE_ADC_READOUT, +} npm1300_fsm_state_t; + +typedef struct { + uint8_t adc_gp0_result_lsbs; + uint8_t adc_vbat_result_msb; + uint8_t adc_nt_result_msb; + uint8_t adc_temp_result_msb; + uint8_t adc_vsys_result_msb; + uint8_t adc_gp1_result_lsbs; + uint8_t adc_vbat2_result_msb; + uint8_t adc_ibat_meas_status; + +} npm1300_adc_regs_t; + +typedef struct { + uint8_t bchg_iset_msb; + uint8_t bchg_iset_lsb; + +} npm1300_chlimit_regs_t; + +// NPM1300 PMIC driver state +typedef struct { + // Set if the PMIC driver is initialized + bool initialized; + + // I2C bus where the PMIC is connected + i2c_bus_t* i2c_bus; + // Number of consecutive I2C errors + int i2c_errors; + // Storage for the pending I2C packet + i2c_packet_t pending_i2c_packet; + + // Timer used for waiting for the ADC conversion + systimer_t* timer; + + // Content of RTSCAUSE register read during driver initialization + uint8_t restart_cause; + + // Current state of the FSM + npm1300_fsm_state_t state; + + // ADC register (global buffer used for ADC measurements) + npm1300_adc_regs_t adc_regs; + // Charging limit registers (global buffer used for charging limit) + npm1300_chlimit_regs_t chlimit_regs; + + // Discharge current limit [mA] + uint16_t i_limit; + + // Charge current limit [mA] + uint16_t i_charge; // written value + uint16_t i_charge_requested; // requested value + uint16_t i_charge_set; // value beeing written + + // Set if the charging is enabled + bool charging; + bool charging_requested; + + // Request flags for ADC measurements + bool adc_trigger_requested; + bool adc_readout_requested; + + // Report callback used for asynchronous measurements + npm1300_report_callback_t report_callback; + void* report_callback_context; + +} npm1300_driver_t; + +// PMIC driver instance +npm1300_driver_t g_npm1300_driver = { + .initialized = false, +}; + +// forward declarations +static void npm1300_timer_callback(void* context); +static void npm1300_i2c_callback(void* context, i2c_packet_t* packet); +static void npm1300_fsm_continue(npm1300_driver_t* drv); + +// Writes a value to the NPM1300 register +// +// This function is used only during driver initialization because +// it's synchronous and blocks the execution. +static bool npm1300_set_reg(i2c_bus_t* bus, uint16_t addr, uint8_t value) { + i2c_op_t ops[] = { + { + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED, + .size = 3, + .data = {addr >> 8, addr & 0xFF, value}, + }, + }; + + i2c_packet_t pkt = { + .address = NPM1300_I2C_ADDRESS, + .timeout = NPM1300_I2C_TIMEOUT, + .op_count = ARRAY_LENGTH(ops), + .ops = ops, + }; + + if (I2C_STATUS_OK != i2c_bus_submit_and_wait(bus, &pkt)) { + return false; + } + + return true; +} + +// Reads a value from the NPM1300 register +// +// This function is used only during driver initialization because +// it's synchronous and blocks the execution. +static bool npm1300_get_reg(i2c_bus_t* bus, uint16_t addr, uint8_t* data) { + i2c_op_t ops[] = { + { + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED, + .size = 2, + .data = {addr >> 8, addr & 0xFF}, + }, + { + .flags = I2C_FLAG_RX, + .size = sizeof(*data), + .ptr = data, + }, + }; + + i2c_packet_t pkt = { + .address = NPM1300_I2C_ADDRESS, + .timeout = NPM1300_I2C_TIMEOUT, + .op_count = ARRAY_LENGTH(ops), + .ops = ops, + }; + + if (I2C_STATUS_OK != i2c_bus_submit_and_wait(bus, &pkt)) { + return false; + } + + return true; +} + +// Initializes the NPM1300 driver to the default state +static bool npm1300_initialize(i2c_bus_t* bus, uint16_t i_charge, + uint16_t i_limit) { + uint16_t bchg_iset = i_charge / 2; // 2mA steps + uint16_t bchg_iset_discharge = (i_limit * 100) / 323; // 3.23mA steps + uint16_t die_temp_stop = 360; // 110°C + uint16_t die_temp_resume = 372; // 100°C + uint16_t ntc_cold = 749; // 0°C + uint16_t ntc_cool = 658; // 10°C + uint16_t ntc_warm = 337; // 45°C + uint16_t ntc_hot = 237; // 60°C + + struct { + uint16_t addr; + uint8_t value; + } table[] = { + {NPM1300_SCRATCH0, 0x00}, + {NPM1300_SCRATCH1, 0x00}, + // SYSREG + {NPM1300_VBUSINILIM0, NPM1300_VBUSINILIM0_500MA}, + {NPM1300_VBUSINILIMSTARTUP, NPM1300_VBUSINILIM0_500MA}, + {NPM1300_VBUSSUSPEND, 0x00}, + {NPM1300_TASKUPDATEILIMSW, NPM1300_TASKUPDATEILIM_SELVBUSILIM0}, + // LOADSW/LDO + {NPM1300_LDSW1GPISEL, 0x00}, + {NPM1300_LDSW2GPISEL, 0x00}, + {NPM1300_TASKLDSW1CLR, 0x01}, + {NPM1300_TASKLDSW2CLR, 0x01}, + // BUCK regulators + // TODO + // ADC settings + {NPM1300_ADCNTCRSEL, NPM1300_ADCNTCRSEL_10K}, + {NPM1300_ADCCONFIG, 0x00}, + {NPM1300_ADCIBATMEASEN, NPM1300_ADCIBATMEASEN_IBATMEASENABLE}, + // Charger settings + {NPM1300_BCHGVTERM, NPM1300_BCHGVTERM_3V65}, + {NPM1300_BCHGVTERMR, NPM1300_BCHGVTERM_3V60}, + {NPM1300_BCHGVTRICKLESEL, NPM1300_BCHGVTRICKLESEL_2V5}, + {NPM1300_BCHGITERMSEL, NPM1300_BCHGITERMSEL_SEL10}, + {NPM1300_BCHGISETMSB, bchg_iset >> 1}, + {NPM1300_BCHGISETLSB, bchg_iset & 1}, + {NPM1300_BCHGISETDISCHARGEMSB, bchg_iset_discharge >> 1}, + {NPM1300_BCHGISETDISCHARGELSB, bchg_iset_discharge & 1}, + {NPM1300_BCHGDISABLECLR, NPM1300_BCHGDISABLECLR_USENTC}, + {NPM1300_BCHGDISABLECLR, NPM1300_BCHGDISABLECLR_ENABLERCHRG}, + {NPM1300_BCHGCONFIG, 0}, + // Disable charging + {NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG}, + // NTC thresholds + {NPM1300_NTCCOLD, ntc_cold >> 2}, + {NPM1300_NTCCOLDLSB, ntc_cold & 0x3}, + {NPM1300_NTCCOOL, ntc_cool >> 2}, + {NPM1300_NTCCOOLLSB, ntc_cool & 0x3}, + {NPM1300_NTCWARM, ntc_warm >> 2}, + {NPM1300_NTCWARMLSB, ntc_warm & 0x3}, + {NPM1300_NTCHOT, ntc_hot >> 2}, + {NPM1300_NTCHOTLSB, ntc_hot & 0x3}, + // Die tempererature thresholds + {NPM1300_DIETEMPSTOP, die_temp_stop >> 2}, + {NPM1300_DIETEMPSTOPLSB, die_temp_stop & 0x03}, + {NPM1300_DIETEMPRESUME, die_temp_resume >> 2}, + {NPM1300_DIETEMPRESUMELSB, die_temp_resume & 0x03}, + // LEDS + {NPM1300_LEDDRV0MODESEL, NPM1300_LEDDRVMODESEL_ERROR}, + {NPM1300_LEDDRV1MODESEL, NPM1300_LEDDRVMODESEL_CHARGING}, + {NPM1300_LEDDRV2MODESEL, NPM1300_LEDDRVMODESEL_NOTUSED}, + // GPIO + {NPM1300_GPIOMODE0, NPM1300_GPIOMODE_GPIINPUT}, + {NPM1300_GPIOMODE1, NPM1300_GPIOMODE_GPIINPUT}, + {NPM1300_GPIOMODE2, NPM1300_GPIOMODE_GPIINPUT}, + {NPM1300_GPIOMODE3, NPM1300_GPIOMODE_GPIINPUT}, + {NPM1300_GPIOMODE4, NPM1300_GPIOMODE_GPIINPUT}, + // POF + {NPM1300_POFCONFIG, 0x00}, + // TIMER + {NPM1300_TIMERCLR, 0x01}, + // Ship and hibernate mode + // {NPM1300_SHPHLDCONFIG, .. }, + // {NPM1300_TASKSHPHLDCFGSTROBE, 0x01}, + + // TODO automatic temp measurement during charging + }; + + for (int i = 0; i < sizeof(table) / sizeof(table[0]); i++) { + if (!npm1300_set_reg(bus, table[i].addr, table[i].value)) { + return false; + } + } + + return true; +} + +bool npm1300_init(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (drv->initialized) { + return true; + } + + memset(drv, 0, sizeof(npm1300_driver_t)); + + drv->i_charge = NPM1300_CHARGING_LIMIT_DEFAULT; // mA + drv->i_limit = 500; // mA (268mA-1340mA) + + drv->i_charge_set = drv->i_charge; + drv->i_charge_requested = drv->i_charge; + + drv->i2c_bus = i2c_bus_open(NPM1300_I2C_INSTANCE); + if (drv->i2c_bus == NULL) { + goto cleanup; + } + + drv->timer = systimer_create(npm1300_timer_callback, drv); + if (drv->timer == NULL) { + goto cleanup; + } + + if (!npm1300_get_reg(drv->i2c_bus, NPM1300_RSTCAUSE, &drv->restart_cause)) { + goto cleanup; + } + + if (!npm1300_initialize(drv->i2c_bus, drv->i_charge, drv->i_limit)) { + goto cleanup; + } + + drv->initialized = true; + + return true; + +cleanup: + npm1300_deinit(); + return false; +} + +void npm1300_deinit(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + i2c_bus_close(drv->i2c_bus); + systimer_delete(drv->timer); + + memset(drv, 0, sizeof(npm1300_driver_t)); +} + +bool npm1300_shipmode(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + // TODO + + return true; +} + +int npm1300_get_charging_limit(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return 0; + } + + return drv->i_charge_requested; +} + +bool npm1300_set_charging_limit(int i_charge) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + if (i_charge < NPM1300_CHARGING_LIMIT_MIN || + i_charge > NPM1300_CHARGING_LIMIT_MAX) { + // The value is out of range + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->i_charge_requested = i_charge; + npm1300_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +bool npm1300_set_charging(bool enable) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->charging_requested = enable; + npm1300_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +uint8_t npm1300_restart_cause(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return 0; + } + + return drv->restart_cause; +} + +bool npm1300_measure(npm1300_report_callback_t callback, void* context) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + + if (drv->report_callback != NULL && callback != NULL) { + // Cannot start another measurement while the previous one is in progress + irq_unlock(irq_key); + return false; + } + + drv->report_callback = callback; + drv->report_callback_context = context; + + if (drv->report_callback != NULL) { + drv->adc_trigger_requested = true; + npm1300_fsm_continue(drv); + } + + irq_unlock(irq_key); + + return true; +} + +// Synchronous measurement context structure +// (used internally within the `npm1300_measure_sync` function) +typedef struct { + // Set when the measurement is done + volatile bool done; + // Report structure where the measurement is stored + npm1300_report_t* report; +} npm1300_sync_measure_t; + +// Callback for the synchronous measurement +static void npm1300_sync_measure_callback(void* context, + npm1300_report_t* report) { + npm1300_sync_measure_t* ctx = (npm1300_sync_measure_t*)context; + *ctx->report = *report; + ctx->done = true; +} + +bool npm1300_measure_sync(npm1300_report_t* report) { + npm1300_sync_measure_t measure = { + .done = false, + .report = report, + }; + + // Start asynchronous measurement + if (!npm1300_measure(npm1300_sync_measure_callback, &measure)) { + return false; + } + + // Wait for the measurement to finish + while (!measure.done) { + __WFI(); + } + + return true; +} + +// Prepares PMIC report from the last readout of the ADC values +// stored in `drv->adc_regs` +// +// This function is called in the irq context. +static void npm1300_calculate_report(npm1300_driver_t* drv, + npm1300_report_t* report) { + memset(report, 0, sizeof(npm1300_report_t)); + + npm1300_adc_regs_t* r = &drv->adc_regs; + + // Gather measured values from the ADC registers + + uint16_t vbat_adc = + (r->adc_vbat_result_msb << 2) + (r->adc_gp0_result_lsbs & 0x03); + + uint16_t ntc_adc = + (r->adc_nt_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 2) & 0x3); + + uint16_t die_adc = + (r->adc_temp_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 4) & 0x3); + + uint16_t vsys_adc = + (r->adc_vsys_result_msb << 2) + ((r->adc_gp0_result_lsbs >> 6) & 0x03); + + uint16_t ibat_adc = + (r->adc_vbat2_result_msb << 2) + ((r->adc_gp1_result_lsbs >> 4) & 0x03); + + // IBAT_MEAS_STATUS register isn't well documented in the NPM1300 datasheet. + // The following is based partially on observation. + // + // 00100 - discharge + // 01000 - usb powered, not charging + // 01100 - charge trickle + // 01110 - charge cool + // 01111 - charge normal + // 1XXXX - invalid value, start measure again + + // bool ibat_invalid = (ibat_meas_status & 0x10) != 0; + bool ibat_discharging = ((r->adc_ibat_meas_status >> 2) & 0x03) == 1; + bool ibat_charging = ((r->adc_ibat_meas_status >> 2) & 0x03) == 3; + + // Calculate the battery current based on the ADC reading and operating state. + // If discharging, use the discharge current limit (i_limit). + // If charging, use the charge current limit (i_charge). + // See the NPM1300 datasheet for details. + if (ibat_discharging) { + report->ibat = ((int)ibat_adc * drv->i_limit) / 1250.0; + } else if (ibat_charging) { + report->ibat = -((int)ibat_adc * drv->i_charge) / 800.0; + } else { + report->ibat = 0; + } + + // Calculate the battery voltage (VBAT) from the ADC value. + // VBAT is scaled by the voltage divider ratio and ADC resolution. + report->vbat = (vbat_adc * 5.0) / 1023.0; + + // Calculate the temperature from the NTC (thermistor). + // Beta value for the thermistor is specified as 3380. + // The equation is derived from the NPM1300 datasheet. + float beta = 3380; + report->ntc_temp = + 1 / (1 / 298.15 - (1 / beta) * logf(1024.0 / ntc_adc - 1)) - 298.15 + + 25.0; + + // Calculate the die temperature from the die ADC reading. + // The equation is derived from the NPM1300 datasheet. + report->die_temp = 394.67 - 0.7926 * die_adc; + + // Calculate the system voltage (VSYS) from the ADC value. + // VSYS is scaled based on the system voltage divider ratio and ADC + // resolution. + report->vsys = (vsys_adc * 6.375) / 1023.0; + + // Populate measurement and status flags from the raw data + report->ibat_meas_status = r->adc_ibat_meas_status; +} + +// I2C operation for writing constant value to the npm1300 register +#define NPM_WRITE_CONST(reg, value) \ + { \ + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, .size = 3, \ + .data = {(reg) >> 8, (reg) & 0xFF, (value)}, \ + } + +// I2C operations for the value of specified uint8_t field +// in `g_npm1300_driver` structure into npm1300 register +#define NPM_WRITE_FIELD(reg, field) \ + { \ + .flags = I2C_FLAG_TX | I2C_FLAG_EMBED | I2C_FLAG_START, \ + .size = 2, \ + .data = {(reg) >> 8, (reg) & 0xFF}, \ + }, \ + { \ + .flags = I2C_FLAG_TX, .size = 1, .ptr = &g_npm1300_driver.field, \ + } + +// I2C operations for reading npm1300 register into the specified +// field in `g_npm1300_driver` structure +#define NPM_READ_FIELD(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_npm1300_driver.field, \ + } + +// I2C operations for enabling of the charging +static const i2c_op_t npm1300_ops_charging_enable[] = { + NPM_WRITE_CONST(NPM1300_BCHGENABLESET, NPM1300_BCHGENABLESET_ENABLECHG), +}; + +// I2C operations for disabling of the charging +static const i2c_op_t npm1300_ops_charging_disable[] = { + NPM_WRITE_CONST(NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG), +}; + +// I2C operations for setting of the charging limit from +// `g_npm1300_driver.chlimit_regs` structure +static const i2c_op_t npm1300_ops_charging_limit[] = { + NPM_WRITE_FIELD(NPM1300_BCHGISETMSB, chlimit_regs.bchg_iset_msb), + NPM_WRITE_FIELD(NPM1300_BCHGISETLSB, chlimit_regs.bchg_iset_lsb), +}; + +// I2C operations for setting of the charging limit from +// `g_npm1300_driver.chlimit_regs` structure together with +// disabling and re-enabling of the charging +static const i2c_op_t npm1300_ops_charging_limit_reenable[] = { + NPM_WRITE_CONST(NPM1300_BCHGENABLECLR, NPM1300_BCHGENABLECLR_DISABLECHG), + NPM_WRITE_FIELD(NPM1300_BCHGISETMSB, chlimit_regs.bchg_iset_msb), + NPM_WRITE_FIELD(NPM1300_BCHGISETLSB, chlimit_regs.bchg_iset_lsb), + NPM_WRITE_CONST(NPM1300_BCHGENABLESET, NPM1300_BCHGENABLESET_ENABLECHG), +}; + +// I2C operations for triggering of the ADC measurements +static const i2c_op_t npm1300_ops_adc_trigger[] = { + NPM_WRITE_CONST(NPM1300_TASKVBATMEASURE, 1), + NPM_WRITE_CONST(NPM1300_TASKVSYSMEASURE, 1), + NPM_WRITE_CONST(NPM1300_TASKNTCMEASURE, 1), + NPM_WRITE_CONST(NPM1300_TASKTEMPMEASURE, 1), +}; + +// I2C operations for readout of the ADC values into the +// `g_npm1300_driver.adc_regs` structure +static const i2c_op_t npm1300_ops_adc_readout[] = { + NPM_WRITE_CONST(NPM1300_TASKUPDATEILIMSW, + NPM1300_TASKUPDATEILIM_SELVBUSILIM0), + NPM_READ_FIELD(NPM1300_ADCGP0RESULTLSBS, adc_regs.adc_gp0_result_lsbs), + NPM_READ_FIELD(NPM1300_ADCVBATRESULTMSB, adc_regs.adc_vbat_result_msb), + NPM_READ_FIELD(NPM1300_ADCNTCRESULTMSB, adc_regs.adc_nt_result_msb), + NPM_READ_FIELD(NPM1300_ADCTEMPRESULTMSB, adc_regs.adc_temp_result_msb), + NPM_READ_FIELD(NPM1300_ADCVSYSRESULTMSB, adc_regs.adc_vsys_result_msb), + NPM_READ_FIELD(NPM1300_ADCGP1RESULTLSBS, adc_regs.adc_gp1_result_lsbs), + NPM_READ_FIELD(NPM1300_ADCVBAT2RESULTMSB, adc_regs.adc_vbat2_result_msb), + NPM_READ_FIELD(NPM1300_ADCIBATMEASSTATUS, adc_regs.adc_ibat_meas_status), +}; + +#define npm1300_i2c_submit(drv, ops) \ + _npm1300_i2c_submit(drv, ops, ARRAY_LENGTH(ops)) + +// helper function for submitting I2C operations +static void _npm1300_i2c_submit(npm1300_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 = NPM1300_I2C_ADDRESS; + pkt->context = drv; + pkt->callback = npm1300_i2c_callback; + pkt->timeout = NPM1300_I2C_TIMEOUT; + 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("npm1300 I2C submit error"); + } +} + +// npm1300 driver timer callback invoked when `drv->timer` expires. +// +// This function is called in the irq context. +static void npm1300_timer_callback(void* context) { + npm1300_driver_t* drv = (npm1300_driver_t*)context; + + switch (drv->state) { + case NPM1300_STATE_ADC_WAIT: + // The ADC conversion is done, read the values + drv->adc_readout_requested = true; + drv->state = NPM1300_STATE_IDLE; + break; + + default: + // we should never get here + drv->state = NPM1300_STATE_IDLE; + break; + } + + npm1300_fsm_continue(drv); +} + +// npm1300 driver I2C completion callback invoked when +// `drv->pending_i2c_packet` is completed +// +// This function is called in the irq context. +static void npm1300_i2c_callback(void* context, i2c_packet_t* packet) { + npm1300_driver_t* drv = (npm1300_driver_t*)context; + + if (packet->status != I2C_STATUS_OK) { + drv->i2c_errors++; + + if (drv->i2c_errors > NPM1300_I2C_ERROR_LIMIT) { + error_shutdown("npm1300 I2C error"); + } + + drv->state = NPM1300_STATE_IDLE; + + // I2C operation will be retried until it succeeds or + // the error limit is reached + npm1300_fsm_continue(drv); + return; + } + + // If the I2C operation was successful, reset the error counter + drv->i2c_errors = 0; + + switch (drv->state) { + case NPM1300_STATE_CHARGING_ENABLE: + drv->charging = true; + drv->state = NPM1300_STATE_IDLE; + break; + + case NPM1300_STATE_CHARGING_DISABLE: + drv->charging = false; + drv->state = NPM1300_STATE_IDLE; + break; + + case NPM1300_STATE_CHARGING_LIMIT: + drv->i_charge = drv->i_charge_set; + drv->state = NPM1300_STATE_IDLE; + break; + + case NPM1300_STATE_ADC_TRIGGER: + drv->adc_trigger_requested = false; + systimer_set(drv->timer, NPM1300_ADC_READOUT_DELAY); + drv->state = NPM1300_STATE_ADC_WAIT; + break; + + case NPM1300_STATE_ADC_READOUT: + drv->adc_readout_requested = false; + + npm1300_report_t report; + npm1300_calculate_report(drv, &report); + + // Invoke report callback + npm1300_report_callback_t report_callback = drv->report_callback; + void* report_callback_context = drv->report_callback_context; + + // Clear the report callback before invoking it + // to allow the new measurement to be scheduled in the callback + drv->report_callback = NULL; + drv->report_callback_context = NULL; + + if (report_callback != NULL) { + report_callback(report_callback_context, &report); + } + + drv->state = NPM1300_STATE_IDLE; + break; + + default: + // we should never get here + drv->state = NPM1300_STATE_IDLE; + break; + } + + npm1300_fsm_continue(drv); +} + +// npm1300 driver FSM continuation function that decides what to do next +// +// This function is called in the irq context or when interrupts are disabled. +static void npm1300_fsm_continue(npm1300_driver_t* drv) { + if (drv->state != NPM1300_STATE_IDLE) { + return; + } + + // The order of the following conditions defines the priority + + if (drv->i_charge != drv->i_charge_requested) { + // Change charging limit + uint16_t bchg_iset = drv->i_charge / 2; // 2mA steps + drv->chlimit_regs.bchg_iset_msb = bchg_iset >> 1; + drv->chlimit_regs.bchg_iset_lsb = bchg_iset & 1; + drv->i_charge_set = drv->i_charge_requested; + + if (drv->charging) { + // When charging is enabled, we need to disable it first + // and then re-enable it after changing the limit + npm1300_i2c_submit(drv, npm1300_ops_charging_limit_reenable); + } else { + npm1300_i2c_submit(drv, npm1300_ops_charging_limit); + } + drv->state = NPM1300_STATE_CHARGING_LIMIT; + } else if (drv->charging != drv->charging_requested) { + // Change charging state + if (drv->charging_requested) { + npm1300_i2c_submit(drv, npm1300_ops_charging_enable); + drv->state = NPM1300_STATE_CHARGING_ENABLE; + } else { + npm1300_i2c_submit(drv, npm1300_ops_charging_disable); + drv->state = NPM1300_STATE_CHARGING_DISABLE; + } + } else if (drv->adc_readout_requested) { + // Read ADC values + npm1300_i2c_submit(drv, npm1300_ops_adc_readout); + drv->state = NPM1300_STATE_ADC_READOUT; + } else if (drv->adc_trigger_requested) { + // Trigger ADC conversion + npm1300_i2c_submit(drv, npm1300_ops_adc_trigger); + drv->state = NPM1300_STATE_ADC_TRIGGER; + } +} + +#endif // KERNEL_MODE diff --git a/core/embed/sys/powerctl/npm1300/npm1300.h b/core/embed/sys/powerctl/npm1300/npm1300.h new file mode 100644 index 0000000000..4b7a47fa90 --- /dev/null +++ b/core/embed/sys/powerctl/npm1300/npm1300.h @@ -0,0 +1,101 @@ +/* + * 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_NPM1300_H +#define TREZORHAL_NPM1300_H + +#include + +// Charging current limits +// - range of np1300 is 32-800mA +// - used battery limit is 180mA +#define NPM1300_CHARGING_LIMIT_MIN 32 // mA +#define NPM1300_CHARGING_LIMIT_MAX 800 // mA // !@# TODO: set to 180mA +#define NPM1300_CHARGING_LIMIT_DEFAULT 180 // mA + +typedef struct { + // Battery voltage [V] + float vbat; + // System voltage [V] + float vsys; + // Battery current [mA] + // - positive value means discharging + // - negative value means charging + float ibat; + // NTC temperature [°C] + float ntc_temp; + // Die temperature [°C] + float die_temp; + // IBAT_MEAS_STATUS register value + // (for debugging purposes, see the NPM1300 datasheet) + uint8_t ibat_meas_status; + +} npm1300_report_t; + +typedef void (*npm1300_report_callback_t)(void* context, + npm1300_report_t* report); + +// Initializes NPM1300 PMIC driver +bool npm1300_init(void); + +// Deinitializes NPM1300 PMIC driver +void npm1300_deinit(void); + +// Gets the cause of the last restart +uint8_t npm1300_restart_cause(void); + +// Switches the device to the ship mode +bool npm1300_shipmode(void); + +// Starts the asynchronous measurement +// +// The measurement is started as soon as possible and finished in +// hundreds of milliseconds. The result is reported using the callback. +// +// The function returns `false` if the measurement cannot be started +// (e.g. because the previous measurement is still in progress or +// the the driver is not initialized). +bool npm1300_measure(npm1300_report_callback_t callback, void* context); + +// Synchroneous version of the `pmic_measure` function. +// +// Use only for testing purposes, as it blocks the execution until +// the measurement is done. +// +// Returns `true` if the measurement was successful and the report +// is stored in the `report` structure. +bool npm1300_measure_sync(npm1300_report_t* report); + +// Enables or disables the charging. +// +// The function returns `false` if the operation cannot be performed. +bool npm1300_set_charging(bool enable); + +// Sets the charging current limit [mA]. +// +// The current value must be in the range defined by the +// `NPM1300_CHARGING_LIMIT_MIN` and `NPM1300_CHARGING_LIMIT_MAX` constants. +// +// The function returns `false` if the operation cannot be performed. +bool npm1300_set_charging_limit(int i_charge); + +// Gets the charging current limit [mA]. +int npm1300_get_charging_limit(void); + +#endif // TREZORHAL_NPM1300_H diff --git a/core/embed/sys/powerctl/npm1300/npm1300_defs.h b/core/embed/sys/powerctl/npm1300/npm1300_defs.h new file mode 100644 index 0000000000..cc3831b800 --- /dev/null +++ b/core/embed/sys/powerctl/npm1300/npm1300_defs.h @@ -0,0 +1,320 @@ +/* + * 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_NPM1300_DEFS_H +#define TREZORHAL_NPM1300_DEFS_H + +// I2C address of the NPM1300 on the I2C bus. +#define NPM1300_I2C_ADDRESS 0x6B + +// Event and interrupt registers + +#define NPM1300_TASKSWRESET 0x0001 +#define NPM1300_EVENTSADCSET 0x0002 +#define NPM1300_EVENTSADCCLR 0x0003 +#define NPM1300_INTENEVENTSADCSET 0x0004 +#define NPM1300_INTENEVENTSADCCLR 0x0005 +#define NPM1300_EVENTSBCHARGER0SET 0x0006 +#define NPM1300_EVENTSBCHARGER0CLR 0x0007 +#define NPM1300_INTENEVENTSBCHARGER0SET 0x0008 +#define NPM1300_INTENEVENTSBCHARGER0CLR 0x0009 +#define NPM1300_EVENTSBCHARGER1SET 0x000A +#define NPM1300_EVENTSBCHARGER1CLR 0x000B +#define NPM1300_INTENEVENTSBCHARGER1SET 0x000C +#define NPM1300_INTENEVENTSBCHARGER1CLR 0x000D +#define NPM1300_EVENTSBCHARGER2SET 0x000E +#define NPM1300_EVENTSBCHARGER2CLR 0x000F +#define NPM1300_INTENEVENTSBCHARGER2SET 0x0010 +#define NPM1300_INTENEVENTSBCHARGER2CLR 0x0011 +#define NPM1300_EVENTSSHPHLDSET 0x0012 +#define NPM1300_EVENTSSHPHLDCLR 0x0013 +#define NPM1300_INTENEVENTSSHPHLDSET 0x0014 +#define NPM1300_INTENEVENTSSHPHLDCLR 0x0015 +#define NPM1300_EVENTSVBUSIN0SET 0x0016 +#define NPM1300_EVENTSVBUSIN0CLR 0x0017 +#define NPM1300_INTENEVENTSVBUSIN0SET 0x0018 +#define NPM1300_INTENEVENTSVBUSIN0CLR 0x0019 +#define NPM1300_EVENTSVBUSIN1SET 0x001A +#define NPM1300_EVENTSVBUSIN1CLR 0x001B +#define NPM1300_INTENEVENTSVBUSIN1SET 0x001C +#define NPM1300_INTENEVENTSVBUSIN1CLR 0x001D +#define NPM1300_EVENTSGPIOSET 0x0022 +#define NPM1300_EVENTSGPIOCLR 0x0023 +#define NPM1300_INTENEVENTSGPIOSET 0x0024 +#define NPM1300_INTENEVENTSGPIOCLR 0x0025 + +// SYSREG registers + +#define NPM1300_TASKUPDATEILIMSW 0x0200 +#define NPM1300_VBUSINILIM0 0x0201 +#define NPM1300_VBUSINILIMSTARTUP 0x0202 +#define NPM1300_VBUSSUSPEND 0x0203 +#define NPM1300_USBCDETECTSTATUS 0x0205 +#define NPM1300_VBUSINSTATUS 0x0207 + +// Charger registers + +#define NPM1300_TASKRELEASEERR 0x0300 +#define NPM1300_TASKCLEARCHGERR 0x0301 +#define NPM1300_TASKCLEARSAFETYTIMER 0x0302 +#define NPM1300_BCHGENABLESET 0x0304 +#define NPM1300_BCHGENABLECLR 0x0305 +#define NPM1300_BCHGDISABLESET 0x0306 +#define NPM1300_BCHGDISABLECLR 0x0307 +#define NPM1300_BCHGISETMSB 0x0308 +#define NPM1300_BCHGISETLSB 0x0309 +#define NPM1300_BCHGISETDISCHARGEMSB 0x030A +#define NPM1300_BCHGISETDISCHARGELSB 0x030B +#define NPM1300_BCHGVTERM 0x030C +#define NPM1300_BCHGVTERMR 0x030D +#define NPM1300_BCHGVTRICKLESEL 0x030E +#define NPM1300_BCHGITERMSEL 0x030F +#define NPM1300_NTCCOLD 0x0310 +#define NPM1300_NTCCOLDLSB 0x0311 +#define NPM1300_NTCCOOL 0x0312 +#define NPM1300_NTCCOOLLSB 0x0313 +#define NPM1300_NTCWARM 0x0314 +#define NPM1300_NTCWARMLSB 0x0315 +#define NPM1300_NTCHOT 0x0316 +#define NPM1300_NTCHOTLSB 0x0317 +#define NPM1300_DIETEMPSTOP 0x0318 +#define NPM1300_DIETEMPSTOPLSB 0x0319 +#define NPM1300_DIETEMPRESUME 0x031A +#define NPM1300_DIETEMPRESUMELSB 0x031B +#define NPM1300_BCHGILIMSTATUS 0x032D +#define NPM1300_NTCSTATUS 0x0332 +#define NPM1300_DIETEMPSTATUS 0x0333 +#define NPM1300_BCHGCHARGESTATUS 0x0334 +#define NPM1300_BCHGERRREASON 0x0336 +#define NPM1300_BCHGERRSENSOR 0x0337 +#define NPM1300_BCHGCONFIG 0x033C + +// Buck regulator registers + +#define NPM1300_BUCK1ENASET 0x0400 +#define NPM1300_BUCK1ENACLR 0x0401 +#define NPM1300_BUCK2ENASET 0x0402 +#define NPM1300_BUCK2ENACLR 0x0403 +#define NPM1300_BUCK1PWMSET 0x0404 +#define NPM1300_BUCK1PWMCLR 0x0405 +#define NPM1300_BUCK2PWMSET 0x0406 +#define NPM1300_BUCK2PWMCLR 0x0407 +#define NPM1300_BUCK1NORMVOUT 0x0408 +#define NPM1300_BUCK1RETVOUT 0x0409 +#define NPM1300_BUCK2NORMVOUT 0x040A +#define NPM1300_BUCK2RETVOUT 0x040B +#define NPM1300_BUCKENCTRL 0x040C +#define NPM1300_BUCKVRETCTRL 0x040D +#define NPM1300_BUCKPWMCTRL 0x040E +#define NPM1300_BUCKSWCTRLSEL 0x040F +#define NPM1300_BUCK1VOUTSTATUS 0x0410 +#define NPM1300_BUCK2VOUTSTATUS 0x0411 +#define NPM1300_BUCKCTRL0 0x0415 +#define NPM1300_BUCKSTATUS 0x0434 + +// System monitor registers + +#define NPM1300_TASKVBATMEASURE 0x0500 +#define NPM1300_TASKNTCMEASURE 0x0501 +#define NPM1300_TASKTEMPMEASURE 0x0502 +#define NPM1300_TASKVSYSMEASURE 0x0503 +#define NPM1300_TASKIBATMEASURE 0x0506 +#define NPM1300_TASKVBUS7MEASURE 0x0507 +#define NPM1300_TASKDELAYEDVBATMEASURE 0x0508 +#define NPM1300_ADCCONFIG 0x0509 +#define NPM1300_ADCNTCRSEL 0x050A +#define NPM1300_ADCAUTOTIMCONF 0x050B +#define NPM1300_TASKAUTOTIMUPDATE 0x050C +#define NPM1300_ADCDELTIMCONF 0x050D +#define NPM1300_ADCIBATMEASSTATUS 0x0510 +#define NPM1300_ADCVBATRESULTMSB 0x0511 +#define NPM1300_ADCNTCRESULTMSB 0x0512 +#define NPM1300_ADCTEMPRESULTMSB 0x0513 +#define NPM1300_ADCVSYSRESULTMSB 0x0514 +#define NPM1300_ADCGP0RESULTLSBS 0x0515 +#define NPM1300_ADCVBAT0RESULTMSB 0x0516 +#define NPM1300_ADCVBAT1RESULTMSB 0x0517 +#define NPM1300_ADCVBAT2RESULTMSB 0x0518 +#define NPM1300_ADCVBAT3RESULTMSB 0x0519 +#define NPM1300_ADCGP1RESULTLSBS 0x051A +#define NPM1300_ADCIBATMEASEN 0x0524 + +// GPIO registers + +#define NPM1300_GPIOMODE0 0x0600 +#define NPM1300_GPIOMODE1 0x0601 +#define NPM1300_GPIOMODE2 0x0602 +#define NPM1300_GPIOMODE3 0x0603 +#define NPM1300_GPIOMODE4 0x0604 +#define NPM1300_GPIODRIVE0 0x0605 +#define NPM1300_GPIODRIVE1 0x0606 +#define NPM1300_GPIODRIVE2 0x0607 +#define NPM1300_GPIODRIVE3 0x0608 +#define NPM1300_GPIODRIVE4 0x0609 +#define NPM1300_GPIOPUEN0 0x060A +#define NPM1300_GPIOPUEN1 0x060B +#define NPM1300_GPIOPUEN2 0x060C +#define NPM1300_GPIOPUEN3 0x060D +#define NPM1300_GPIOPUEN4 0x060E +#define NPM1300_GPIOPDEN0 0x060F +#define NPM1300_GPIOPDEN1 0x0610 +#define NPM1300_GPIOPDEN2 0x0611 +#define NPM1300_GPIOPDEN3 0x0612 +#define NPM1300_GPIOPDEN4 0x0613 +#define NPM1300_GPIOOPENDRAIN0 0x0614 +#define NPM1300_GPIOOPENDRAIN1 0x0615 +#define NPM1300_GPIOOPENDRAIN2 0x0616 +#define NPM1300_GPIOOPENDRAIN3 0x0617 +#define NPM1300_GPIOOPENDRAIN4 0x0618 +#define NPM1300_GPIODEBOUNCE0 0x0619 +#define NPM1300_GPIODEBOUNCE1 0x061A +#define NPM1300_GPIODEBOUNCE2 0x061B +#define NPM1300_GPIODEBOUNCE3 0x061C +#define NPM1300_GPIODEBOUNCE4 0x061D +#define NPM1300_GPIOSTATUS 0x061E + +// Timer registers + +#define NPM1300_TIMERSET 0x0700 +#define NPM1300_TIMERCLR 0x0701 +#define NPM1300_TIMERTARGETSTROBE 0x0703 +#define NPM1300_WATCHDOGKICK 0x0704 +#define NPM1300_TIMERCONFIG 0x0705 +#define NPM1300_TIMERSTATUS 0x0706 +#define NPM1300_TIMERHIBYTE 0x0708 +#define NPM1300_TIMERMIDBYTE 0x0709 +#define NPM1300_TIMERLOBYTE 0x070A + +// LOADSW/LDO registers + +#define NPM1300_TASKLDSW1SET 0x0800 +#define NPM1300_TASKLDSW1CLR 0x0801 +#define NPM1300_TASKLDSW2SET 0x0802 +#define NPM1300_TASKLDSW2CLR 0x0803 +#define NPM1300_LDSWSTATUS 0x0804 +#define NPM1300_LDSW1GPISEL 0x0805 +#define NPM1300_LDSW2GPISEL 0x0806 +#define NPM1300_LDSWCONFIG 0x0807 +#define NPM1300_LDSW1LDOSEL 0x0808 +#define NPM1300_LDSW2LDOSEL 0x0809 +#define NPM1300_LDSW1VOUTSEL 0x080C +#define NPM1300_LDSW2VOUTSEL 0x080D + +// POF registers + +#define NPM1300_POFCONFIG 0x0900 + +// LED registers + +#define NPM1300_LEDDRV0MODESEL 0x0A00 +#define NPM1300_LEDDRV1MODESEL 0x0A01 +#define NPM1300_LEDDRV2MODESEL 0x0A02 +#define NPM1300_LEDDRV0SET 0x0A03 +#define NPM1300_LEDDRV0CLR 0x0A04 +#define NPM1300_LEDDRV1SET 0x0A05 +#define NPM1300_LEDDRV1CLR 0x0A06 +#define NPM1300_LEDDRV2SET 0x0A07 +#define NPM1300_LEDDRV2CLR 0x0A08 + +// Reset and hibernation mode registers + +#define NPM1300_TASKENTERHIBERNATE 0x0B00 +#define NPM1300_TASKSHPHLDCFGSTROBE 0x0B01 +#define NPM1300_TASKENTERSHIPMODE 0x0B02 +#define NPM1300_TASKRESETCFG 0x0B03 +#define NPM1300_SHPHLDCONFIG 0x0B04 +#define NPM1300_SHPHLDSTATUS 0x0B05 +#define NPM1300_LPRESETCONFIG 0x0B06 + +// Reset and error registers + +#define NPM1300_TASKCLRERRLOG 0x0E00 +#define NPM1300_SCRATCH0 0x0E01 +#define NPM1300_SCRATCH1 0x0E02 +#define NPM1300_RSTCAUSE 0x0E03 +#define NPM1300_CHARGERERRREASON 0x0E04 +#define NPM1300_CHARGERERRSENSOR 0x0E05 + +// ------------------------------------------------------ + +// TASKSWRESET +#define NPM1300_TASKSWRESET_TRIGGER 0x01 + +// TASKUPDATEILIM +#define NPM1300_TASKUPDATEILIM_SELVBUSILIM0 1 + +// VBUSINILIM0 +#define NPM1300_VBUSINILIM0_500MA 5 // Default + +// BCHGENABLESET +#define NPM1300_BCHGENABLESET_ENABLECHG 0x01 + +// BCHGENABLECLR +#define NPM1300_BCHGENABLECLR_DISABLECHG 0x01 + +// BGCDISABLECLR +#define NPM1300_BCHGDISABLECLR_ENABLERCHRG 0x01 +#define NPM1300_BCHGDISABLECLR_USENTC 0x02 + +// BCHGVTERM, BCHGVTERMR +#define NPM1300_BCHGVTERM_3V60 2 // Default +#define NPM1300_BCHGVTERM_3V65 3 + +// ADCCONFIG +#define NPM1300_ADCCONFIG_AUTOENABLE 0x01 +#define NPM1300_ADCCONFIG_BURSTMODE 0x02 + +// ADCNTCRSEL +#define NPM1300_ADCNTCRSEL_HI_Z 0 +#define NPM1300_ADCNTCRSEL_10K 1 // Default +#define NPM1300_ADCNTCRSEL_47K 2 +#define NPM1300_ADCNTCRSEL_100K 3 + +// ADCIBATMEASEN +#define NPM1300_ADCIBATMEASEN_IBATMEASENABLE 0x01 + +// BCHGVTRICKLESEL +#define NPM1300_BCHGVTRICKLESEL_2V9 0 // Default +#define NPM1300_BCHGVTRICKLESEL_2V5 1 + +// BCHGITERMSEL +#define NPM1300_BCHGITERMSEL_SEL10 0 // Default +#define NPM1300_BCHGITERMSEL_SEL20 1 + +// LEDDRVxMODESEL + +#define NPM1300_LEDDRVMODESEL_ERROR 0x00 +#define NPM1300_LEDDRVMODESEL_CHARGING 0x01 +#define NPM1300_LEDDRVMODESEL_HOST 0x02 +#define NPM1300_LEDDRVMODESEL_NOTUSED 0x03 + +// GPIOMODEx + +#define NPM1300_GPIOMODE_GPIINPUT 0x00 +#define NPM1300_GPIOMODE_GPILOGIC1 0x01 +#define NPM1300_GPIOMODE_GPILOGIC0 0x02 +#define NPM1300_GPIOMODE_GPIEVENTRISE 0x03 +#define NPM1300_GPIOMODE_GPIEVENTFALL 0x04 +#define NPM1300_GPIOMODE_GPOIRQ 0x05 +#define NPM1300_GPIOMODE_GPORESET 0x06 +#define NPM1300_GPIOMODE_GPOPLW 0x07 +#define NPM1300_GPIOMODE_GPOLOGIC1 0x08 +#define NPM1300_GPIOMODE_GPOLOGIC9 0x09 + +#endif // TREZORHAL_NPM1300_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 19012a721a..fa5cca545d 100644 --- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py +++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py @@ -147,6 +147,8 @@ def configure( ("USE_RESET_TO_BOOT", "1"), ] + sources += ["embed/sys/powerctl/npm1300/npm1300.c"] + env.get("ENV")["LINKER_SCRIPT"] = linker_script defs = env.get("CPPDEFINES_IMPLICIT")