diff --git a/drivers/micropython.cmake b/drivers/micropython.cmake index 2c310eb..ea024dd 100644 --- a/drivers/micropython.cmake +++ b/drivers/micropython.cmake @@ -13,5 +13,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/tildagon_usb/tildagon_usb.cmake) # Add TCA9548A I2C MUX and micropython machine.I2C bindings include(${CMAKE_CURRENT_LIST_DIR}/tildagon_i2c/tildagon_i2c.cmake) +# Add PMIC and usb PD and micropython power bindings +include(${CMAKE_CURRENT_LIST_DIR}/tildagon_power/tildagon_power.cmake) + # Add OTA helpers include(${CMAKE_CURRENT_LIST_DIR}/ota/micropython.cmake) diff --git a/drivers/tildagon_i2c/tildagon_i2c.c b/drivers/tildagon_i2c/tildagon_i2c.c index bcec9a4..25cd2d6 100644 --- a/drivers/tildagon_i2c/tildagon_i2c.c +++ b/drivers/tildagon_i2c/tildagon_i2c.c @@ -36,7 +36,6 @@ #include "driver/i2c.h" #include "hal/i2c_ll.h" -#include "tca9548a.h" #include "tildagon_i2c.h" @@ -48,16 +47,23 @@ #define MP_I2C_MUX_PORT_MAX (7) -typedef struct _tildagon_mux_i2c_obj_t { - mp_obj_base_t base; - const tca9548a_i2c_mux_t *mux; - tca9548a_i2c_port_t port; -} tildagon_mux_i2c_obj_t; static tildagon_mux_i2c_obj_t tildagon_mux_i2c_obj[8]; static tca9548a_i2c_mux_t tildagon_i2c_mux; +tildagon_mux_i2c_obj_t* tildagon_get_mux_obj( uint8_t port ) +{ + if ( tildagon_mux_i2c_obj[port].base.type == NULL ) + { + // Created for the first time + tildagon_mux_i2c_obj[port].base.type = &machine_i2c_type; + tildagon_mux_i2c_obj[port].mux = tildagon_get_i2c_mux(); + tildagon_mux_i2c_obj[port].port = port; + } + return &tildagon_mux_i2c_obj[port]; +} + const tca9548a_i2c_mux_t *tildagon_get_i2c_mux() { return &tildagon_i2c_mux; } @@ -81,16 +87,11 @@ void tildagon_i2c_init() { i2c_driver_install(TILDAGON_HOST_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0); } -int tildagon_mux_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { - tildagon_mux_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (addr == self->mux->addr) { - return -MP_ENODEV; - } - - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +int tildagon_mux_i2c_transaction(tildagon_mux_i2c_obj_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + int data_len = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { i2c_master_start(cmd); @@ -134,6 +135,16 @@ int tildagon_mux_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, m return data_len; } +int tildagon_mux_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + tildagon_mux_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (addr == self->mux->addr) { + return -MP_ENODEV; + } + + return tildagon_mux_i2c_transaction( self, addr, n, bufs, flags); +} + /******************************************************************************/ // MicroPython bindings for machine API diff --git a/drivers/tildagon_i2c/tildagon_i2c.h b/drivers/tildagon_i2c/tildagon_i2c.h index 7cb42e1..84b5be1 100644 --- a/drivers/tildagon_i2c/tildagon_i2c.h +++ b/drivers/tildagon_i2c/tildagon_i2c.h @@ -1,6 +1,11 @@ #ifndef _MICROPY_PY_TILDAGON_I2C #define _MICROPY_PY_TILDAGON_I2C + +#include "tca9548a.h" +#include "driver/i2c.h" +#include "extmod/modmachine.h" + #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 #define I2C_SCLK_FREQ XTAL_CLK_FREQ #elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 @@ -15,9 +20,18 @@ #define TILDAGON_HOST_I2C_PORT (0) #define TILDAGON_HOST_I2C_TIMEOUT (50000) +typedef struct _tildagon_mux_i2c_obj_t { + mp_obj_base_t base; + const tca9548a_i2c_mux_t *mux; + tca9548a_i2c_port_t port; +} tildagon_mux_i2c_obj_t; + const tca9548a_i2c_mux_t *tildagon_get_i2c_mux(); +tildagon_mux_i2c_obj_t *tildagon_get_mux_obj( uint8_t port ); + void tildagon_i2c_init(); +int tildagon_mux_i2c_transaction(tildagon_mux_i2c_obj_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); #endif // _MICROPY_PY_TILDAGON_I2C diff --git a/drivers/tildagon_power/bq25895/bq25895.c b/drivers/tildagon_power/bq25895/bq25895.c new file mode 100644 index 0000000..7016625 --- /dev/null +++ b/drivers/tildagon_power/bq25895/bq25895.c @@ -0,0 +1,194 @@ +/* + BQ25895 power management ic driver in c + +The driver supports basic read write access with some helpers and setup + +*/ + +#include "bq25895.h" + +typedef struct +{ + uint8_t regaddr; + uint8_t mask; + uint8_t position; +} bq_register_t; + +typedef struct +{ + uint8_t regaddr; + uint8_t mask; + uint8_t position; + float scaling; + float offset; +} scaled_register_t; + +#define ADDRESS 0x6A +#define READ ( MP_MACHINE_I2C_FLAG_WRITE1 | MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP ) +#define WRITE MP_MACHINE_I2C_FLAG_STOP + +static const scaled_register_t input_Ilim = { 0x00U, 0x3FU, 0U, 50.0F, 100.0F }; +static const bq_register_t Ilim_pin = { 0x00U, 0x40U, 6U }; +static const bq_register_t enable_HiZ = { 0x00U, 0x80U, 7U }; +static const bq_register_t otg_boost = { 0x03U, 0x20U, 5U }; +static const bq_register_t batfet_disable = { 0x09U, 0x20U, 5U }; +static const bq_register_t register_reset = { 0x14U, 0x80U, 7U }; + +static void write_bits( bq_state_t* state, bq_register_t reg, uint8_t value ); +static void write_scaled( bq_state_t* state, scaled_register_t scaledregister, float value ); + +/** + * @brief initialise the bq25895 + * @details reset then setup 500mA Iin limit, boost disabled, charging enabled, + * ADC at 1Hz, disable unused features and disable watchdog to reduce interruptions + * @param state pmic object + */ +void bq_init( bq_state_t* state ) +{ + write_bits( state, register_reset, 1 ); + uint8_t write_buffer[3] = { 0x02, 0x60, 0x1A }; + mp_machine_i2c_buf_t buffer = { .len = 3, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + write_buffer[0] = 0x07; + write_buffer[1] = 0x8C; + buffer.len = 2; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); +} + +/** + * @brief Put the converter into High impedance mode on the input to prevent + * current draw or take it out of HiZ mode + * @param state pmic object + * @param enable uint8_t if > 0 enable the high impedance mode, if 0 disable + * the high impedance mode + */ +void bq_enable_HiZ_input( bq_state_t* state,uint8_t enable ) +{ + if (enable > 1) + { + enable = 1; + } + write_bits( state, enable_HiZ, enable ); +} + +/** + * @brief Control the boost output + * @param state pmic object + * @param enable uint8_t if > 0 enable boost converter + * if 0 disable the boost converter + */ +void bq_enable_boost( bq_state_t* state, uint8_t enable ) +{ + if (enable > 1) + { + enable = 1; + } + write_bits( state, otg_boost, enable ); +} + +/** + * @brief Disconnect the battery from the IC + * @param state pmic object + */ +void bq_disconnect_battery( bq_state_t* state ) +{ + write_bits( state, batfet_disable, 1 ); +} + +/** + * @brief Set the Input current limit + * @param state pmic object + * @param limit float Limit in mA, range 100-3250mA resolution 50mA + */ +void bq_set_input_current_limit( bq_state_t* state, float limit ) +{ + write_scaled( state, input_Ilim, limit ); + if (limit > 1500.0F) + { + write_bits( state, Ilim_pin, 1 ); + } + else + { + write_bits( state, Ilim_pin, 0 ); + } +} + +/** + * @brief update the status + * @param state pmic object +*/ +void bq_update_state( bq_state_t* state ) +{ + uint8_t address = 0x0BU; + uint8_t read_buffer[8]; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = 8, .buf = read_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + state->status = read_buffer[0]; + state->fault = read_buffer[1]; + if ( ( read_buffer[3] & 0x7F ) == 0U ) + { + state->vbat = 0.0F; + } + else + { + state->vbat = ((float)( read_buffer[3] & 0x7F) * 0.02F ) + 2.304F; + } + if ( ( read_buffer[4] & 0x7F ) == 0U ) + { + state->vsys = 0.0F; + } + else + { + state->vsys = ((float)( read_buffer[4] & 0x7F) * 0.02F ) + 2.304F; + } + if ( ( read_buffer[6] & 0x7F) == 0U ) + { + state->vbus = 0.0F; + } + else + { + state->vbus = ((float)( read_buffer[6] & 0x7F) * 0.10F ) + 2.600F; + } + state->ichrg = ((float)( read_buffer[7] & 0x7F) * 0.05F ); +} + +/** + * @brief read modify write a single register entry + * @param state pmic object + * @param reg bq_register_t details of the register to read + * @param value uint to convert to a register entry + */ +inline void write_bits( bq_state_t* state, bq_register_t reg, uint8_t value ) +{ + uint8_t regVal = 0U; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = ®.regaddr }, + { .len = 1, .buf = ®Val } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + regVal = regVal & (~reg.mask); + regVal = regVal | ( value << reg.position ); + uint8_t write_buffer[2] = { reg.regaddr, regVal }; + buffer[0].len = 2; + buffer[0].buf = write_buffer; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, buffer, WRITE ); + } + +/** + * @brief read modify write a value from a register after applying scaling and offset. + * @param state pmic object + * @param scaledregister scaled_register_t details of the register to read + * @param value float to convert to a register entry + */ +inline void write_scaled( bq_state_t* state, scaled_register_t scaledregister, float value ) +{ + uint8_t regVal = 0; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &scaledregister.regaddr }, + { .len = 1, .buf = ®Val } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + regVal = regVal & (~scaledregister.mask); + regVal |= ( (int)(( value - scaledregister.offset ) / scaledregister.scaling ) << scaledregister.position ) & scaledregister.mask; + uint8_t write_buffer[2] = { scaledregister.regaddr, regVal }; + ((mp_machine_i2c_buf_t*)&buffer[0])->len = 2; + ((mp_machine_i2c_buf_t*)&buffer[0])->buf = write_buffer; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, buffer, WRITE ); +} diff --git a/drivers/tildagon_power/bq25895/bq25895.h b/drivers/tildagon_power/bq25895/bq25895.h new file mode 100644 index 0000000..7a31850 --- /dev/null +++ b/drivers/tildagon_power/bq25895/bq25895.h @@ -0,0 +1,79 @@ + +#ifndef BQ25895_H +#define BQ25895_H + +#include +#include "tildagon_i2c.h" + +typedef enum +{ + BQ_NOTCHARGING = 0x00, + BQ_PRECHARGING = 0x08, + BQ_FASTCHARGING = 0x10, + BQ_TERMINATED = 0x18 +} bq_charge_status_t; + +typedef enum +{ + BQ_FAULT_NONE = 0x00, + BQ_FAULT_INPUT = 0x04, + BQ_FAULT_THERMAL = 0x08, + BQ_FAULT_TIMER = 0x0C, +} bq_charge_fault_t; + +#define BQ_CHARGE_STAT_MASK (uint8_t)0x18U +#define BQ_FAULT_BOOST_MASK (uint8_t)0x08U +#define BQ_FAULT_CHARGE_MASK (uint8_t)0x30U +#define BQ_FAULT_BATTERY_MASK (uint8_t)0x40U + +typedef struct +{ + float vbus; + float vsys; + float vbat; + float ichrg; + uint8_t fault; + uint8_t status; + tildagon_mux_i2c_obj_t* mux_port; +} bq_state_t; + +/** + * @brief initialise the bq25895 + * @details reset then setup 500mA Iin limit, boost disabled, charging enabled, + * ADC at 1Hz, disable unused features and disable watchdog to reduce interruptions + * @param state pmic object + */ +extern void bq_init( bq_state_t* state ); +/** + * @brief Put the converter into High impedance mode on the input to prevent + * current draw or take it out of HiZ mode + * @param state pmic object + * @param enable uint8_t if > 0 enable the high impedance mode, if 0 disable + * the high impedance mode + */ +extern void bq_enable_HiZ_input( bq_state_t* state, uint8_t enable ); +/** + * @brief Control the boost output + * @param state pmic object + * @param enable uint8_t if > 0 enable boost converter + * if 0 disable the boost converter + */ +extern void bq_enable_boost( bq_state_t* state, uint8_t enable ); +/** + * @brief Disconnect the battery from the IC + * @param state pmic object + */ +extern void bq_disconnect_battery( bq_state_t* state ); +/** + * @brief Set the Input current limit + * @param state pmic object + * @param limit float Limit in mA, range 100-3250mA resolution 50mA + */ +extern void bq_set_input_current_limit( bq_state_t* state, float limit ); +/** + * @brief update the status + * @param state pmic object +*/ +extern void bq_update_state( bq_state_t* state ); + +#endif /* BQ25895_H */ diff --git a/drivers/tildagon_power/fusb302b/fusb302b.c b/drivers/tildagon_power/fusb302b/fusb302b.c new file mode 100644 index 0000000..94a94ff --- /dev/null +++ b/drivers/tildagon_power/fusb302b/fusb302b.c @@ -0,0 +1,392 @@ +/* +FUSB302 Programmable USB Type-C Controller w/PD driver in MicroPython. + +The driver supports basic read write access with some helpers + +*/ +#include "fusb302b.h" +#include "fusb302b_pd.h" + +typedef struct +{ + uint8_t regaddr; + uint8_t mask; + uint8_t position; +} fusb_register_t; + + +#define ADDRESS 0x22 +#define READ ( MP_MACHINE_I2C_FLAG_WRITE1 | MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP ) +#define WRITE MP_MACHINE_I2C_FLAG_STOP +static const fusb_register_t select_cc = { 0x02U, 0x0CU, 2U }; +static const fusb_register_t enable_bmc = { 0x03U, 0x03U, 0U }; +static const fusb_register_t auto_crc = { 0x03U, 0x04U, 2U }; +static const fusb_register_t tx_flush = { 0x06U, 0x40U, 6U }; +static const fusb_register_t rx_flush = { 0x07U, 0x04U, 2U }; +static const fusb_register_t mask_bc_level = { 0x0AU, 0x01U, 0U }; +static const fusb_register_t mask_comparator_change = { 0x0AU, 0x20U, 5U }; +static const fusb_register_t enable_oscillator = { 0x0BU, 0x01U, 0U }; +static const fusb_register_t register_reset = { 0x0CU, 0x01U, 0U }; +static const fusb_register_t pd_reset = { 0x0CU, 0x02U, 1U }; +static const fusb_register_t mask_hardreset_int = { 0x0EU, 0x01U, 0U }; +static const fusb_register_t mask_softreset_int = { 0x0EU, 0x02U, 1U }; +static const fusb_register_t mask_tx_sent_int = { 0x0EU, 0x04U, 2U }; +static const fusb_register_t mask_retry_fail_int = { 0x0EU, 0x10U, 4U }; +static const fusb_register_t mask_toggle_done_int = { 0x0EU, 0x40U, 6U }; +static const fusb_register_t mask_good_crc_sent_int = { 0x0FU, 0x01U, 0U }; +static const fusb_register_t rx_fifo_empty = { 0x41U, 0x20U, 5U }; + +static void power_up( fusb_state_t* state ); +static uint8_t read_bits( fusb_state_t* state, fusb_register_t reg ); +static void write_bits( fusb_state_t* state, fusb_register_t reg, uint8_t value ); + +/** + * @brief Initialise the fusb302 to a device + * @details reset the device then set comparator threshold to 2.226V, enable Vbus measurment, + * flush buffers, enable interrupts, 3 retries and Vbus, BC level and good crc interrupts, enable toggle + * @param state the port object + */ +void fusb_setup_device( fusb_state_t* state ) +{ + write_bits( state, register_reset, 1 ); + power_up(state); + uint8_t write_buffer[13] = { 0x04, 0x3E, 0x60, 0x44, 0x04, 0xA5, 0x07, 0x7F, 0x07, 0x00, 0x0F, 0x3F, 0x00 }; + mp_machine_i2c_buf_t buffer = { .len = 13, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + state->host = 0U; + state->input_current_limit = 500U; + state->cc_select = 0U; +} + +/** + * @brief Initialise the fusb302 to a host + * @details reset then setup pull ups, data roles, cc measurement level, enable toggle + * and toggle interrupt, 1.5A current limit, 3 auto retries and flush buffers + * @param state the port object + */ +void fusb_setup_host( fusb_state_t* state ) +{ + write_bits( state, register_reset, 1 ); + power_up(state); + uint8_t write_buffer[15] = { 0x02, 0xC0, 0xB0, 0x25, 0x60, 0x48, 0x04, 0xA7, 0x07, 0xFF, 0x07, 0x00, 0x0F, 0xB0, 0x01 }; + mp_machine_i2c_buf_t buffer = { .len = 15, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + state->cc_select = 0U; + state->host = 1U; +} + +/** + * @brief set the measurement to a cc line + * @param state the port object + * @param cc_select the pin to measure on + */ +void fusb_set_cc( fusb_state_t* state, uint8_t cc_select ) +{ + write_bits( state, select_cc, cc_select ); +} + +/** + * @brief disble toggle + * @param state the port object + */ +void fusb_stop_toggle( fusb_state_t* state ) +{ + uint8_t write_buffer[2] = { 0x08, 0x00 }; + mp_machine_i2c_buf_t buffer = { .len = 2, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); +} + +/** + * @brief status read from the device + * @param state the port object + */ +void fusb_get_status( fusb_state_t* state ) +{ + uint8_t read_buffer[2]; + uint8_t address = 0x40; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = 2, .buf = read_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + state->status = read_buffer[0] | ( (uint16_t)read_buffer[1] << 8 ); +} + +/** + * @brief statusa read from the device + * @param state the port object + */ +void fusb_get_statusa( fusb_state_t* state ) +{ + uint8_t read_buffer[2]; + uint8_t address = 0x3C; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = 2, .buf = read_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + state->statusa = read_buffer[0] | ( (uint16_t)read_buffer[1] << 8 ); +} + +/** + * @brief read interrupt a and b registers + * @param state the port object + */ +uint16_t fusb_get_interruptab( fusb_state_t* state ) +{ + uint8_t read_buffer[2]; + uint8_t address = 0x3E; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = 2, .buf = read_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + return read_buffer[0] | ( (uint16_t)read_buffer[1] << 8 ); +} + +/** + * @brief read interrupt register + * @param state the port object + */ +uint8_t fusb_get_interrupt( fusb_state_t* state ) +{ + uint8_t read_buffer; + uint8_t address = 0x42; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = 1, .buf = &read_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + return read_buffer; +} + +/** + * @brief setup the PD to send the good CRC packet automatically + * @param state the port object + */ +void fusb_auto_good_crc ( fusb_state_t* state ) +{ + write_bits( state, auto_crc, 1 ); +} + +/** + * @brief flush both the rx and tx buffers + * @param state the port object + */ +void fusb_flush_buffers( fusb_state_t* state ) +{ + write_bits( state, tx_flush, 1 ); + write_bits( state, rx_flush, 1 ); +} + +/** + * @brief mask toggle interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_toggle( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_toggle_done_int, value ); +} + +/** + * @brief mask comparator interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_comp( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_comparator_change, value ); +} + +/** + * @brief mask BC level interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_bclevel( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_bc_level, value ); +} + +/** + * @brief mask PD good CRC interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_gdcrc( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_good_crc_sent_int, value ); +} + +/** + * @brief mask PD soft reset interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_softreset( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_softreset_int, value ); +} + +/** + * @brief mask PD hard reset interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_hardreset( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_hardreset_int, value ); +} + +/** + * @brief mask PD retry failure interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_retryfail( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_retry_fail_int, value ); +} + +/** + * @brief mask PD tx sent got a good crc interrupt + * @param state the port object + * @param value of mask + */ +void fusb_mask_interrupt_txsent( fusb_state_t* state, uint8_t value ) +{ + write_bits( state, mask_tx_sent_int, value ); +} + +/** + * @brief enable or disable comms based on cc_select + * @param state the port object + */ +void fusb_setup_pd( fusb_state_t* state ) +{ + if ( state->cc_select ) + { + uint8_t write_buffer[2] = { 0x08, 0x04 }; + mp_machine_i2c_buf_t buffer = { .len = 2, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + write_buffer[0] = mask_good_crc_sent_int.regaddr; + write_buffer[1] = 0x00; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + + write_bits( state, rx_flush, 1 ); + uint8_t data = 0x20; + if ( state->cc_select == 1 ) + { + data |= 0x01; + } + else + { + data |= 0x02; + } + if ( state->host == 0 ) + { + data |= 0x84; + } + else + { + data |= 0x04; + } + write_buffer[0] = enable_bmc.regaddr; + write_buffer[1] = data; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + write_bits( state, tx_flush, 1 ); + write_bits( state, rx_flush, 1 ); + write_buffer[0] = 0x0B; + write_buffer[1] = 0x0F; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + write_buffer[0] = pd_reset.regaddr; + write_buffer[1] = 0x02; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); + } + else + { + write_bits( state, auto_crc, 0 ); + write_bits( state, mask_good_crc_sent_int, 1 ); + write_bits( state, enable_bmc, 0 ); + } +} + +/** + * @brief are bytes available + * @param state the port object + */ +uint8_t fusb_rx_empty( fusb_state_t* state ) +{ + return read_bits( state, rx_fifo_empty ); +} + +/** + * @brief get the contents of the rx fifo + * @param state the port object + * @param rx_buffer buffer to return the data of size length + * @param length amount of data + */ +void fusb_get_fifo(fusb_state_t* state, uint8_t* rx_buffer, uint8_t length ) +{ + uint8_t address = 0x43; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = &address }, + { .len = length, .buf = rx_buffer } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); +} + +/** + * @brief send a message to the tx fifo of up to PD_MAX_TX_MSG_SIZE bytes + * @param state the port object + * @param message buffer to send, must be prefixed with TX_FIFO_ADDRESS. + * @param length amount of data + */ +void fusb_send( fusb_state_t* state, uint8_t* message, uint8_t length ) +{ + mp_machine_i2c_buf_t buffer = { .len = length, .buf = message }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); +} + +/** + * @brief Turn on parts of the fusb hardware + * @param state the port object + */ +void power_up( fusb_state_t* state ) +{ + /* + bit 0: Bandgap and wake circuit. + bit 1: Receiver powered and current references for Measure block + bit 2: Measure block powered. + bit 3: Enable internal oscillator. + */ + uint8_t write_buffer[2] = { enable_oscillator.regaddr, 0x07 }; + mp_machine_i2c_buf_t buffer = { .len = 2, .buf = write_buffer }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, &buffer, WRITE ); +} + +/** + * @brief Returns a value from the selected bits of a register + * @param state the port object + * @param reg fusb_register_t details of the register to read + * @return the value of the register + */ +uint8_t read_bits( fusb_state_t* state, fusb_register_t reg ) +{ + uint8_t regVal = 0; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = ®.regaddr }, + { .len = 1, .buf = ®Val } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + return ( ( regVal & reg.mask) >> reg.position ) ; +} + +/** + * @brief read modify write a single register entry + * @param state the port object + * @param reg fusb_register_t details of the register to read + * @param value uint to convert to a register entry + */ +void write_bits( fusb_state_t* state, fusb_register_t reg, uint8_t value ) +{ + uint8_t regVal = 0; + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = ®.regaddr }, + { .len = 1, .buf = ®Val } }; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 2, buffer, READ ); + regVal = regVal & (~reg.mask); + regVal = regVal | ( value << reg.position ); + uint8_t write_buffer[2] = { reg.regaddr, regVal }; + buffer[0].len = 2; + buffer[0].buf = write_buffer; + tildagon_mux_i2c_transaction( state->mux_port, ADDRESS, 1, buffer, WRITE ); +} diff --git a/drivers/tildagon_power/fusb302b/fusb302b.h b/drivers/tildagon_power/fusb302b/fusb302b.h new file mode 100644 index 0000000..e9e150c --- /dev/null +++ b/drivers/tildagon_power/fusb302b/fusb302b.h @@ -0,0 +1,159 @@ + +#ifndef FUSB302B_H +#define FUSB302B_H + +#include +#include "tildagon_i2c.h" + +#define FUSB_TXSENT_I_MASK (uint16_t)0x0004U +#define FUSB_TOGGLE_I_MASK (uint16_t)0x0040U +#define FUSB_GD_CRC_I_MASK (uint16_t)0x0100U +#define FUSB_BC_LVL_I_MASK (uint8_t)0x01U +#define FUSB_CMPCHG_I_MASK (uint8_t)0x20U +#define FUSB_VBUSOK_I_MASK (uint8_t)0x80U + +#define FUSB_STATUSA_TOGGLE_MASK (uint16_t)0x3800U +#define FUSB_STATUSA_TOGGLE_SHIFT (int8_t)11 +#define FUSB_STATUS_COMP_MASK (uint16_t)0x0020U +#define FUSB_STATUS_BCLVL_MASK (uint16_t)0x0003U +#define FUSB_STATUS_VBUSOK_MASK (uint16_t)0x0080U + +typedef struct +{ + tildagon_mux_i2c_obj_t* mux_port; + uint8_t cc_select; + uint16_t input_current_limit; + uint16_t status; + uint16_t statusa; + uint8_t host; +} fusb_state_t; + +/** + * @brief Initialise the fusb302 to a device + * @details reset the device then set comparator threshold to 2.226V, enable Vbus measurment, + * flush buffers, enable interrupts, 3 retries and Vbus, BC level and good crc interrupts, enable toggle + * @param state the port object + */ +extern void fusb_setup_device( fusb_state_t* state ); +/** + * @brief Initialise the fusb302 to a host + * @details reset then setup pull ups, data roles, cc measurement level, enable toggle + * and toggle interrupt, 1.5A current limit, 3 auto retries and flush buffers + * @param state the port object + */ +extern void fusb_setup_host( fusb_state_t* state ); +/** + * @brief set the measurement to a cc line + * @param state the port object + * @param cc_select the pin to measure on + */ +extern void fusb_set_cc( fusb_state_t* state, uint8_t cc_select ); +/** + * @brief disble toggle + * @param state the port object + */ +extern void fusb_stop_toggle( fusb_state_t* state ); +/** + * @brief status read from the device + * @param state the port object + */ +extern void fusb_get_status( fusb_state_t* state ); +/** + * @brief statusa read from the device + * @param state the port object + */ +extern void fusb_get_statusa( fusb_state_t* state ); +/** + * @brief read interrupt a and b registers + * @param state the port object + */ +extern uint16_t fusb_get_interruptab( fusb_state_t* state ); +/** + * @brief read interrupt register + * @param state the port object + */ +extern uint8_t fusb_get_interrupt( fusb_state_t* state ); +/** + * @brief setup the PD to send the good CRC packet automatically + * @param state the port object + */ +extern void fusb_auto_good_crc ( fusb_state_t* state ); +/** + * @brief flush both the rx and tx buffers + * @param state the port object + */ +extern void fusb_flush_buffers( fusb_state_t* state ); +/** + * @brief mask toggle interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_toggle( fusb_state_t* state, uint8_t value ); +/** + * @brief mask comparator interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_comp( fusb_state_t* state, uint8_t value ); +/** + * @brief mask BC level interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_bclevel( fusb_state_t* state, uint8_t value ); +/** + * @brief mask PD good CRC interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_gdcrc( fusb_state_t* state, uint8_t value ); +/** + * @brief mask PD soft reset interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_softreset( fusb_state_t* state, uint8_t value ); +/** + * @brief mask PD hard reset interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_hardreset( fusb_state_t* state, uint8_t value ); +/** + * @brief mask PD retry failure interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_retryfail( fusb_state_t* state, uint8_t value ); +/** + * @brief mask PD tx sent got a good crc interrupt + * @param state the port object + * @param value of mask + */ +extern void fusb_mask_interrupt_txsent( fusb_state_t* state, uint8_t value ); +/** + * @brief enable or disable comms based on cc_select + * @param state the port object + */ +extern void fusb_setup_pd( fusb_state_t* state ); +/** + * @brief are bytes available + * @param state the port object + */ +extern uint8_t fusb_rx_empty( fusb_state_t* state ); +/** + * @brief get the contents of the rx fifo + * @param state the port object + * @param rx_buffer buffer to return the data of size length + * @param length amount of data + */ +extern void fusb_get_fifo(fusb_state_t* state, uint8_t* rx_buffer, uint8_t length ); +/** + * @brief send a message to the tx fifo of up to PD_MAX_TX_MSG_SIZE bytes + * @param state the port object + * @param message buffer to send + * @param length amount of data + */ +extern void fusb_send( fusb_state_t* state, uint8_t* message, uint8_t length ); + +#endif /* FUSB302B_H */ diff --git a/drivers/tildagon_power/fusb302b/fusb302b_pd.c b/drivers/tildagon_power/fusb302b/fusb302b_pd.c new file mode 100644 index 0000000..1118c11 --- /dev/null +++ b/drivers/tildagon_power/fusb302b/fusb302b_pd.c @@ -0,0 +1,211 @@ + +#include "fusb302b_pd.h" + +#define TX_FIFO_ADDRESS 0x43U +/* FIFO tokens */ +#define TX_ON 0xA1U +#define TX_SOP1 0x12U +#define TX_SOP2 0x13U +#define TX_SOP3 0x1BU +#define TX_RESET1 0x15U +#define TX_RESET2 0x16U +#define TX_PACKSYM 0x80U +#define TX_JAM_CRC 0xFFU +#define TX_EOP 0x14U +#define TX_OFF 0xFEU +#define RX_SOP 0xE0U +#define RX_SOP1 0xC0U +#define RX_SOP2 0xA0U +#define RX_SOP1DB 0x80U +#define RX_SOP2DB 0x60U + +/** + * @brief parse and decode the receive buffer + * @param state the comms state onject + * @param fusb the object for the fusb to use + */ +void fusbpd_decode( pd_state_t* state, fusb_state_t* fusb ) +{ + uint8_t rx_buffer[32]; + /* parse rx buffer */ + while ( !fusb_rx_empty( fusb ) ) + { + fusb_get_fifo( fusb, rx_buffer, 1 ); + if ( rx_buffer[0] == RX_SOP ) + { + fusb_get_fifo( fusb, state->last_rx_header.raw, 2 ); + + if ( state->last_rx_header.sop.number_objects == 0 ) + { + /* handle control messages */ + state->last_rx_control_msg_type = state->last_rx_header.sop.message_type; + } + else + { + uint8_t buffer_index = 0U; + if ( state->last_rx_header.sop.message_type == PD_DATA_SOURCE_CAPABILITIES ) + { + for ( uint8_t i = 8U; i; i-- ) + { + state->pdos[i].raw32 = 0U; + } + fusb_get_fifo( fusb, rx_buffer, ( state->last_rx_header.sop.number_objects * 4) + 4 ); + for ( uint8_t i = 0U; i < state->last_rx_header.sop.number_objects; i++ ) + { + state->pdos[i].raw32 = *((uint32_t*)&rx_buffer[ buffer_index ]); + buffer_index += 4U; + } + state->number_of_pdos = state->last_rx_header.sop.number_objects; + } + else if ( state->last_rx_header.sop.message_type == PD_DATA_VENDOR_DEFINED ) + { + + fusb_get_fifo( fusb, rx_buffer, ( state->last_rx_header.sop.number_objects * 4) + 4 ); + buffer_index += 2U; + uint16_t vendor_id = rx_buffer[ buffer_index ] + ( rx_buffer[ buffer_index + 1 ] << 8 ); + buffer_index += 2U; + if ( vendor_id == PD_VENDOR_ID ) + { + *((uint32_t*)&state->rx_badge_id[0]) = *((uint32_t*)&rx_buffer[buffer_index]); + *((uint32_t*)&state->rx_badge_id[4]) = *((uint32_t*)&rx_buffer[buffer_index + 4]); + buffer_index += 8U; + } + else + { + //todo save this to pass to micropython? + } + } + else + { + } + state->last_rx_data_msg_type = state->last_rx_header.sop.message_type; + } + } + } +} + +/** + * @brief select the highest voltage pdo up to 14V + * @param state the comms state onject + * @return the index of the pdo object + */ +uint8_t fusbpd_select_pdo( pd_state_t* state ) +{ + uint16_t highest_voltage = 5000U; + uint8_t index = 0U; + for ( uint8_t i = 0U; i < state->last_rx_header.sop.number_objects; i++ ) + { + const uint16_t voltage = state->pdos[i].fixed.voltage * 50; + if ( + ( state->pdos[i].fixed.pdo_type == PD_FIXED_SUPPLY ) + && ( voltage > highest_voltage ) + && ( voltage < 14000 ) + ) + { + highest_voltage = voltage; + index = i; + } + } + return index; +} + +/** + * @brief creat a request power message + * @param state the comms state onject + * @param num the index of the pdo list sent from the source + * @param current the current required to run the device + * @param max_current the maximum current required by the device + */ +void fusbpd_request_power( pd_state_t* state, uint8_t num, uint16_t current, uint16_t max_current ) +{ + state->tx_buffer[0] = TX_FIFO_ADDRESS; + state->tx_buffer[1] = TX_SOP1; + state->tx_buffer[2] = TX_SOP1; + state->tx_buffer[3] = TX_SOP1; + state->tx_buffer[4] = TX_SOP2; + state->tx_buffer[5] = TX_PACKSYM | 0x06; + state->tx_buffer[6] = ( 0x01 << 6 ) /* PD 2.0 */ | 0x02 /* request */; + state->tx_buffer[7] = ( 0x01 << 4 ) /* object count */ | ( ( state->msg_id & 0x07 ) << 1 ); + state->msg_id++; + + /* packing max current into fields */ + uint16_t max_current_b = max_current / 10; + state->tx_buffer[8] = max_current_b & 0xFF; + state->tx_buffer[9] = max_current_b >> 8; + + /* packing current into fields */ + uint16_t current_b = current / 10; + uint8_t current_l = current_b & 0x3f; + state->tx_buffer[9] |= current_l << 2; + state->tx_buffer[10] = current_b >> 6; + + state->tx_buffer[11] = ( (num+1) << 4 )/* object position */ | 0x01 /* no suspend */; + state->tx_buffer[12] = TX_JAM_CRC; + state->tx_buffer[13] = TX_EOP; + state->tx_buffer[14] = TX_OFF; + state->tx_buffer[15] = TX_ON; + state->message_length = 16; +} + +/** + * @brief create a request source capabilities message + * @param state the comms state onject + */ +void fusbpd_request_capability( pd_state_t* state ) +{ + state->tx_buffer[0] = TX_FIFO_ADDRESS; + state->tx_buffer[1] = TX_SOP1; + state->tx_buffer[2] = TX_SOP1; + state->tx_buffer[3] = TX_SOP1; + state->tx_buffer[4] = TX_SOP2; + state->tx_buffer[5] = TX_PACKSYM | 0x02; + state->tx_buffer[6] = 0x47U; + state->tx_buffer[7] = 0x00U | ( ( state->msg_id & 0x07 ) << 1 ) ; + state->msg_id++; + state->tx_buffer[8] = TX_JAM_CRC; + state->tx_buffer[9] = TX_EOP; + state->tx_buffer[10] = TX_OFF; + state->tx_buffer[11] = TX_ON; + state->message_length = 12; +} + +/** + * @brief create a vendor specific pdo message with the esp32 unique id + * @param state the comms state onject + */ +void fusbpd_vendor_specific( pd_state_t* state ) +{ + state->tx_buffer[0] = TX_FIFO_ADDRESS; + state->tx_buffer[1] = TX_SOP1; + state->tx_buffer[2] = TX_SOP1; + state->tx_buffer[3] = TX_SOP1; + state->tx_buffer[4] = TX_SOP2; + state->tx_buffer[5] = TX_PACKSYM | 0x0E; + pd_header_union_t header = { 0U }; + header.sop.number_objects = 0x03U; + header.sop.message_id = state->msg_id; + header.sop.message_type = 0x0FU; + header.sop.revision = 0x01U; + header.sop.data_role = state->data_role; + header.sop.power_role = state->power_role; + state->tx_buffer[6] = header.raw[0]; + state->tx_buffer[7] = header.raw[1]; + state->msg_id++; + state->tx_buffer[8] = 0x00U; + state->tx_buffer[9] = 0x00U; + state->tx_buffer[10] = PD_VENDOR_ID >> 8; + state->tx_buffer[11] = PD_VENDOR_ID & 0XFF; + state->tx_buffer[12] = state->badge_id[0]; + state->tx_buffer[13] = state->badge_id[1]; + state->tx_buffer[14] = state->badge_id[2]; + state->tx_buffer[15] = state->badge_id[3]; + state->tx_buffer[16] = state->badge_id[4]; + state->tx_buffer[17] = state->badge_id[5]; + state->tx_buffer[18] = state->badge_id[6]; + state->tx_buffer[19] = state->badge_id[7]; + state->tx_buffer[20] = TX_JAM_CRC; + state->tx_buffer[21] = TX_EOP; + state->tx_buffer[22] = TX_OFF; + state->tx_buffer[23] = TX_ON; + state->message_length = 24; +} diff --git a/drivers/tildagon_power/fusb302b/fusb302b_pd.h b/drivers/tildagon_power/fusb302b/fusb302b_pd.h new file mode 100644 index 0000000..c53c159 --- /dev/null +++ b/drivers/tildagon_power/fusb302b/fusb302b_pd.h @@ -0,0 +1,192 @@ + +#ifndef FUSB302B_PD_H +#define FUSB302B_PD_H + +#include +#include "fusb302b.h" + +/* + PD data structures +*/ +typedef enum +{ + PD_CONTROL_DO_NOT_USE = 0x00U, + PD_CONTROL_GOODCRC = 0x01U, + PD_CONTROL_GOTOMIN = 0x02U, + PD_CONTROL_ACCEPT = 0x03U, + PD_CONTROL_REJECT = 0x04U, + PD_CONTROL_PING = 0x05U, + PD_CONTROL_PS_RDY = 0x06U, + PD_CONTROL_GET_SOURCE_CAP = 0x07U, + PD_CONTROL_GET_SINK_CAP = 0x08U, + PD_CONTROL_DATA_ROLE_SWAP = 0x09U, + PD_CONTROL_POWER_ROLE_SWAP = 0x0AU, + PD_CONTROL_VCONN_SWAP = 0x0BU, + PD_CONTROL_WAIT = 0x0CU, + PD_CONTROL_SOFT_RESET = 0x0DU, + PD_CONTROL_DATA_RESET = 0x0EU, + PD_CONTROL_DATA_RESET_COMPLETE = 0x0FU, + PD_CONTROL_NOT_SUPPORTED = 0x10U, + PD_CONTROL_GET_SRC_CAP_EXTENDED = 0x11U, + PD_CONTROL_GET_STATUS = 0x12U, + PD_CONTROL_FR_SWAP = 0x13U, + PD_CONTROL_GET_PPS_STATUS = 0x14U, + PD_CONTROL_COUNTRY_CODE = 0x15U, +} pd_control_message_type_t; + +typedef enum +{ + PD_DATA_DO_NOT_USE = 0x00U, + PD_DATA_SOURCE_CAPABILITIES = 0x01U, + PD_DATA_REQUEST = 0x02U, + PD_DATA_BIST = 0x03U, + PD_DATA_SINK_CAPABILITIES = 0x04U, + PD_DATA_VENDOR_DEFINED = 0x0FU, +} pd_data_message_types_t; + +/* SOP header */ +typedef struct +{ + uint16_t message_type : 5; + uint16_t data_role : 1; + uint16_t revision : 2; + uint16_t power_role : 1; + uint16_t message_id : 3; + uint16_t number_objects : 3; + uint16_t extended : 1; +} pd_sop_header_t; + +/* SOP' and SOP" header */ +typedef struct +{ + uint16_t message_type : 5; + uint16_t reserved : 1; + uint16_t revision : 2; + uint16_t cable_plug : 1; + uint16_t message_id : 3; + uint16_t number_objects : 3; + uint16_t extended : 1; +} pd_sop_prime_header_t; + +typedef union +{ + uint8_t raw[2]; + pd_sop_header_t sop; + pd_sop_prime_header_t sop_prime; +} pd_header_union_t; + +/* power data objects */ + +typedef struct +{ + uint32_t max_current : 10; /* Maximum Current in 10mA units */ + uint32_t voltage : 10; /* Voltage in 50mV units */ + uint32_t peak_current : 2; /* Peak Current capability */ + uint32_t reserved : 3; /* Reserved – Shall be set to zero. */ + uint32_t dual_role : 1; /* Dual-Role Data */ + uint32_t usb_cooms : 1; /* USB Communications Capable */ + uint32_t Unconstrained : 1; /* Unconstrained Power */ + uint32_t suspend : 1; /* USB Suspend Supported */ + uint32_t drp : 1; /* Dual-Role Power */ + uint32_t pdo_type : 2; /* Fixed supply == 0 */ +} pd_fixed_pdo_t; + +typedef struct +{ + uint32_t max_power : 10; /* Maximum Allowable Power in 250mW units */ + uint32_t min_volt : 10; /* Maximum Voltage in 50mV units */ + uint32_t max_volt : 10; /* Minimum Voltage in 50mV units */ + uint32_t pdo_type : 2; /* Battery == 1 */ +} pd_battery_pdo_t; + +typedef struct +{ + uint32_t max_current : 10; /* Maximum Current in 10mA units */ + uint32_t min_voltage : 10; /* Minimum Voltage in 50mV units */ + uint32_t max_voltage : 10; /* Maximum Voltage in 50mV units */ + uint32_t pdo_type : 2; /* Variable Supply (non-Battery) == 2 */ +} pd_variable_pdo_t; + +typedef union +{ + uint8_t raw[4]; + uint32_t raw32; + pd_fixed_pdo_t fixed; + pd_battery_pdo_t battery; + pd_variable_pdo_t variable; +} pd_source_pdo_union_t; + +typedef struct +{ + uint32_t min_current : 10; + uint32_t current : 10; + uint32_t reserved : 2; + uint32_t epr_cap : 1; + uint32_t uem_suspend : 1; + uint32_t no_suspend : 1; + uint32_t usb_comms : 1; + uint32_t capability_mismatch : 1; + uint32_t give_back : 1; + uint32_t position : 1; +} pd_request_pdo_t; + +typedef union +{ + uint32_t raw; + pd_request_pdo_t bits; +} pd_request_pdo_union_t; + +#define PD_FIXED_SUPPLY 0U +#define PD_BATTERY 1U +#define PD_VARIABLE_SUPPLY 2U +#define PD_MAX_TX_MSG_SIZE 50 +#define PD_VENDOR_ID 0xCDCD + +typedef struct +{ + uint8_t tx_buffer[PD_MAX_TX_MSG_SIZE]; + uint8_t message_length; + uint8_t msg_id; + uint8_t power_role; + uint8_t data_role; + pd_header_union_t last_rx_header; + pd_source_pdo_union_t pdos[8]; + uint8_t number_of_pdos; + pd_control_message_type_t last_rx_control_msg_type; + pd_data_message_types_t last_rx_data_msg_type; + uint8_t badge_id[8]; + uint8_t rx_badge_id[8]; +} pd_state_t; + +/** + * @brief parse and decode the receive buffer + * @param state the comms state onject + * @param fusb the object for the fusb to use + */ +extern void fusbpd_decode( pd_state_t* state, fusb_state_t* fusb ); +/** + * @brief select the highest voltage pdo up to 14V + * @param state the comms state onject + * @return the index of the pdo object + */ +extern uint8_t fusbpd_select_pdo( pd_state_t* state ); +/** + * @brief creat a request power message + * @param state the comms state onject + * @param num the index of the pdo list sent from the source + * @param current the current required to run the device + * @param max_current the maximum current required by the device + */ +extern void fusbpd_request_power( pd_state_t* state, uint8_t num, uint16_t current, uint16_t max_current ); +/** + * @brief create a request source capabilities message + * @param state the comms state onject + */ +extern void fusbpd_request_capability( pd_state_t* state ); +/** + * @brief create a vendor specific pdo message with the esp32 unique id + * @param state the comms state onject + */ +extern void fusbpd_vendor_specific( pd_state_t* state ); + +#endif /* FUSB302B_PD_H */ diff --git a/drivers/tildagon_power/mp_power.c b/drivers/tildagon_power/mp_power.c new file mode 100644 index 0000000..b071a2c --- /dev/null +++ b/drivers/tildagon_power/mp_power.c @@ -0,0 +1,234 @@ +#include "py/obj.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include +#include "tildagon_power.h" + +static mp_obj_t power_enable5V( mp_obj_t a_obj ) +{ + bool enable = (bool)mp_obj_get_int(a_obj); + tildagon_power_enable_5v( enable ); + return MP_OBJ_NEW_SMALL_INT(1); +} + +static MP_DEFINE_CONST_FUN_OBJ_1( enable5V_obj, power_enable5V); + +static mp_obj_t power_Off( void ) +{ + tildagon_power_off(); + return MP_OBJ_NEW_SMALL_INT(1); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Off_obj, power_Off); + +static mp_obj_t power_Vbus( void ) +{ + bq_update_state( &pmic ); + return mp_obj_new_float(pmic.vbus); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Vbus_obj, power_Vbus); + +static mp_obj_t power_Vsys( void ) +{ + bq_update_state( &pmic ); + return mp_obj_new_float(pmic.vsys); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Vsys_obj, power_Vsys); + +static mp_obj_t power_Vbat( void ) +{ + bq_update_state( &pmic ); + return mp_obj_new_float(pmic.vbat); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Vbat_obj, power_Vbat); + +static mp_obj_t power_Icharge( void ) +{ + bq_update_state( &pmic ); + return mp_obj_new_float(pmic.ichrg); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Icharge_obj, power_Icharge); + +static mp_obj_t power_BatteryLevel( void ) +{ + bq_update_state( &pmic ); + return mp_obj_new_float( ( ( pmic.vbat-VBATMIN ) / ( VBATMAX- VBATMIN ) ) * 100.0F ); +} + +static MP_DEFINE_CONST_FUN_OBJ_0( BatteryLevel_obj, power_BatteryLevel); + +static mp_obj_t power_BatteryChargeState( void ) +{ + mp_obj_t charge_state = mp_obj_new_str( "Not Charging", 12 ); + bq_update_state( &pmic ); + switch ( pmic.status & BQ_CHARGE_STAT_MASK ) + { + case BQ_NOTCHARGING: + { + charge_state = mp_obj_new_str( "Not Charging", 12 ); + break; + } + case BQ_PRECHARGING: + { + charge_state = mp_obj_new_str( "Pre-Charging", 12 ); + break; + } + case BQ_FASTCHARGING: + { + charge_state = mp_obj_new_str( "Fast Charging", 13 ); + break; + } + case BQ_TERMINATED: + { + charge_state = mp_obj_new_str( "Terminated", 10 ); + break; + } + } + return charge_state; +} + +static MP_DEFINE_CONST_FUN_OBJ_0( BatteryChargeState_obj, power_BatteryChargeState); + +static mp_obj_t power_SupplyCapabilities( void ) +{ + mp_obj_t capabilities = mp_obj_new_list(0, NULL); + if ( usb_in.pd.number_of_pdos > 0 ) + { + for ( uint8_t i = 0; i < usb_in.pd.number_of_pdos; i++ ) + { + mp_obj_t tuple[3]; + switch ( usb_in.pd.pdos[i].fixed.pdo_type ) + { + case 0: /* fixed supply */ + { + tuple[0] = mp_obj_new_str( "fixed", 5 ); + tuple[1] = mp_obj_new_int( usb_in.pd.pdos[i].fixed.max_current * 10 ); + tuple[2] = mp_obj_new_int( ( usb_in.pd.pdos[i].fixed.voltage * 50 ) / 1000 ); + mp_obj_t capability = mp_obj_new_tuple(3, tuple); + mp_obj_list_append(capabilities, capability); + break; + } + case 1: /* battery */ + { + tuple[0] = mp_obj_new_str( "battery", 7 ); + tuple[1] = mp_obj_new_int( ( usb_in.pd.pdos[i].battery.min_volt * 50 ) / 1000 ); + tuple[2] = mp_obj_new_int( ( usb_in.pd.pdos[i].battery.max_volt * 50 ) / 1000 ); + mp_obj_t capability = mp_obj_new_tuple(3, tuple); + mp_obj_list_append(capabilities, capability); + break; + } + case 2: /* non battery variable supply */ + { + tuple[0] = mp_obj_new_str( "variable", 8 ); + tuple[1] = mp_obj_new_int( usb_in.pd.pdos[i].variable.max_current * 10 ); + tuple[2] = mp_obj_new_int( ( usb_in.pd.pdos[i].variable.max_voltage * 50 ) / 1000 ); + mp_obj_t capability = mp_obj_new_tuple(3, tuple); + mp_obj_list_append(capabilities, capability); + break; + } + default: + { + /* don't add anything to the list */ + } + } + } + } + else + { + /* do something for non pd supplies */ + + if ( usb_in.fusb.status & FUSB_STATUS_VBUSOK_MASK ) + { + bq_update_state( &pmic ); + mp_obj_t tuple[3]; + tuple[0] = mp_obj_new_str( "non-pd", 6 ); + tuple[1] = mp_obj_new_int( usb_in.fusb.input_current_limit ); + tuple[2] = mp_obj_new_int( (int)pmic.vbus ); + mp_obj_t capability = mp_obj_new_tuple(3, tuple); + mp_obj_list_append(capabilities, capability); + } + else + { + mp_obj_t tuple[3]; + tuple[0] = mp_obj_new_str( "disconnected", 12 ); + tuple[1] = mp_obj_new_str( "0", 1 ); + tuple[2] = mp_obj_new_str( "0", 1 ); + mp_obj_t capability = mp_obj_new_tuple(3, tuple); + mp_obj_list_append(capabilities, capability); + } + } + return capabilities; +} + +static MP_DEFINE_CONST_FUN_OBJ_0( SupplyCapabilities_obj, power_SupplyCapabilities); + +static mp_obj_t power_Fault( void ) +{ + mp_obj_t faults = mp_obj_new_dict( 3 ); + if ( pmic.fault & BQ_FAULT_BATTERY_MASK ) + { + mp_obj_dict_store( faults, mp_obj_new_str( "Battery", 7 ), mp_obj_new_str( "Battery Over Voltage", 20 ) ); + } + else + { + mp_obj_dict_store( faults, mp_obj_new_str( "Battery", 7 ), mp_obj_new_str( "Normal", 6 ) ); + } + if ( pmic.fault & BQ_FAULT_BOOST_MASK ) + { + mp_obj_dict_store( faults, mp_obj_new_str( "Boost", 5 ), mp_obj_new_str( "Overloaded or low battery", 25 ) ); + } + else + { + mp_obj_dict_store( faults, mp_obj_new_str( "Boost", 5 ), mp_obj_new_str( "Normal", 6 ) ); + } + switch ( pmic.fault & BQ_FAULT_CHARGE_MASK ) + { + case BQ_FAULT_INPUT: + { + mp_obj_dict_store( faults, mp_obj_new_str( "Charge", 6 ), mp_obj_new_str( "Input Fault", 11 ) ); + break; + } + case BQ_FAULT_TIMER: + { + mp_obj_dict_store( faults, mp_obj_new_str( "Charge", 6 ), mp_obj_new_str( "Safety timer expired", 20 ) ); + break; + } + case BQ_FAULT_THERMAL: /* thermal shutdown ignored */ + case BQ_FAULT_NONE: + default: + { + mp_obj_dict_store( faults, mp_obj_new_str( "Charge", 6 ), mp_obj_new_str( "Normal", 6 ) ); + break; + } + } + return faults; +} + +static MP_DEFINE_CONST_FUN_OBJ_0( Fault_obj, power_Fault); + +static const mp_rom_map_elem_t power_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_power) }, + { MP_ROM_QSTR(MP_QSTR_Enable5V), MP_ROM_PTR(&enable5V_obj) }, + { MP_ROM_QSTR(MP_QSTR_Off), MP_ROM_PTR(&Off_obj) }, + { MP_ROM_QSTR(MP_QSTR_Vin), MP_ROM_PTR(&Vbus_obj) }, + { MP_ROM_QSTR(MP_QSTR_Vsys), MP_ROM_PTR(&Vsys_obj) }, + { MP_ROM_QSTR(MP_QSTR_Vbat), MP_ROM_PTR(&Vbat_obj) }, + { MP_ROM_QSTR(MP_QSTR_Icharge), MP_ROM_PTR(&Icharge_obj) }, + { MP_ROM_QSTR(MP_QSTR_BatteryLevel), MP_ROM_PTR(&BatteryLevel_obj) }, + { MP_ROM_QSTR(MP_QSTR_BatteryChargeState), MP_ROM_PTR(&BatteryChargeState_obj) }, + { MP_ROM_QSTR(MP_QSTR_SupplyCapabilities), MP_ROM_PTR(&SupplyCapabilities_obj) }, + { MP_ROM_QSTR(MP_QSTR_Fault), MP_ROM_PTR(&Fault_obj) }, +}; + +static MP_DEFINE_CONST_DICT(power_module_globals, power_module_globals_table); + +const mp_obj_module_t power_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&power_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_power, power_user_cmodule); diff --git a/drivers/tildagon_power/mp_power_event.c b/drivers/tildagon_power/mp_power_event.c new file mode 100644 index 0000000..f35a353 --- /dev/null +++ b/drivers/tildagon_power/mp_power_event.c @@ -0,0 +1,103 @@ + +#include "py/runtime.h"// debug +#include "py/obj.h" +#include "py/objmodule.h" +#include "py/builtin.h" +#include "mp_power_event.h" + +static mp_obj_t callbacks[MP_POWER_EVENT_MAX] = { NULL }; + +void push_event( mp_power_event_t event ) +{ + if ( callbacks[event] != NULL ) + { + mp_sched_schedule(callbacks[event], mp_const_none); + } +} + +static mp_obj_t mp_power_charge_event_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_CHARGE] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_charge_event_set_cb_obj, mp_power_charge_event_set_cb); + +static mp_obj_t mp_power_fault_event_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_FAULT] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_fault_event_set_cb_obj, mp_power_fault_event_set_cb); + +static mp_obj_t mp_power_host_attach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_HOST_ATTACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_host_attach_set_cb_obj, mp_power_host_attach_set_cb); + +static mp_obj_t mp_power_host_detach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_HOST_DETACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_host_detach_set_cb_obj, mp_power_host_detach_set_cb); + +static mp_obj_t mp_power_device_attach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_DEVICE_ATTACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_device_attach_set_cb_obj, mp_power_device_attach_set_cb); + +static mp_obj_t mp_power_device_detach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_DEVICE_DETACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_device_detach_set_cb_obj, mp_power_device_detach_set_cb); + +static mp_obj_t mp_power_lanyard_attach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_LANYARD_ATTACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_lanyard_attach_set_cb_obj, mp_power_lanyard_attach_set_cb); + +static mp_obj_t mp_power_lanyard_detach_set_cb(mp_obj_t cb) +{ + callbacks[MP_POWER_EVENT_LANYARD_DETACH] = cb; + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mp_power_event_lanyard_detach_set_cb_obj, mp_power_lanyard_detach_set_cb); + + +static const mp_rom_map_elem_t power_event_globals_table[] = +{ + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_power_event) }, + { MP_ROM_QSTR(MP_QSTR_set_charge_cb), MP_ROM_PTR(&mp_power_charge_event_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_fault_cb), MP_ROM_PTR(&mp_power_fault_event_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_host_attach_cb), MP_ROM_PTR(&mp_power_event_host_attach_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_host_detach_cb), MP_ROM_PTR(&mp_power_event_host_detach_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_device_attach_cb), MP_ROM_PTR(&mp_power_event_device_attach_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_device_detach_cb), MP_ROM_PTR(&mp_power_event_device_detach_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_lanyard_attach_cb), MP_ROM_PTR(&mp_power_event_lanyard_attach_set_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_lanyard_detach_cb), MP_ROM_PTR(&mp_power_event_lanyard_detach_set_cb_obj) }, +}; + +static MP_DEFINE_CONST_DICT(power_event_globals, power_event_globals_table); + +const mp_obj_module_t mp_power_events_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&power_event_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_power_event, mp_power_events_cmodule); diff --git a/drivers/tildagon_power/mp_power_event.h b/drivers/tildagon_power/mp_power_event.h new file mode 100644 index 0000000..b7bfd16 --- /dev/null +++ b/drivers/tildagon_power/mp_power_event.h @@ -0,0 +1,22 @@ + +#ifndef MP_POWER_EVENT_H +#define MP_POWER_EVENT_H + +#include "stdint.h" + +typedef enum +{ + MP_POWER_EVENT_CHARGE, + MP_POWER_EVENT_FAULT, + MP_POWER_EVENT_HOST_ATTACH, + MP_POWER_EVENT_HOST_DETACH, + MP_POWER_EVENT_DEVICE_ATTACH, + MP_POWER_EVENT_DEVICE_DETACH, + MP_POWER_EVENT_LANYARD_ATTACH, + MP_POWER_EVENT_LANYARD_DETACH, + MP_POWER_EVENT_MAX +} mp_power_event_t; + +extern void push_event( mp_power_event_t event ); + +#endif diff --git a/drivers/tildagon_power/tildagon_power.c b/drivers/tildagon_power/tildagon_power.c new file mode 100644 index 0000000..c5c0cf2 --- /dev/null +++ b/drivers/tildagon_power/tildagon_power.c @@ -0,0 +1,610 @@ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/idf_additions.h" +#include "esp_task.h" +#include "esp_random.h" +#include "driver/i2c_master.h" +#include "modmachine.h" + +#include "tildagon_power.h" +#include "mp_power_event.h" + +typedef enum +{ + DISABLED, + UNATTACHED, + ATTACHED, + MAX_STATES +} attach_machine_state_t; + +typedef enum +{ + NOT_STARTED = 0x00, + WAITING = 0x01, + POWER_REQUESTED = 0x02, + PSU_READY_RECEIVED = 0x03, + BADGE_TO_BADGE = 0x04, + LANYARD = 0x05, + REQUEST_RETRY_FAIL = 0x06, + VENDOR_SENT = 0x07, + VENDOR_RETRY_FAIL = 0x08, +} pd_machine_state_t; + +typedef enum +{ + NO_EVENT, + INTERRUPT_EVENT, + HOST_TOGGLE, + HOST_ATTACH, + HOST_DETACH, + HOST_GOODCRCSENT, + HOST_TX_SENT, + HOST_MAX_EVENT, + DEVICE_TOGGLE, + DEVICE_ATTACH, + DEVICE_DETACH, + DEVICE_GOODCRCSENT, + DEVICE_TX_SENT, + DEVICE_BC_LEVEL, +} event_t; + +typedef void (*funptr_t)( event_t ); + +void host_disabled_handler( event_t event ); +void host_unattached_handler( event_t event ); +void host_attached_handler ( event_t event ); +void device_disabled_handler ( event_t event ); +void device_unattached_handler( event_t event ); +void device_attached_handler( event_t event ); +void device_pd_machine ( event_t event ); +void generate_events( void ); +void determine_input_current_limit ( usb_state_t* state ); +void clean_in( void ); +void clean_out( void ); + + +funptr_t host_attach_machine[MAX_STATES] = +{ + host_disabled_handler, + host_unattached_handler, + host_attached_handler, +}; + +funptr_t device_attach_machine[MAX_STATES] = +{ + device_disabled_handler, + device_unattached_handler, + device_attached_handler, +}; + +bq_state_t pmic = { 0 }; +usb_state_t usb_in = { 0 }; +usb_state_t usb_out = { 0 }; + +static attach_machine_state_t host_attach_state = DISABLED; +static attach_machine_state_t device_attach_state = DISABLED; +static pd_machine_state_t host_pd_state = NOT_STARTED; +static pd_machine_state_t device_pd_state = NOT_STARTED; +static TaskHandle_t tildagon_power_task_handle = NULL; +static QueueHandle_t event_queue; +bool lanyard_mode = false; +//todo integrate with port expanders +#include "tildagon_i2c.h" +#define READ ( MP_MACHINE_I2C_FLAG_WRITE1 | MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP ) +#define WRITE MP_MACHINE_I2C_FLAG_STOP +void dev_5v_sw( bool enable ) +{ + uint8_t state = 0x00; + uint8_t regVal = 0; + uint8_t regAddr = 0x02; + if ( enable ) + { + state = 0x10; + } + mp_machine_i2c_buf_t buffer[2] = { { .len = 1, .buf = ®Addr }, + { .len = 1, .buf = ®Val } }; + tildagon_mux_i2c_transaction( usb_in.fusb.mux_port, 0x5A, 2, buffer, READ ); + regVal = regVal & 0xEF; + regVal = regVal | state; + uint8_t write_buffer[2] = { 0x02, regVal }; + buffer[0].len = 2; + buffer[0].buf = write_buffer; + tildagon_mux_i2c_transaction( usb_in.fusb.mux_port, 0x5A, 1, buffer, WRITE ); +} + + +/** + * @brief fast rate task to handle the interrupt generated events + */ +void tildagon_power_fast_task(void *param __attribute__((__unused__))) +{ + event_queue = xQueueCreate( 10, sizeof(event_t) ); + usb_in.fusb.mux_port = tildagon_get_mux_obj( 7 ); + usb_out.fusb.mux_port = tildagon_get_mux_obj( 0 ); + pmic.mux_port = tildagon_get_mux_obj( 7 ); + // turn off 5V switch before setting up PMIC as the reset will enable the boost. + dev_5v_sw(false); + //todo replace when port expander interface available + bq_init( &pmic ); + + /* initialise isr */ + //todo move to allow sharing of sys_int isr + machine_pins_init(); + gpio_set_intr_type(GPIO_NUM_10, GPIO_INTR_NEGEDGE ); + gpio_isr_handler_add( GPIO_NUM_10, tildagon_power_interrupt_event, NULL ); + + clean_in(); + clean_out(); + + /* determine the current state */ + fusb_get_status( &usb_in.fusb ); + fusb_get_statusa( &usb_in.fusb ); + if ( usb_in.fusb.statusa & FUSB_STATUSA_TOGGLE_MASK ) + { + const event_t event = DEVICE_TOGGLE; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + + fusb_get_status( &usb_out.fusb ); + fusb_get_statusa( &usb_out.fusb ); + if ( usb_out.fusb.statusa & FUSB_STATUSA_TOGGLE_MASK ) + { + const event_t event = HOST_TOGGLE; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + + bq_update_state( &pmic ); + /* + The interrupt adds an event to the queue to trigger a read over I2C + as the freertos has a thread safe accessor to the queue + */ + if ( event_queue != NULL ) + { + while ( 1 ) + { + event_t event = NO_EVENT; + if ( xQueueReceive(event_queue, &event, portMAX_DELAY) ) + { + if ( ( event == NO_EVENT ) ) + { + /* this may happen if portMAX_DELAY isn't configured to block forever */ + } + else if ( ( event == INTERRUPT_EVENT ) ) + { + generate_events(); + } + else if ( event < HOST_MAX_EVENT ) + { + (*host_attach_machine[host_attach_state])( event ); + } + else if ( !lanyard_mode ) + { + (*device_attach_machine[device_attach_state])( event ); + } + else + { + /* throw away */ + } + } + } + } +} + +/** + * @brief initialise the badge power management task + */ +void tildagon_power_init( void ) +{ + xTaskCreatePinnedToCore(tildagon_power_fast_task, "power_task", 2048, NULL, tskIDLE_PRIORITY+3, &tildagon_power_task_handle, 0); +} + +/** + * @brief add an interrupt event to the queue. intended to be called from an ISR + */ +void tildagon_power_interrupt_event( void* param ) +{ + BaseType_t TaskWoken; + TaskWoken = pdFALSE; + const event_t event = INTERRUPT_EVENT; + xQueueSendToBackFromISR( event_queue, (void*)&event, &TaskWoken ); +} + +/** + * @brief disconnect the battery to allow turn off on usb is disconnect + */ +void tildagon_power_off( void ) +{ + //todo replace 5v off. + dev_5v_sw(false); + bq_disconnect_battery( &pmic ); +} + +/** + * @brief turn the 5V supply on or off + */ +void tildagon_power_enable_5v( bool enable ) +{ + //todo replace when port expander interface available + if ( enable ) + { + bq_enable_boost( &pmic, 1 ); + dev_5v_sw(true); + } + else + { + /* open switch then disable 5V */ + dev_5v_sw(false); + bq_enable_boost( &pmic, 0 ); + bq_enable_HiZ_input( &pmic, 0 ); + } +} + +/** + * @brief handler for host events possible when nothing is attached + */ +void host_disabled_handler( event_t event ) +{ + if( event == HOST_TOGGLE ) + { + host_attach_state = UNATTACHED; + usb_out.fusb.cc_select = ( usb_out.fusb.statusa >> FUSB_STATUSA_TOGGLE_SHIFT ) & 0x03; + fusb_mask_interrupt_toggle( &usb_out.fusb, 1 ); + fusb_stop_toggle(&usb_out.fusb ); + fusb_mask_interrupt_bclevel( &usb_out.fusb, 0 ); + fusb_mask_interrupt_comp( &usb_out.fusb, 0 ); + fusb_set_cc( &usb_out.fusb, usb_out.fusb.cc_select ); + } +} + +/** + * @brief handler for host events to detect attach + */ +void host_unattached_handler( event_t event ) +{ + if( event == HOST_ATTACH ) + { + host_attach_state = ATTACHED; + fusb_mask_interrupt_bclevel( &usb_out.fusb, 1 ); + tildagon_power_enable_5v(true); + fusb_setup_pd(&usb_out.fusb ); + fusb_mask_interrupt_retryfail( &usb_out.fusb, 0 ); + fusb_mask_interrupt_txsent( &usb_out.fusb, 0 ); + fusbpd_vendor_specific( &usb_out.pd ); + fusb_send ( &usb_out.fusb, usb_out.pd.tx_buffer, usb_out.pd.message_length ); + host_pd_state = VENDOR_SENT; + push_event( MP_POWER_EVENT_HOST_ATTACH ); + } +} + +/** + * @brief handler for events while attached + */ +void host_attached_handler( event_t event ) +{ + if( event == HOST_DETACH ) + { + host_attach_state = DISABLED; + clean_out(); + push_event( MP_POWER_EVENT_HOST_DETACH ); + } + else + { + //todo host pd state machine for badge to badge + if ( host_pd_state > NOT_STARTED ) + { + //host_pd_machine( event ); + } + } +} + + +/** + * @brief handler for device events possible when nothing is attached + */ +void device_disabled_handler( event_t event ) +{ + if( event == DEVICE_TOGGLE ) + { + device_attach_state = UNATTACHED; + fusb_mask_interrupt_toggle( &usb_in.fusb, 1 ); + /* use toggle status to get which CC line to use for PD */ + fusb_mask_interrupt_bclevel( &usb_in.fusb, 0 ); + usb_in.fusb.cc_select = ( usb_in.fusb.statusa >> FUSB_STATUSA_TOGGLE_SHIFT ) & 0x03; + fusb_set_cc( &usb_in.fusb, usb_in.fusb.cc_select ); + if ( usb_in.fusb.status & FUSB_STATUS_VBUSOK_MASK ) + { + const event_t event = DEVICE_ATTACH; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + } +} + +/** + * @brief handler for device events to detect attach + */ +void device_unattached_handler( event_t event ) +{ + if ( event == DEVICE_ATTACH ) + { + device_attach_state = ATTACHED; + push_event( MP_POWER_EVENT_DEVICE_ATTACH ); + } + else if ( event == DEVICE_BC_LEVEL ) + { + determine_input_current_limit( &usb_in ); + if ( ( usb_in.fusb.input_current_limit >= 1500 ) && ( device_pd_state == NOT_STARTED ) ) + { + //todo enable device pd + //fusb_setup_pd( &usb_in.fusb ); + device_pd_state = WAITING; + } + fusb_mask_interrupt_bclevel( &usb_in.fusb, 1 ); + } +} + +/** + * @brief handler for device events while attached + */ +void device_attached_handler( event_t event ) +{ + if ( event == DEVICE_DETACH) + { + device_attach_state = DISABLED; + clean_in(); + push_event( MP_POWER_EVENT_DEVICE_DETACH ); + } + else if ( event == DEVICE_BC_LEVEL ) + { + determine_input_current_limit( &usb_in ); + if ( ( usb_in.fusb.input_current_limit >= 1500 ) && ( device_pd_state == NOT_STARTED ) ) + { + //todo enable device pd + //fusb_setup_pd( &usb_in.fusb ); + device_pd_state = WAITING; + } + fusb_mask_interrupt_bclevel( &usb_in.fusb, 1 ); + } + else + { + if ( device_pd_state > NOT_STARTED ) + { + device_pd_machine( event ); + } + } +} + + +/** + * @brief state machine for the device pd comms + */ +void device_pd_machine ( event_t event ) +{ + switch ( device_pd_state ) + { + case WAITING: + { + if ( event == DEVICE_GOODCRCSENT ) + { + fusbpd_decode( &usb_in.pd, &usb_in.fusb ); + if ( usb_in.pd.last_rx_data_msg_type == PD_DATA_SOURCE_CAPABILITIES ) + { + uint8_t index = fusbpd_select_pdo( &usb_in.pd ); + fusbpd_request_power( &usb_in.pd, index, usb_in.pd.pdos[index].fixed.max_current * 10, usb_in.pd.pdos[index].fixed.max_current * 10 ); + fusb_send( &usb_in.fusb, usb_in.pd.tx_buffer, usb_in.pd.message_length ); + usb_in.pd.last_rx_data_msg_type = PD_DATA_DO_NOT_USE; + device_pd_state = POWER_REQUESTED; + } + else if( usb_in.pd.last_rx_data_msg_type == PD_DATA_VENDOR_DEFINED ) + { + /* if vendor pdo received decide on badge to badge and callback? */ + } + } + break; + } + case POWER_REQUESTED: + { + if ( event == DEVICE_GOODCRCSENT ) + { + fusb_auto_good_crc( &usb_in.fusb ); + fusbpd_decode( &usb_in.pd, &usb_in.fusb ); + /* if psu ready move state */ + if ( usb_in.pd.last_rx_control_msg_type == PD_CONTROL_PS_RDY ) + { + device_pd_state = PSU_READY_RECEIVED; + } + } + break; + } + case PSU_READY_RECEIVED: + case BADGE_TO_BADGE: + default: + { + /* stay in this state until detach */ + } + } +} + + +/** + * @brief Determines if the interrupt was a USB event and which one. + */ +void generate_events( void ) +{ + uint8_t prev_status = pmic.status; + uint8_t prev_faut = pmic.fault; + + bq_update_state( &pmic ); + if ( ( pmic.vbus > 2.6 ) && ( pmic.vbus < 4.3 ) ) + { + bq_enable_HiZ_input( &pmic, 1 ); + lanyard_mode = true; + push_event(MP_POWER_EVENT_LANYARD_ATTACH); + } + if ( prev_status == pmic.status ) + { + uint8_t status_change = prev_status ^ pmic.status; + if ( status_change & BQ_CHARGE_STAT_MASK ) + { + push_event( MP_POWER_EVENT_CHARGE ); + } + } + if ( ( prev_faut ^ pmic.fault ) & 0x71 ) + { + push_event( MP_POWER_EVENT_FAULT ); + } + while ( gpio_get_level( GPIO_NUM_10 ) == 0 ) + { + if ( gpio_get_level( GPIO_NUM_10 ) == 0 ) + { + uint16_t interruptab = fusb_get_interruptab( &usb_in.fusb ); + uint8_t interrupt = fusb_get_interrupt( &usb_in.fusb ); + fusb_get_status( &usb_in.fusb ); + fusb_get_statusa( &usb_in.fusb ); + if( interruptab & FUSB_TOGGLE_I_MASK ) + { + const event_t event = DEVICE_TOGGLE; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + if( ( interrupt & FUSB_VBUSOK_I_MASK ) && ( usb_in.fusb.status & FUSB_STATUS_VBUSOK_MASK ) ) + { + const event_t event = DEVICE_ATTACH; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + else if ( ( interrupt & FUSB_VBUSOK_I_MASK ) && ( ( usb_in.fusb.status & FUSB_STATUS_VBUSOK_MASK ) == 0 ) ) + { + const event_t event = DEVICE_DETACH; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + if ( device_pd_state > NOT_STARTED ) + { + if ( interruptab & FUSB_GD_CRC_I_MASK ) + { + const event_t event = DEVICE_GOODCRCSENT; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + if ( interruptab & FUSB_TXSENT_I_MASK ) + { + usb_in.pd.msg_id++; + } + } + else if ( interrupt & FUSB_BC_LVL_I_MASK ) + { + const event_t event = DEVICE_BC_LEVEL; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + } + + if ( gpio_get_level( GPIO_NUM_10 ) == 0 ) + { + uint16_t interruptab = fusb_get_interruptab( &usb_out.fusb ); + uint8_t interrupt = fusb_get_interrupt( &usb_out.fusb ); + fusb_get_status( &usb_out.fusb ); + fusb_get_statusa( &usb_out.fusb ); + + if ( interruptab & FUSB_TOGGLE_I_MASK ) + { + const event_t event = HOST_TOGGLE; + xQueueSendToBack(event_queue, (void*)&event, (TickType_t)0 ); + } + if ( ( ( ( usb_out.fusb.status & FUSB_STATUS_COMP_MASK ) == 0U ) && ( interrupt & FUSB_CMPCHG_I_MASK ) ) + || ( ( interrupt & FUSB_BC_LVL_I_MASK ) && ( ( usb_out.fusb.status & FUSB_STATUS_BCLVL_MASK ) < 3 ) ) ) + { + const event_t event = HOST_ATTACH; + xQueueSendToBack(event_queue, (void*)&event, (TickType_t)0 ); + } + else if ( ( usb_out.fusb.status & FUSB_STATUS_COMP_MASK ) && ( interrupt & FUSB_CMPCHG_I_MASK ) ) + { + const event_t event = HOST_DETACH; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + if( host_pd_state > NOT_STARTED ) + { + if ( interruptab & FUSB_GD_CRC_I_MASK ) + { + const event_t event = HOST_GOODCRCSENT; + xQueueSendToBack(event_queue, (void*)&event , (TickType_t)0 ); + } + if ( interruptab & FUSB_TXSENT_I_MASK ) + { + usb_out.pd.msg_id++; + } + } + } + } +} + +/** + * @brief figure out the input current limit of the supply + */ +void determine_input_current_limit ( usb_state_t* state ) +{ + state->fusb.input_current_limit = 500; + uint16_t bc_level = state->fusb.status & FUSB_STATUS_BCLVL_MASK; + if ( bc_level > 0 ) + { + if ( bc_level == 2 ) + { + state->fusb.input_current_limit = 1500U; + } + else if ( ( bc_level == 3 ) && ( ( state->fusb.status & FUSB_STATUS_COMP_MASK ) == 0 ) ) + { + state->fusb.input_current_limit = 3000U; + } + } + bq_set_input_current_limit( &pmic, (float) state->fusb.input_current_limit ); +} + +/** + * @brief reset the device port to the initial state + */ +void clean_in( void ) +{ + usb_in.fusb.input_current_limit = 500; + bq_set_input_current_limit( &pmic, (float) usb_in.fusb.input_current_limit ); + device_pd_state = NOT_STARTED; + usb_in.fusb.cc_select = 0U; + fusb_setup_device( &usb_in.fusb ); + usb_in.pd.last_rx_control_msg_type = 0U; + usb_in.pd.last_rx_data_msg_type = 0U; + usb_in.pd.number_of_pdos = 0U; + *((uint16_t*)&usb_in.pd.last_rx_header.raw[0]) = 0U; + usb_in.pd.msg_id = 0U; + *((uint32_t*)&usb_in.pd.rx_badge_id[0]) = 0U; + *((uint32_t*)&usb_in.pd.rx_badge_id[4]) = 0U; +} + +/** + * @brief reset the host port to the initial state + */ +void clean_out( void ) +{ + tildagon_power_enable_5v(false); + host_pd_state = NOT_STARTED; + usb_in.fusb.cc_select = 0; + fusb_setup_host( &usb_out.fusb ); + usb_out.pd.last_rx_control_msg_type = 0U; + usb_out.pd.last_rx_data_msg_type = 0U; + usb_out.pd.number_of_pdos = 0U; + usb_out.pd.msg_id = 0U; + + *((uint16_t*)&usb_out.pd.last_rx_header.raw[0]) = 0U; + *((uint32_t*)&usb_out.pd.rx_badge_id[0]) = 0U; + *((uint32_t*)&usb_out.pd.rx_badge_id[4]) = 0U; + + /* setup lanyard and badge to badge numbers */ + esp_fill_random( usb_in.pd.badge_id, 8 ); + for ( uint8_t i = 0; i < 8; i++) + { + usb_out.pd.badge_id[i] = usb_in.pd.badge_id[i]; + } + + if ( lanyard_mode ) + { + lanyard_mode = false; + clean_in(); + push_event(MP_POWER_EVENT_LANYARD_DETACH); + } +} + diff --git a/drivers/tildagon_power/tildagon_power.cmake b/drivers/tildagon_power/tildagon_power.cmake new file mode 100644 index 0000000..6a93a8a --- /dev/null +++ b/drivers/tildagon_power/tildagon_power.cmake @@ -0,0 +1,23 @@ +# Create an INTERFACE library for our C module. +add_library(usermod_tildagon_power INTERFACE) + + +# Add our source files to the lib +target_sources(usermod_tildagon_power INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/tildagon_power.c + ${CMAKE_CURRENT_LIST_DIR}/bq25895/bq25895.c + ${CMAKE_CURRENT_LIST_DIR}/fusb302b/fusb302b.c + ${CMAKE_CURRENT_LIST_DIR}/fusb302b/fusb302b_pd.c + ${CMAKE_CURRENT_LIST_DIR}/mp_power.c + ${CMAKE_CURRENT_LIST_DIR}/mp_power_event.c +) + +# Add the current directory as an include directory. +target_include_directories(usermod_tildagon_power INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/bq25895 + ${CMAKE_CURRENT_LIST_DIR}/fusb302b +) + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE usermod_tildagon_power) \ No newline at end of file diff --git a/drivers/tildagon_power/tildagon_power.h b/drivers/tildagon_power/tildagon_power.h new file mode 100644 index 0000000..a86ec93 --- /dev/null +++ b/drivers/tildagon_power/tildagon_power.h @@ -0,0 +1,44 @@ + +#ifndef TILDAGON_POWER_H +#define TILDAGON_POWER_H + + +#include +#include +#include "bq25895/bq25895.h" +#include "fusb302b/fusb302b_pd.h" +#include "fusb302b/fusb302b.h" + + +#define VBATMAX 4.104F +#define VBATMIN 2.500F + +typedef struct +{ + fusb_state_t fusb; + pd_state_t pd; +} usb_state_t; + +extern bq_state_t pmic; +extern usb_state_t usb_in; +extern usb_state_t usb_out; + +/** + * @brief initialise the badge power management task + */ +extern void tildagon_power_init( void ); +/** + * @brief add an interrupt event to the queue. intended to be called from an ISR + */ +extern void tildagon_power_interrupt_event ( void* param ); +/** + * @brief disconnect the battery to allow turn off on usb is disconnect + */ +extern void tildagon_power_off( void ); +/** + * @brief turn the 5V supply on or off + */ +extern void tildagon_power_enable_5v( bool enable ); + + +#endif diff --git a/modules/bq25895.py b/modules/bq25895.py index e8168a4..28a6ddd 100644 --- a/modules/bq25895.py +++ b/modules/bq25895.py @@ -488,10 +488,9 @@ def init(self): from machine import Pin, I2C from utime import sleep_ms - i2c = I2C(0, scl=Pin(46), sda=Pin(45), freq=400000) + i2c = I2C(7) pin_reset_i2c = Pin(9, Pin.OUT) pin_reset_i2c.on() - i2c.writeto(0x77, bytes([(0x1 << 7)])) pmic = bq25895(i2c) pmic.init() # start 1Hz ADC sampling diff --git a/modules/fusb302b.py b/modules/fusb302b.py index 2b7761e..110cc6b 100644 --- a/modules/fusb302b.py +++ b/modules/fusb302b.py @@ -109,17 +109,17 @@ def __init__(self, i2c_instance): # reg 0E mask a mask_hardreset_int = Register(0x0E, 0x01, 0) mask_softreset_int = Register(0x0E, 0x02, 1) - mask_tx_snet_int = Register(0x0E, 0x04, 2) + mask_tx_sent_int = Register(0x0E, 0x04, 2) mask_hard_sent_int = Register(0x0E, 0x08, 3) - mask_retry_fail_int = Register(0x0E, 0x01, 4) - mask_soft_fail_int = Register(0x0E, 0x02, 5) - mask_toggle_done_int = Register(0x0E, 0x04, 6) - mask_ocp_temp_int = Register(0x0E, 0x08, 7) + mask_retry_fail_int = Register(0x0E, 0x10, 4) + mask_soft_fail_int = Register(0x0E, 0x20, 5) + mask_toggle_done_int = Register(0x0E, 0x40, 6) + mask_ocp_temp_int = Register(0x0E, 0x80, 7) # reg 0F mask b mask_good_crc_sent_int = Register(0x0F, 0x01, 0) # reg 10 Control4 toggle_unattahce_exit = Register(0x10, 0x01, 0) - # reg 3C status a read only + # reg 3C status 0a read only hard_reset_order = Register(0x3C, 0x01, 0) soft_reset_order = Register(0x3C, 0x02, 1) power_state = Register(0x3C, 0x0C, 2) @@ -144,7 +144,7 @@ def __init__(self, i2c_instance): # reg 40 status0 bc_level = Register(0x40, 0x03, 0) wake = Register(0x40, 0x04, 2) - alert = Register(0x40, 0x80, 3) + alert = Register(0x40, 0x08, 3) crc_check = Register(0x40, 0x10, 4) comparator = Register(0x40, 0x20, 5) activity = Register(0x40, 0x40, 6) @@ -323,7 +323,7 @@ def power_up(self): self.ADDRESS, self.enable_oscillator.register, bytes([0x0F]) ) - def get_Status0(self): + def get_status0(self): """ Returns the decoded status0 read from the device Do not use from an ISR @@ -434,7 +434,7 @@ def get_status1a(self): ("RXSOP", 0), ("RXSOP1DB", 0), ("RXSOP2DB", 0), - # ( 'TOGSS', 0 ) + ("TOGSS", 0 ), ] ) status["RXSOP"] = (read & self.rx_sop.mask) >> self.ocp.position @@ -444,7 +444,9 @@ def get_status1a(self): status["RXSOP2DB"] = ( read & self.rx_sop_double_debug.mask ) >> self.rx_sop_double_debug.position - # status['TOGSS'] = ( read & self.toggle_status.mask ) >> self.toggle_status.position + status["TOGSS"] = ( + read & self.toggle_status.mask + ) >> self.toggle_status.position return status def get_interrupts(self): @@ -460,9 +462,9 @@ def get_interrupts(self): Interruptb = self.i2c.readfrom_mem( self.ADDRESS, self.good_crc_sent_int.register, 1 )[0] - Interrupt = self.i2c.readfrom_mem(self.ADDRESS, self.bc_level_int.register, 1)[ - 0 - ] + Interrupt = self.i2c.readfrom_mem( + self.ADDRESS, self.bc_level_int.register, 1 + )[0] current_interrupts = dict( [ ("I_HARDRST", 0), @@ -471,6 +473,7 @@ def get_interrupts(self): ("I_HARDSENT", 0), ("I_RETRYFAIL", 0), ("I_SOFTFAIL", 0), + ("I_TOGDONE", 0 ), ("I_OCP_TEMP", 0), ("I_GCRCSENT", 0), ("I_BC_LVL", 0), @@ -504,6 +507,9 @@ def get_interrupts(self): current_interrupts["I_OCP_TEMP"] = ( Interrupta & self.ocp_temp_int.mask ) >> self.ocp_temp_int.position + current_interrupts['I_TOGDONE '] = ( + Interrupta & self.toggle_done_int.mask + ) >> self.toggle_done_int.position current_interrupts["I_GCRCSENT"] = ( Interruptb & self.good_crc_sent_int.mask ) >> self.good_crc_sent_int.position @@ -549,14 +555,14 @@ def determine_input_current_limit(self): self.write_bits(self.measure_cc, 1) self.cc_select = 1 sleep(0.001) - status = self.get_Status0() + status = self.get_status0() if status["BC_LVL"] == 0: # Ra connected to this CC, change to other CC connection self.cc_select = 2 self.write_bits(self.measure_cc, 2) sleep(0.001) # re-read status - status = self.get_Status0() + status = self.get_status0() # determine current level. self.input_current_limit = 500 if status["BC_LVL"] > 0: @@ -622,12 +628,12 @@ def setup_device(self): self.i2c.writeto_mem(self.ADDRESS, 0x07, bytes([0x04])) # set bits for good crc and auto response last as we need to respond within a timeout. - self.i2c.writeto_mem(self.ADDRESS, 0x03, bytes([0xA4])) + self.i2c.writeto_mem(self.ADDRESS, 0x03, bytes([0x24])) self.host = False def setup_host(self): """ - Initialise the fusb302 to a device + Initialise the fusb302 to a host """ # put device into a known state self.reset() @@ -635,7 +641,7 @@ def setup_host(self): # set power and data roles self.i2c.writeto_mem(self.ADDRESS, 0x03, bytes([0xB0])) # setup pull ups and measure on cc1 - self.i2c.writeto_mem(self.ADDRESS, 0x02, bytes([0xC4])) + self.i2c.writeto_mem(self.ADDRESS, 0x02, bytes([0xC0])) self.cc_select = 1 # set measurement level self.i2c.writeto_mem(self.ADDRESS, 0x04, bytes([0x25])) @@ -689,7 +695,7 @@ def setup_pd(self): def get_rxb(self, length=80): # read the FIFO contents - return self.i2c.readfrom_mem(0x22, 0x43, length) + return self.i2c.readfrom_mem(self.ADDRESS, 0x43, length) def request_pdo(self, num, current, max_current, msg_id=0): sop_seq = [ @@ -729,9 +735,9 @@ def request_pdo(self, num, current, max_current, msg_id=0): sop_seq[4] |= pdo_len - self.i2c.writeto_mem(0x22, 0x43, bytes(sop_seq)) - self.i2c.writeto_mem(0x22, 0x43, bytes(pdo)) - self.i2c.writeto_mem(0x22, 0x43, bytes(eop_seq)) + self.i2c.writeto_mem(self.ADDRESS, self.rxtx_fifo.register, bytes(sop_seq)) + self.i2c.writeto_mem(self.ADDRESS, self.rxtx_fifo.register, bytes(pdo)) + self.i2c.writeto_mem(self.ADDRESS, self.rxtx_fifo.register, bytes(eop_seq)) pdo_types = ["fixed", "batt", "var", "pps"] pps_types = ["spr", "epr", "res", "res"] @@ -739,17 +745,14 @@ def request_pdo(self, num, current, max_current, msg_id=0): def read_pdos(self): pdo_list = [] header = self.get_rxb(1)[0] - print(header) if header == 0xE0: b1, b0 = self.get_rxb(2) pdo_count = (b0 >> 4) & 0b111 read_len = pdo_count * 4 pdos = self.get_rxb(read_len) - print(pdos.hex()) _ = self.get_rxb(4) # crc for pdo_i in range(pdo_count): pdo_bytes = pdos[(pdo_i * 4) :][:4] - print(pdo_bytes.hex()) parsed_pdo = self.parse_pdo(pdo_bytes) pdo_list.append(parsed_pdo) return pdo_list @@ -795,7 +798,7 @@ def parse_pdo(self, pdo): limited, ) - def request_capability(self): + def request_capability(self, msg_id = 0): """ ask for the power supply options """ @@ -804,31 +807,30 @@ def request_capability(self): self.TX_SOP1, self.TX_SOP1, self.TX_SOP2, - self.TX_PACKSYM, + self.TX_PACKSYM | 0x02, 0x47, - 0xE0, + ( 0x00 | ( ( msg_id & 0x07 ) << 1 ) ), self.TX_JAM_CRC, self.TX_EOP, self.TX_OFF, self.TX_ON, ] - self.i2c.writeto_mem(0x22, 0x43, bytes(seq)) + self.i2c.writeto_mem(self.ADDRESS, self.rxtx_fifo.register, bytes(seq)) def soft_reset(self): """ reset the protocol layer on other port """ - self.i2c.writeto_mem(0x22, 0x43, bytes(self.TX_RESET1)) + self.i2c.writeto_mem(self.ADDRESS, self.rxtx_fifo.register, bytes(self.TX_RESET1)) if __name__ == "__main__": from machine import Pin, I2C - i2c = I2C(0, scl=Pin(46), sda=Pin(45), freq=400000) + i2c = I2C(0) pin_reset_i2c = Pin(9, Pin.OUT) pin_reset_i2c.on() - i2c.writeto(0x77, bytes([(0x1 << 7)])) usb_in = fusb302(i2c) usb_in.setup_device() usb_in.determine_input_current_limit() diff --git a/modules/main.py b/modules/main.py index 8ac719a..d0c9958 100644 --- a/modules/main.py +++ b/modules/main.py @@ -6,9 +6,11 @@ from system.patterndisplay.app import PatternDisplay from system.notification.app import NotificationService from system.launcher.app import Launcher +from system.power.handler import PowerEventHandler from frontboards.twentyfour import TwentyTwentyFour + # Start front-board interface scheduler.start_app(TwentyTwentyFour()) @@ -24,7 +26,8 @@ # Start notification handler scheduler.start_app(NotificationService(), always_on_top=True) +PowerEventHandler.RegisterDefaultCallbacks(PowerEventHandler) + Partition.mark_app_valid_cancel_rollback() scheduler.run_forever() - diff --git a/modules/system/power/events.py b/modules/system/power/events.py new file mode 100644 index 0000000..1ae54e8 --- /dev/null +++ b/modules/system/power/events.py @@ -0,0 +1,29 @@ + + + +class PowerEvent: + def __init__(self, EventName): + self.__str__ = EventName + +class RequestChargeEvent(PowerEvent): ... + +class RequestBatFaultEvent(PowerEvent): ... + +class RequestBoostFaultEvent(PowerEvent): ... + +class RequestChargeFaultEvent(PowerEvent): ... + +class RequestTimeoutFaultEvent(PowerEvent): ... + +class RequestHostAttachEvent(PowerEvent): ... + +class RequestHostDetachEvent(PowerEvent): ... + +class RequestDeviceAttachEvent(PowerEvent): ... + +class RequestDeviceDetachEvent(PowerEvent): ... + +class RequestLanyardAttachEvent(PowerEvent): ... + +class RequestLanyardDetachEvent(PowerEvent): ... + diff --git a/modules/system/power/handler.py b/modules/system/power/handler.py new file mode 100644 index 0000000..d93871c --- /dev/null +++ b/modules/system/power/handler.py @@ -0,0 +1,54 @@ +from system.eventbus import eventbus +from system.power import events + +import power_event as pe + + +class PowerEventHandler: + def RegisterDefaultCallbacks(self): + pe.set_charge_cb(self.ChargeEventHandler) + pe.set_device_attach_cb(self.DeviceAttachHandler) + pe.set_device_detach_cb(self.DeviceDetachHandler) + pe.set_fault_cb(self.FaultEventHandler) + pe.set_host_attach_cb(self.HostAttachHandler) + pe.set_host_detach_cb(self.HostDetachHandler) + pe.set_lanyard_attach_cb(self.LanyardAttachHandler) + pe.set_lanyard_detach_cb(self.LanyardDetachHandler) + + def ChargeEventHandler(self): + eventbus.emit( + events.RequestChargeEvent(events.PowerEvent("Charge Cycle change")) + ) + + def FaultEventHandler(self): + eventbus.emit(events.RequestChargeFaultEvent(events.PowerEvent("Charge Fault"))) + + def HostAttachHandler(self): + eventbus.emit( + events.RequestHostAttachEvent(events.PowerEvent("Host attatched")) + ) + + def HostDetachHandler(self): + eventbus.emit( + events.RequestHostDetachEvent(events.PowerEvent("Host Detatched")) + ) + + def DeviceAttachHandler(self): + eventbus.emit( + events.RequestDeviceAttachEvent(events.PowerEvent("Device attatched")) + ) + + def DeviceDetachHandler(self): + eventbus.emit( + events.RequestDeviceDetachEvent(events.PowerEvent("Device detatched")) + ) + + def LanyardAttachHandler(self): + eventbus.emit( + events.RequestLanyardAttachEvent(events.PowerEvent("Lanyard attatched")) + ) + + def LanyardDetachHandler(self): + eventbus.emit( + events.RequestLanyardDetachEvent(events.PowerEvent("Lanyard Detatched")) + ) diff --git a/tildagon/board_init.c b/tildagon/board_init.c index 4306a95..8e57e4c 100644 --- a/tildagon/board_init.c +++ b/tildagon/board_init.c @@ -1,4 +1,6 @@ +#include "tildagon_power.h" + // This is the default startup handler for ESP32, does VFS and stuff void boardctrl_startup(void); @@ -13,8 +15,10 @@ void tildagon_i2c_init(void); void tildagon_startup(void) { // call the micropy default startup - does VFS init on ESP32 boardctrl_startup(); - + + tildagon_i2c_init(); + tildagon_power_init(); + tildagon_usb_init(); - tildagon_i2c_init(); }