/* SPDX-License-Identifier: GPL-3.0-or-later MNT Pocket Reform System Controller Firmware for RP2040 Copyright 2023-2024 MNT Research GmbH fusb_read/write functions based on: https://git.clarahobbs.com/pd-buddy/pd-buddy-firmware/src/branch/master/lib/src/fusb302b.c */ #include #include #include "pico/stdlib.h" #include "pico/binary_info.h" #include "pico/sleep.h" #include "hardware/i2c.h" #include "hardware/spi.h" #include "hardware/irq.h" #include "hardware/rtc.h" #include "fusb302b.h" #include "pd.h" #define FW_STRING1 "PREF1SYS" #define FW_STRING2 "R1" #define FW_STRING3 "20240416" #define FW_REV FW_STRING1 FW_STRING2 FW_STRING3 #define PIN_SDA 0 #define PIN_SCL 1 #define PIN_DISP_RESET 2 #define PIN_FLIGHTMODE 3 #define PIN_KBD_UART_TX 4 #define PIN_KBD_UART_RX 5 #define PIN_WOWWAN 6 #define PIN_DISP_EN 7 #define PIN_SOM_MOSI 8 #define PIN_SOM_SS0 9 #define PIN_SOM_SCK 10 #define PIN_SOM_MISO 11 #define PIN_SOM_UART_TX 12 #define PIN_SOM_UART_RX 13 #define PIN_FUSB_INT 14 #define PIN_LED_B 15 #define PIN_LED_R 16 #define PIN_LED_G 17 #define PIN_MODEM_POWER 18 #define PIN_SOM_WAKE 19 #define PIN_MODEM_RESET 20 #define PIN_1V1_ENABLE 23 #define PIN_3V3_ENABLE 24 #define PIN_5V_ENABLE 25 #define PIN_PHONE_DPR 27 #define PIN_USB_SRC_ENABLE 28 #define PIN_PWREN_LATCH 29 // FUSB302B USB-PD controller #define FUSB_ADDR 0x22 // MAX17320 protector/balancer // https://datasheets.maximintegrated.com/en/ds/MAX17320.pdf #define MAX_ADDR1 0x36 #define MAX_ADDR2 0x0b // MP2650 charger // https://www.monolithicpower.com/en/documentview/productdocument/index/version/2/document_type/Datasheet/lang/en/sku/MP2650GV/document_id/9664/ #define MPS_ADDR 0x5c #define I2C_TIMEOUT (1000*500) #define UART_ID uart1 #define BAUD_RATE 115200 #define DATA_BITS 8 #define STOP_BITS 1 #define PARITY UART_PARITY_NONE // battery information // TODO: turn into a struct // 4.8A x 3600 seconds/hour (per cell) #define MAX_CAPACITY (4.0)*3600.0 float report_capacity_max_ampsecs = MAX_CAPACITY; float report_capacity_accu_ampsecs = MAX_CAPACITY; float report_capacity_min_ampsecs = 0; int report_capacity_percentage = 0; float report_volts = 0; float report_current = 0; float report_cells_v[8] = {0,0,0,0,0,0,0,0}; bool reached_full_charge = true; // FIXME bool som_is_powered = false; bool print_pack_info = false; void i2c_scan() { printf("\nI2C Scan\n"); printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n"); for (int addr = 0; addr < (1 << 7); ++addr) { if (addr % 16 == 0) { printf("%02x ", addr); } int ret; uint8_t rxdata; ret = i2c_read_blocking(i2c0, addr, &rxdata, 1, false); printf(ret < 0 ? "." : "@"); printf(addr % 16 == 15 ? "\n" : " "); } } uint8_t max_read_byte(uint8_t addr) { uint8_t buf; i2c_write_blocking(i2c0, MAX_ADDR1, &addr, 1, true); i2c_read_blocking(i2c0, MAX_ADDR1, &buf, 1, false); return buf; } uint16_t max_read_word(uint8_t addr) { uint8_t buf[2]; i2c_write_blocking(i2c0, MAX_ADDR1, &addr, 1, true); i2c_read_blocking(i2c0, MAX_ADDR1, buf, 2, false); uint16_t result = ((uint16_t)buf[1]<<8) | (uint16_t)buf[0]; return result; } uint16_t max_read_word_100(uint8_t addr) { uint8_t buf[2]; i2c_write_blocking(i2c0, MAX_ADDR2, &addr, 1, true); i2c_read_blocking(i2c0, MAX_ADDR2, buf, 2, false); uint16_t result = ((uint16_t)buf[1]<<8) | (uint16_t)buf[0]; return result; } void max_read_buf(uint8_t addr, uint8_t size, uint8_t *buf) { i2c_write_blocking(i2c0, MAX_ADDR1, &addr, 1, true); i2c_read_blocking(i2c0, MAX_ADDR1, buf, size, false); } void max_write_byte(uint8_t addr, uint8_t byte) { uint8_t buf[2] = {addr, byte}; i2c_write_blocking(i2c0, MAX_ADDR1, buf, 2, false); } void max_write_word(uint8_t addr, uint16_t word) { uint8_t buf[3] = {addr, word&0xff, word>>8}; int res = i2c_write_blocking(i2c0, MAX_ADDR1, buf, 3, false); } void max_write_word_100(uint8_t addr, uint16_t word) { uint8_t buf[3] = {addr, word&0xff, word>>8}; int res = i2c_write_blocking(i2c0, MAX_ADDR2, buf, 3, false); } void max_write_buf(uint8_t addr, uint8_t size, const uint8_t *buf) { uint8_t txbuf[size + 1]; txbuf[0] = addr; for (int i = 0; i < size; i++) { txbuf[i + 1] = buf[i]; } i2c_write_blocking(i2c0, MAX_ADDR1, txbuf, size + 1, false); } float max_word_to_mv(uint16_t w) { float result = ((float)w)*0.078125; return result; } float max_word_to_pack_mv(uint16_t w) { float result = ((float)w)*0.3125; return result; } float max_word_to_ma(uint16_t w) { float result = ((float)w)*0.3125; return result; } float max_word_to_time(uint16_t w) { float result = ((float)w)*5.625; return result; } float max_word_to_cap(uint16_t w) { float result = ((float)w)*1.0; // depends on Rsense, 1.0 @ 5mohms. otherwise 5.0μVh / Rsense return result; } float max_word_to_percentage(uint16_t w) { // TODO: cap to 100% float result = ((float)w)*0.00390625; return result; } uint8_t mps_read_byte(uint8_t addr) { uint8_t buf; i2c_write_blocking(i2c0, MPS_ADDR, &addr, 1, true); i2c_read_blocking(i2c0, MPS_ADDR, &buf, 1, false); return buf; } uint16_t mps_read_word(uint8_t addr) { uint8_t buf[2]; i2c_write_blocking(i2c0, MPS_ADDR, &addr, 1, true); i2c_read_blocking(i2c0, MPS_ADDR, buf, 2, false); uint16_t result = ((uint16_t)buf[1]<<8) | (uint16_t)buf[0]; return result; } float mps_word_to_ntc(uint16_t w) { float result = (float)(w&0xfff)*1.6/4096.0; return result; } float mps_word_to_3200(uint16_t w) { float result = (w>>10)*100 + ((w&(1<<9))>>9)*50 + ((w&(1<<8))>>8)*25 + ((w&(1<<7))>>7)*12.5 + ((w&(1<<6))>>6)*6.25; return result; } float mps_word_to_6400(uint16_t w) { float result = (w>>9)*100 + ((w&(1<<8))>>8)*50 + ((w&(1<<7))>>7)*25 + ((w&(1<<6))>>6)*12.5; return result; } float mps_word_to_12800(uint16_t w) { float result = (w>>8)*100 + ((w&(1<<7))>>7)*50 + ((w&(1<<6))>>6)*25; return result; } float mps_word_to_w(uint16_t w) { float result = (w>>9) + ((w&(1<<8))>>8)*0.5 + ((w&(1<<7))>>7)*0.25 + ((w&(1<<6))>>6)*0.125; return result; } // tj=903-2.578*t // tj-903=-2.578*t // (tj-903)/-2.578=t float mps_word_to_temp(uint16_t w) { float result = (float)(w>>6); result = (result - 903) / -2.578; //result = 903-2.578*result; return result; } void mps_read_buf(uint8_t addr, uint8_t size, uint8_t *buf) { i2c_write_blocking(i2c0, MPS_ADDR, &addr, 1, true); i2c_read_blocking(i2c0, MPS_ADDR, buf, size, false); } void mps_write_byte(uint8_t addr, uint8_t byte) { uint8_t buf[2] = {addr, byte}; i2c_write_blocking(i2c0, MPS_ADDR, buf, 2, false); } void mps_write_buf(uint8_t addr, uint8_t size, const uint8_t *buf) { uint8_t txbuf[size + 1]; txbuf[0] = addr; for (int i = 0; i < size; i++) { txbuf[i + 1] = buf[i]; } i2c_write_blocking(i2c0, MPS_ADDR, txbuf, size + 1, false); } uint8_t fusb_read_byte(uint8_t addr) { uint8_t buf; i2c_write_blocking(i2c0, FUSB_ADDR, &addr, 1, true); i2c_read_blocking(i2c0, FUSB_ADDR, &buf, 1, false); return buf; } void fusb_read_buf(uint8_t addr, uint8_t size, uint8_t *buf) { i2c_write_blocking(i2c0, FUSB_ADDR, &addr, 1, true); i2c_read_blocking(i2c0, FUSB_ADDR, buf, size, false); } void fusb_write_byte(uint8_t addr, uint8_t byte) { uint8_t buf[2] = {addr, byte}; i2c_write_blocking(i2c0, FUSB_ADDR, buf, 2, false); } void fusb_write_buf(uint8_t addr, uint8_t size, const uint8_t *buf) { uint8_t txbuf[size + 1]; txbuf[0] = addr; for (int i = 0; i < size; i++) { txbuf[i + 1] = buf[i]; } i2c_write_blocking(i2c0, FUSB_ADDR, txbuf, size + 1, false); } void fusb_send_message(const union pd_msg *msg) { /* Token sequences for the FUSB302B */ static uint8_t sop_seq[5] = { FUSB_FIFO_TX_SOP1, FUSB_FIFO_TX_SOP1, FUSB_FIFO_TX_SOP1, FUSB_FIFO_TX_SOP2, FUSB_FIFO_TX_PACKSYM }; static uint8_t eop_seq[4] = { FUSB_FIFO_TX_JAM_CRC, FUSB_FIFO_TX_EOP, FUSB_FIFO_TX_TXOFF, FUSB_FIFO_TX_TXON }; /* Get the length of the message: a two-octet header plus NUMOBJ four-octet * data objects */ uint8_t msg_len = 2 + 4 * PD_NUMOBJ_GET(msg); /* Set the number of bytes to be transmitted in the packet */ sop_seq[4] = FUSB_FIFO_TX_PACKSYM | msg_len; /* Write all three parts of the message to the TX FIFO */ fusb_write_buf(FUSB_FIFOS, 5, sop_seq); fusb_write_buf(FUSB_FIFOS, msg_len, msg->bytes); fusb_write_buf(FUSB_FIFOS, 4, eop_seq); } uint8_t fusb_read_message(union pd_msg *msg) { uint8_t garbage[4]; uint8_t numobj; /* If this isn't an SOP message, return error. * Because of our configuration, we should be able to assume this means the * buffer is empty, and not try to read past a non-SOP message. */ uint8_t rxb = fusb_read_byte(FUSB_FIFOS); if (rxb!=0) printf("# [fusb] rx = 0x%02x\n", rxb); if ((rxb & FUSB_FIFO_RX_TOKEN_BITS) != FUSB_FIFO_RX_SOP) { return 1; } /* Read the message header into msg */ fusb_read_buf(FUSB_FIFOS, 2, msg->bytes); /* Get the number of data objects */ numobj = PD_NUMOBJ_GET(msg); /* If there is at least one data object, read the data objects */ if (numobj > 0) { fusb_read_buf(FUSB_FIFOS, numobj * 4, msg->bytes + 2); } /* Throw the CRC32 in the garbage, since the PHY already checked it. */ fusb_read_buf(FUSB_FIFOS, 4, garbage); return 0; } // returns voltage int print_src_fixed_pdo(int number, uint32_t pdo) { int tmp; printf("[pd_src_fixed_pdo]\n"); printf("number = %d\n", number); /* Dual-role power */ tmp = (pdo & PD_PDO_SRC_FIXED_DUAL_ROLE_PWR) >> PD_PDO_SRC_FIXED_DUAL_ROLE_PWR_SHIFT; if (tmp) { printf("dual_role_pwr = %d\n", tmp); } /* USB Suspend Supported */ tmp = (pdo & PD_PDO_SRC_FIXED_USB_SUSPEND) >> PD_PDO_SRC_FIXED_USB_SUSPEND_SHIFT; if (tmp) { printf("usb_suspend = %d\n", tmp); } /* Unconstrained Power */ tmp = (pdo & PD_PDO_SRC_FIXED_UNCONSTRAINED) >> PD_PDO_SRC_FIXED_UNCONSTRAINED_SHIFT; if (tmp) { printf("unconstrained_pwr = %d\n", tmp); } /* USB Communications Capable */ tmp = (pdo & PD_PDO_SRC_FIXED_USB_COMMS) >> PD_PDO_SRC_FIXED_USB_COMMS_SHIFT; if (tmp) { printf("usb_comms = %d\n", tmp); } /* Dual-Role Data */ tmp = (pdo & PD_PDO_SRC_FIXED_DUAL_ROLE_DATA) >> PD_PDO_SRC_FIXED_DUAL_ROLE_DATA_SHIFT; if (tmp) { printf("dual_role_data = %d\n", tmp); } /* Unchunked Extended Messages Supported */ tmp = (pdo & PD_PDO_SRC_FIXED_UNCHUNKED_EXT_MSG) >> PD_PDO_SRC_FIXED_UNCHUNKED_EXT_MSG_SHIFT; if (tmp) { printf("unchunked_ext_msg = %d\n", tmp); } /* Peak Current */ tmp = (pdo & PD_PDO_SRC_FIXED_PEAK_CURRENT) >> PD_PDO_SRC_FIXED_PEAK_CURRENT_SHIFT; if (tmp) { printf("peak_i = %d\n", tmp); } /* Voltage */ tmp = (pdo & PD_PDO_SRC_FIXED_VOLTAGE) >> PD_PDO_SRC_FIXED_VOLTAGE_SHIFT; printf("v = %d.%02d\n", PD_PDV_V(tmp), PD_PDV_CV(tmp)); int voltage = (int)PD_PDV_V(tmp); /* Maximum Current */ tmp = (pdo & PD_PDO_SRC_FIXED_CURRENT) >> PD_PDO_SRC_FIXED_CURRENT_SHIFT; printf("i_a: %d.%02d\n", PD_PDI_A(tmp), PD_PDI_CA(tmp)); return voltage; } int charger_configure() { // TODO: check all MP2650 registers, esp. 4, 7, b // set input current limit to 2000mA mps_write_byte(0x00, (1<<5)|(1<<3)); // set input voltage limit to 6V (above 5V USB voltage) mps_write_byte(0x01, (1<<6)); // set charge current limit to 2000mA (1600+400) mps_write_byte(0x02, (1<<5)|(1<<3)); } float charger_dump() { // TODO: if max reports overvoltage (dysbalanced cells), // can we lower the charging voltage temporarily? // alternatively, the current uint8_t status = mps_read_byte(0x13); uint8_t fault = mps_read_byte(0x14); float adc_bat_v = mps_word_to_6400(mps_read_word(0x16))/1000.0; float adc_sys_v = mps_word_to_6400(mps_read_word(0x18))/1000.0; float adc_charge_c = mps_word_to_6400(mps_read_word(0x1a))/1000.0; float adc_input_v = mps_word_to_12800(mps_read_word(0x1c))/1000.0; float adc_input_c = mps_word_to_3200(mps_read_word(0x1e))/1000.0; float adc_temp = mps_word_to_temp(mps_read_word(0x24)); float adc_sys_pwr = mps_word_to_w(mps_read_word(0x26)); float adc_discharge_c = mps_word_to_6400(mps_read_word(0x28))/1000.0; float adc_ntc_v = mps_word_to_ntc(mps_read_word(0x40))/1000.0; uint8_t input_c_limit = mps_read_byte(0x00); uint8_t input_v_limit = mps_read_byte(0x01); uint8_t charge_c = mps_read_byte(0x02); uint8_t precharge_c = mps_read_byte(0x03); uint8_t bat_full_v = mps_read_byte(0x04); // carry over to globals for SPI reporting report_current = -(adc_input_c - adc_discharge_c); report_volts = adc_sys_v; if (print_pack_info) { printf("[charger_info]\n"); printf("status = 0x%x\n", status); printf("fault = 0x%x\n", fault); printf("adc_bat_v = %f\n", adc_bat_v); printf("adc_sys_v = %f\n", adc_sys_v); printf("adc_charge_c = %f\n", adc_charge_c); printf("adc_input_v = %f\n", adc_input_v); printf("adc_input_c = %f\n", adc_input_c); printf("adc_temp = %f\n", adc_temp); printf("adc_sys_pwr = %f\n", adc_sys_pwr); printf("adc_discharge_c = %f\n", adc_discharge_c); printf("adc_ntc_v = %f\n", adc_ntc_v); printf("input_c_limit = 0x%x\n", input_c_limit); printf("input_v_limit = 0x%x\n", input_v_limit); printf("charge_c = 0x%x\n", charge_c); printf("precharge_c = 0x%x\n", precharge_c); printf("bat_full_v = 0x%d\n", bat_full_v); } return adc_input_v; } void max_dump() { // disable write protection (CommStat) max_write_word(0x61, 0x0000); max_write_word(0x61, 0x0000); // set pack cfg: 2 cells (0), 1+1 thermistor, 6v charge pump, 11:thtype=10k, btpken on, no aoldo max_write_word_100(0xb5, (0<<14)|(1<<13)|(0<<11)|(0<<8)|(2<<2)|0); // enable balancing (zener) // nBalCfg max_write_word(0x61, 0x0000); max_write_word(0x61, 0x0000); max_write_word_100(0xd4, (1<<13)|(3<<10)|(3<<5)); uint16_t comm_stat = max_read_word(0x61); uint16_t status = max_read_word(0x00); uint16_t packcfg = max_read_word_100(0xb5); uint16_t prot_status = max_read_word(0xd9); uint16_t prot_alert = max_read_word(0xaf); uint16_t prot_cfg2 = max_read_word_100(0xf1); uint16_t therm_cfg = max_read_word_100(0xca); float vcell = max_word_to_mv(max_read_word(0x1a)); float avg_vcell = max_word_to_mv(max_read_word(0x19)); float cell1 = max_word_to_mv(max_read_word(0xd8)); float cell2 = max_word_to_mv(max_read_word(0xd7)); float cell3 = max_word_to_mv(max_read_word(0xd6)); float cell4 = max_word_to_mv(max_read_word(0xd5)); // this value looks good (checked with inducing voltages w/ power supply) float vpack = max_word_to_pack_mv(max_read_word(0xda)); float temp = ((float)((int16_t)max_read_word(0x1b)))*(1.0/256.0); float die_temp = ((float)((int16_t)max_read_word(0x34)))*(1.0/256.0); float temp1 = ((float)((int16_t)max_read_word_100(0x3a)))*(1.0/256.0); float temp2 = ((float)((int16_t)max_read_word_100(0x39)))*(1.0/256.0); float temp3 = ((float)((int16_t)max_read_word_100(0x38)))*(1.0/256.0); float temp4 = ((float)((int16_t)max_read_word_100(0x37)))*(1.0/256.0); float rep_capacity = max_word_to_cap(max_read_word(0x05)); float rep_percentage = max_word_to_percentage(max_read_word(0x06)); float rep_age = max_word_to_percentage(max_read_word(0x07)); float rep_full_capacity = max_word_to_cap(max_read_word(0x10)); float rep_time_to_empty = max_word_to_time(max_read_word(0x11)); float rep_time_to_full = max_word_to_time(max_read_word(0x20)); // carry over to globals report_capacity_percentage = (int)rep_percentage; // charger mostly doesn't charge to >98% if (report_capacity_percentage >= 98) report_capacity_percentage = 100; report_cells_v[0] = cell1; report_cells_v[1] = cell2; if (print_pack_info) { printf("[pack_info]\n"); printf("comm_stat = 0x%04x\n", comm_stat); printf("packcfg = 0x%04x\n", packcfg); printf("status = 0x%04x\n", status); printf("status_prot_alert = %d\n", (status & 0x8000) ? 1 : 0); printf("prot_alert = 0x%04x\n", prot_alert); printf("prot_cfg2 = 0x%04x\n", prot_cfg2); printf("therm_cfg = 0x%04x\n", therm_cfg); printf("temp = %f\n", temp); printf("die temp = %f\n", die_temp); printf("temp1 = %f\n", temp1); printf("temp2 = %f\n", temp2); printf("temp3 = %f\n", temp3); printf("temp4 = %f\n", temp4); printf("prot_status = 0x%04x\n", prot_status); printf("prot_status_meaning = \""); if (prot_status & (1<<14)) { printf("too hot, "); } if (prot_status & (1<<13)) { printf("full, "); } if (prot_status & (1<<12)) { printf("too cold for charge, "); } if (prot_status & (1<<11)) { printf("overvoltage, "); } if (prot_status & (1<<10)) { printf("overcharge current, "); } if (prot_status & (1<<9)) { printf("qoverflow, "); } if (prot_status & (1<<8)) { printf("prequal timeout, "); } if (prot_status & (1<<7)) { printf("imbalance, "); } if (prot_status & (1<<6)) { printf("perm fail, "); } if (prot_status & (1<<5)) { printf("die hot, "); } if (prot_status & (1<<4)) { printf("too hot for discharge, "); } if (prot_status & (1<<3)) { printf("undervoltage, "); } if (prot_status & (1<<2)) { printf("overdischarge current, "); } if (prot_status & (1<<1)) { printf("resdfault, "); } if (prot_status & (1<<0)) { printf("ship, "); } printf("\"\n"); printf("vcell = %f\n", vcell); printf("avg_vcell = %f\n", avg_vcell); printf("cell1 = %f\n", cell1); printf("cell2 = %f\n", cell2); printf("cell3 = %f\n", cell3); printf("cell4 = %f\n", cell4); printf("vpack = %f\n", vpack); printf("rep_capacity_mah = %f\n", rep_capacity); printf("rep_percentage = %f\n", rep_percentage); printf("rep_age_percentage = %f\n", rep_age); printf("rep_full_capacity_mah = %f\n", rep_full_capacity); printf("rep_time_to_empty_sec = %f\n", rep_time_to_empty); printf("rep_time_to_full_sec = %f\n", rep_time_to_full); } if (status & 0x0002) { printf("# POR, clearing status\n"); max_write_word(0x61, 0x0000); max_write_word(0x61, 0x0000); max_write_word(0x00, status & (~0x0002)); } } void init_spi_client(); void turn_som_power_on() { init_spi_client(); // latch gpio_put(PIN_PWREN_LATCH, 1); gpio_put(PIN_LED_B, 1); printf("# [action] turn_som_power_on\n"); gpio_put(PIN_1V1_ENABLE, 1); sleep_ms(10); gpio_put(PIN_3V3_ENABLE, 1); sleep_ms(10); /*gpio_put(PIN_SOM_MOSI, 1); gpio_put(PIN_SOM_SS0, 1); gpio_put(PIN_SOM_SCK, 1); gpio_put(PIN_SOM_MISO, 1);*/ gpio_put(PIN_5V_ENABLE, 1); // MODEM gpio_put(PIN_FLIGHTMODE, 1); // active low gpio_put(PIN_MODEM_RESET, 0); // active low (?) gpio_put(PIN_MODEM_POWER, 1); // active high gpio_put(PIN_PHONE_DPR, 1); // active high sleep_ms(10); gpio_put(PIN_DISP_EN, 1); sleep_ms(10); gpio_put(PIN_DISP_RESET, 1); // MODEM gpio_put(PIN_MODEM_RESET, 1); // active low // done with latching gpio_put(PIN_PWREN_LATCH, 0); som_is_powered = true; } void turn_som_power_off() { init_spi_client(); // latch gpio_put(PIN_PWREN_LATCH, 1); // FIXME spi test /*gpio_put(PIN_SOM_MOSI, 0); gpio_put(PIN_SOM_SS0, 0); gpio_put(PIN_SOM_SCK, 0); gpio_put(PIN_SOM_MISO, 0);*/ gpio_put(PIN_LED_B, 0); printf("# [action] turn_som_power_off\n"); gpio_put(PIN_DISP_RESET, 0); gpio_put(PIN_DISP_EN, 0); // MODEM gpio_put(PIN_FLIGHTMODE, 0); // active low gpio_put(PIN_MODEM_RESET, 0); // active low gpio_put(PIN_MODEM_POWER, 0); // active high gpio_put(PIN_PHONE_DPR, 0); // active high gpio_put(PIN_5V_ENABLE, 0); sleep_ms(10); gpio_put(PIN_3V3_ENABLE, 0); sleep_ms(10); gpio_put(PIN_1V1_ENABLE, 0); // done with latching gpio_put(PIN_PWREN_LATCH, 0); som_is_powered = false; } void som_wake() { uart_puts(uart0, "wake\r\n"); } #define ST_EXPECT_DIGIT_0 0 #define ST_EXPECT_DIGIT_1 1 #define ST_EXPECT_DIGIT_2 2 #define ST_EXPECT_DIGIT_3 3 #define ST_EXPECT_CMD 4 #define ST_SYNTAX_ERROR 5 #define ST_EXPECT_RETURN 6 #define ST_EXPECT_MAGIC 7 char remote_cmd = 0; uint8_t remote_arg = 0; unsigned char cmd_state = ST_EXPECT_DIGIT_0; unsigned int cmd_number = 0; int cmd_echo = 0; char uart_buffer[255] = {0}; // chr: input character void handle_commands(char chr) { if (cmd_echo) { sprintf(uart_buffer, "%c", chr); uart_puts(UART_ID, uart_buffer); } // states: // 0-3 digits of optional command argument // 4 command letter expected // 5 syntax error (unexpected character) // 6 command letter entered if (cmd_state>=ST_EXPECT_DIGIT_0 && cmd_state<=ST_EXPECT_DIGIT_3) { // read number or command if (chr >= '0' && chr <= '9') { cmd_number*=10; cmd_number+=(chr-'0'); cmd_state++; } else if ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')) { // command entered instead of digit remote_cmd = chr; cmd_state = ST_EXPECT_RETURN; } else if (chr == '\n' || chr == ' ') { // ignore newlines or spaces } else if (chr == '\r') { sprintf(uart_buffer, "error:syntax\r\n"); uart_puts(UART_ID, uart_buffer); cmd_state = ST_EXPECT_DIGIT_0; cmd_number = 0; } else { // syntax error cmd_state = ST_SYNTAX_ERROR; } } else if (cmd_state == ST_EXPECT_CMD) { // read command if ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')) { remote_cmd = chr; cmd_state = ST_EXPECT_RETURN; } else { cmd_state = ST_SYNTAX_ERROR; } } else if (cmd_state == ST_SYNTAX_ERROR) { // syntax error if (chr == '\r') { sprintf(uart_buffer, "error:syntax\r\n"); uart_puts(UART_ID, uart_buffer); cmd_state = ST_EXPECT_DIGIT_0; cmd_number = 0; } } else if (cmd_state == ST_EXPECT_RETURN) { if (chr == '\n' || chr == ' ') { // ignore newlines or spaces } else if (chr == '\r') { if (cmd_echo) { // FIXME sprintf(uart_buffer,"\n"); uart_puts(UART_ID, uart_buffer); } // execute if (remote_cmd == 'p') { // toggle system power and/or reset imx if (cmd_number == 0) { turn_som_power_off(); sprintf(uart_buffer,"system: off\r\n"); uart_puts(UART_ID, uart_buffer); } else if (cmd_number == 2) { //reset_som(); sprintf(uart_buffer,"system: reset\r\n"); uart_puts(UART_ID, uart_buffer); } else { turn_som_power_on(); sprintf(uart_buffer,"system: on\r\n"); uart_puts(UART_ID, uart_buffer); } } else if (remote_cmd == 'a') { // TODO // get system current (mA) sprintf(uart_buffer,"%d\r\n",0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'v' && cmd_number>=0 && cmd_number<=0) { // TODO // get cell voltage sprintf(uart_buffer,"%d\r\n",0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'V') { // TODO // get system voltage sprintf(uart_buffer,"%d\r\n",0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 's') { // TODO sprintf(uart_buffer,FW_REV"normal,%d,%d,%d\r\n",0,0,0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'u') { // TODO // turn reporting to i.MX on or off } else if (remote_cmd == 'w') { // wake SoC som_wake(); sprintf(uart_buffer,"system: wake\r\n"); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'c') { // get status of cells, current, voltage, fuel gauge int mA = (int)(report_current*1000.0); char mA_sign = ' '; if (mA<0) { mA = -mA; mA_sign = '-'; } int mV = (int)(report_volts*1000.0); sprintf(uart_buffer,"%02d %02d %02d %02d %02d %02d %02d %02d mA%c%04dmV%05d %3d%% P%d\r\n", (int)(report_cells_v[0]/100), (int)(report_cells_v[1]/100), (int)(report_cells_v[2]/100), (int)(report_cells_v[3]/100), (int)(report_cells_v[4]/100), (int)(report_cells_v[5]/100), (int)(report_cells_v[6]/100), (int)(report_cells_v[7]/100), mA_sign, mA, mV, report_capacity_percentage, som_is_powered?1:0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'S') { // TODO // get charger system cycles in current state sprintf(uart_buffer, "%d\r\n", 0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'C') { // TODO // get battery capacity (mAh) sprintf(uart_buffer,"%d/%d/%d\r\n",0,0,0); uart_puts(UART_ID, uart_buffer); } else if (remote_cmd == 'e') { // toggle serial echo cmd_echo = cmd_number?1:0; } else { sprintf(uart_buffer, "error:command\r\n"); uart_puts(UART_ID, uart_buffer); } cmd_state = ST_EXPECT_DIGIT_0; cmd_number = 0; } else { cmd_state = ST_SYNTAX_ERROR; } } } #define SPI_BUF_LEN 0x8 uint8_t spi_buf[SPI_BUF_LEN]; unsigned char spi_cmd_state = ST_EXPECT_MAGIC; unsigned char spi_command = '\0'; uint8_t spi_arg1 = 0; void init_spi_client() { gpio_set_function(PIN_SOM_MOSI, GPIO_FUNC_SPI); gpio_set_function(PIN_SOM_MISO, GPIO_FUNC_SPI); gpio_set_function(PIN_SOM_SS0, GPIO_FUNC_SPI); gpio_set_function(PIN_SOM_SCK, GPIO_FUNC_SPI); spi_init(spi1, 400 * 1000); // we don't appreciate the wording, but it's the API we are given spi_set_slave(spi1, true); spi_set_format(spi1, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST); printf("# [spi] init_spi_client done\n"); } /** * @brief SPI command from imx poll function * * Ported from MNT Reform reform2-lpc-fw. */ void handle_spi_commands() { int len = 0; int all_zeroes = 1; while (spi_is_readable(spi1) && len < SPI_BUF_LEN) { // 0x00 is "repeated tx data" spi_read_blocking(spi1, 0x00, &spi_buf[len], 1); if (spi_buf[len] != 0) all_zeroes = 0; len++; } if (len == 0) { return; } //printf("# [spi] rx (len = %d): %02x %02x %02x %02x %02x %02x %02x %02x\n", len, spi_buf[0], spi_buf[1], spi_buf[2], spi_buf[3], spi_buf[4], spi_buf[5], spi_buf[6], spi_buf[7]); // states: // 0 arg1 byte expected // 4 command byte expected // 6 execute command // 7 magic byte expected for (uint8_t s = 0; s < len; s++) { if (spi_cmd_state == ST_EXPECT_MAGIC) { // magic byte found, prevents garbage data // in the bus from triggering a command if (spi_buf[s] == 0xb5) { spi_cmd_state = ST_EXPECT_CMD; } } else if (spi_cmd_state == ST_EXPECT_CMD) { // read command spi_command = spi_buf[s]; spi_cmd_state = ST_EXPECT_DIGIT_0; } else if (spi_cmd_state == ST_EXPECT_DIGIT_0) { // read arg1 byte spi_arg1 = spi_buf[s]; spi_cmd_state = ST_EXPECT_RETURN; } //printf("# [spi] after 0x%02x (pos %d): state %d cmd %c (%02x) arg %d\n", spi_buf[s], s, spi_cmd_state, spi_command, spi_command, spi_arg1); } if (spi_cmd_state == ST_EXPECT_MAGIC && !all_zeroes) { // reset SPI0 block // this is a workaround for confusion with // software spi from BPI-CM4 where we get // bit-shifted bytes init_spi_client(); spi_cmd_state = ST_EXPECT_MAGIC; spi_command = 0; spi_arg1 = 0; return; } if (spi_cmd_state != ST_EXPECT_RETURN) { // waiting for more data return; } printf("# [spi] exec: '%c' 0x%02x\n", spi_command, spi_arg1); // clear receive buffer, reuse as send buffer memset(spi_buf, 0, SPI_BUF_LEN); // execute power state command if (spi_command == 'p') { // toggle system power and/or reset imx if (spi_arg1 == 1) { turn_som_power_off(); } if (spi_arg1 == 2) { turn_som_power_on(); } if (spi_arg1 == 3) { // TODO //reset_som(); } spi_buf[0] = som_is_powered; } // return firmware version and api info else if (spi_command == 'f') { if(spi_arg1 == 0) { memcpy(spi_buf, FW_STRING1, 8); } else if(spi_arg1 == 1) { memcpy(spi_buf, FW_STRING2, 2); } else { memcpy(spi_buf, FW_STRING3, 8); } } // execute status query command else if (spi_command == 'q') { uint8_t percentage = (uint8_t)report_capacity_percentage; int16_t voltsInt = (int16_t)(report_volts*1000.0); int16_t currentInt = (int16_t)(report_current*1000.0); spi_buf[0] = (uint8_t)voltsInt; spi_buf[1] = (uint8_t)(voltsInt >> 8); spi_buf[2] = (uint8_t)currentInt; spi_buf[3] = (uint8_t)(currentInt >> 8); spi_buf[4] = (uint8_t)percentage; spi_buf[5] = (uint8_t)0; // TODO "state" not implemented spi_buf[6] = (uint8_t)0; } // get cell voltage else if (spi_command == 'v') { uint16_t volts = 0; uint8_t cell1 = 0; if (spi_arg1 == 1) { cell1 = 4; } for (uint8_t c = 0; c < 4; c++) { volts = report_cells_v[c + cell1]; spi_buf[c*2] = (uint8_t)volts; spi_buf[(c*2)+1] = (uint8_t)(volts >> 8); } } // get calculated capacity else if (spi_command == 'c') { uint16_t cap_accu = (uint16_t) report_capacity_max_ampsecs / 3.6; uint16_t cap_min = (uint16_t) report_capacity_min_ampsecs / 3.6; uint16_t cap_max = (uint16_t) report_capacity_max_ampsecs / 3.6; spi_buf[0] = (uint8_t)cap_accu; spi_buf[1] = (uint8_t)(cap_accu >> 8); spi_buf[2] = (uint8_t)cap_min; spi_buf[3] = (uint8_t)(cap_min >> 8); spi_buf[4] = (uint8_t)cap_max; spi_buf[5] = (uint8_t)(cap_max >> 8); } else if (spi_command == 'u') { // not implemented } // FIXME: if we don't reset, SPI wants to transact the amount of bytes // that we read above for unknown reasons init_spi_client(); if (som_is_powered) { spi_write_blocking(spi1, spi_buf, SPI_BUF_LEN); } spi_cmd_state = ST_EXPECT_MAGIC; spi_command = 0; spi_arg1 = 0; return; } void on_uart_rx() { while (uart_is_readable(UART_ID)) { handle_commands(uart_getc(UART_ID)); } } int main() { //set_sys_clock_48mhz(); stdio_init_all(); init_spi_client(); // UART to keyboard uart_init(UART_ID, BAUD_RATE); uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY); uart_set_hw_flow(UART_ID, false, false); uart_set_fifo_enabled(UART_ID, true); gpio_set_function(PIN_KBD_UART_TX, GPIO_FUNC_UART); gpio_set_function(PIN_KBD_UART_RX, GPIO_FUNC_UART); int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ; // UART to som uart_init(uart0, BAUD_RATE); uart_set_format(uart0, DATA_BITS, STOP_BITS, PARITY); uart_set_hw_flow(uart0, false, false); uart_set_fifo_enabled(uart0, true); gpio_set_function(PIN_SOM_UART_TX, GPIO_FUNC_UART); gpio_set_function(PIN_SOM_UART_RX, GPIO_FUNC_UART); gpio_set_function(PIN_SDA, GPIO_FUNC_I2C); gpio_set_function(PIN_SCL, GPIO_FUNC_I2C); bi_decl(bi_2pins_with_func(PIN_SDA, PIN_SCL, GPIO_FUNC_I2C)); i2c_init(i2c0, 100 * 1000); gpio_init(PIN_LED_R); gpio_init(PIN_LED_G); gpio_init(PIN_LED_B); gpio_set_dir(PIN_LED_R, 1); gpio_set_dir(PIN_LED_G, 1); gpio_set_dir(PIN_LED_B, 1); gpio_init(PIN_1V1_ENABLE); gpio_init(PIN_3V3_ENABLE); gpio_init(PIN_5V_ENABLE); gpio_set_dir(PIN_1V1_ENABLE, 1); gpio_set_dir(PIN_3V3_ENABLE, 1); gpio_set_dir(PIN_5V_ENABLE, 1); gpio_put(PIN_1V1_ENABLE, 0); gpio_put(PIN_3V3_ENABLE, 0); gpio_put(PIN_5V_ENABLE, 0); gpio_init(PIN_PWREN_LATCH); gpio_set_dir(PIN_PWREN_LATCH, 1); gpio_put(PIN_PWREN_LATCH, 0); gpio_init(PIN_DISP_RESET); gpio_init(PIN_DISP_EN); gpio_set_dir(PIN_DISP_EN, 1); gpio_set_dir(PIN_DISP_RESET, 1); gpio_put(PIN_DISP_EN, 0); gpio_put(PIN_DISP_RESET, 0); gpio_init(PIN_FLIGHTMODE); gpio_init(PIN_MODEM_POWER); gpio_init(PIN_MODEM_RESET); gpio_init(PIN_PHONE_DPR); gpio_set_dir(PIN_FLIGHTMODE, 1); gpio_set_dir(PIN_MODEM_POWER, 1); gpio_set_dir(PIN_MODEM_RESET, 1); gpio_set_dir(PIN_PHONE_DPR, 1); gpio_put(PIN_FLIGHTMODE, 0); // active low gpio_put(PIN_MODEM_POWER, 0); // active high gpio_put(PIN_MODEM_RESET, 0); // active low (?) gpio_put(PIN_PHONE_DPR, 0); // active high // causes 0.146W power use when high in off state! gpio_put(PIN_LED_R, 0); gpio_put(PIN_LED_G, 0); gpio_put(PIN_LED_B, 0); gpio_init(PIN_USB_SRC_ENABLE); gpio_set_dir(PIN_USB_SRC_ENABLE, 1); gpio_put(PIN_USB_SRC_ENABLE, 0); // latch the PWR and display pins gpio_put(PIN_PWREN_LATCH, 1); gpio_put(PIN_PWREN_LATCH, 0); unsigned int t = 0; unsigned int t_report = 0; int state = 0; int request_sent = 0; uint8_t rxdata[2]; union pd_msg tx; int tx_id_count = 0; union pd_msg rx_msg; int power_objects = 0; int max_voltage = 0; sleep_ms(5000); #ifdef FACTORY_MODE // in factory mode, turn on power immediately to be able to flash // the keyboard turn_som_power_on(); #endif printf("# [pocket_sysctl] entering main loop.\n"); while (true) { // handle commands from keyboard while (uart_is_readable(UART_ID)) { handle_commands(uart_getc(UART_ID)); } handle_spi_commands(); #ifdef ACM_ENABLED // handle commands over usb serial int usb_c = getchar_timeout_us(0); if (usb_c != PICO_ERROR_TIMEOUT) { printf("# [acm_command] '%c'\n", usb_c); if (usb_c == '1') { turn_som_power_on(); } else if (usb_c == '0') { turn_som_power_off(); } else if (usb_c == 'p') { print_pack_info = !print_pack_info; } } #endif if (state == 0) { gpio_put(PIN_LED_R, 0); power_objects = 0; request_sent = 0; // by default, we output 5V on VUSB gpio_put(PIN_USB_SRC_ENABLE, 1); //printf("# [pd] state 0\n"); // probe FUSB302BMPX if (i2c_read_timeout_us(i2c0, FUSB_ADDR, rxdata, 1, false, I2C_TIMEOUT)) { // 1. set auto GoodCRC // AUTO_CRC in Switches1 // Address: 03h; Reset Value: 0b0010_0000 //printf("# [pd] FUSB probed.\n"); fusb_write_byte(FUSB_RESET, FUSB_RESET_SW_RES); sleep_us(10); // turn on all power fusb_write_byte(FUSB_POWER, 0x0F); // automatic retransmission //fusb_write_byte(FUSB_CONTROL3, 0x07); // AUTO_HARDRESET | AUTO_SOFTRESET | 3 retries | AUTO_RETRY fusb_write_byte(FUSB_CONTROL3, (1<<4) | (1<<3) | (3<<1) | 1); // flush rx buffer fusb_write_byte(FUSB_CONTROL1, FUSB_CONTROL1_RX_FLUSH); // pdwn means pulldown. 0 = no pull down /* Measure CC1 */ fusb_write_byte(FUSB_SWITCHES0, 4|2|1); // MEAS_CC1|PDWN2 |PDWN1 sleep_us(250); uint8_t cc1 = fusb_read_byte(FUSB_STATUS0) & FUSB_STATUS0_BC_LVL; //printf("# [pd] CC1: %d\n", cc1); /* Measure CC2 */ fusb_write_byte(FUSB_SWITCHES0, 8|2|1); // MEAS_CC2|PDWN2 |PDWN1 sleep_us(250); uint8_t cc2 = fusb_read_byte(FUSB_STATUS0) & FUSB_STATUS0_BC_LVL; //printf("# [pd] CC2: %d\n", cc2); // detect orientation if (cc1 > cc2) { fusb_write_byte(FUSB_SWITCHES1, 4|1); // |AUTO_CRC|TXCC1 fusb_write_byte(FUSB_SWITCHES0, 4|2|1); // MEAS_CC1|PDWN2 |PDWN1 } else { fusb_write_byte(FUSB_SWITCHES1, 4|2); // |AUTO_CRC|TXCC2 fusb_write_byte(FUSB_SWITCHES0, 8|2|1); // MEAS_CC2|PDWN2 |PDWN1 } //printf("# [pd] switches set.\n"); fusb_write_byte(FUSB_RESET, FUSB_RESET_PD_RESET); // automatic soft reset // FIXME disturbs PD with some supplies //fusb_write_byte(FUSB_CONTROL3, (1<<6) | (1<<4) | (1<<3) | (3<<1) | 1); //sleep_ms(1); //fusb_write_byte(FUSB_CONTROL3, (1<<4) | (1<<3) | (3<<1) | 1); //printf("# [pd] auto hard/soft reset and retries set.\n"); t = 0; state = 1; } else { if (t > 1000) { printf("# [pd] state 0: fusb timeout.\n"); t = 0; } } } else if (state == 1) { //printf("[pocket-sysctl] state 1\n"); if (t>3000) { printf("# [pd] state 1, timeout.\n"); float input_voltage = charger_dump(); max_dump(); t = 0; // without batteries, the system dies here (brownout?) // but the charger might have set up the requested voltage anyway if (input_voltage < 6) { fusb_write_byte(FUSB_CONTROL3, (1<<6) | (1<<4) | (1<<3) | (3<<1) | 1); //sleep_ms(1); fusb_write_byte(FUSB_CONTROL3, (1<<4) | (1<<3) | (3<<1) | 1); state = 0; } else { state = 3; } } int res = fusb_read_message(&rx_msg); if (!res) { //printf("# [pd] s1: charger responds, turning off USB_SRC\n"); // if a charger is responding, turn off our 5V output gpio_put(PIN_USB_SRC_ENABLE, 0); uint8_t msgtype = PD_MSGTYPE_GET(&rx_msg); uint8_t numobj = PD_NUMOBJ_GET(&rx_msg); if (msgtype == PD_MSGTYPE_SOURCE_CAPABILITIES) { max_voltage = 0; for (int i=0; i max_voltage && voltage <= 20) { power_objects = i+1; max_voltage = voltage; } } else { printf("# [pd] state 1, not a fixed PDO: 0x%08x\n", pdo); } } if (!request_sent) { state = 2; t = 0; } } else if (msgtype == PD_MSGTYPE_PS_RDY) { // power supply is ready printf("# [pd] state 1, power supply ready.\n"); request_sent = 0; t = 0; state = 3; } else { printf("# [pd] state 1, msg type: 0x%x numobj: %d\n", msgtype, numobj); } } else { //sleep_ms(1); //printf("# [pd] state 1, no message\n"); } } else if (state == 2) { printf("# [pd] state 2, requesting PO %d, %d V\n", power_objects, max_voltage); tx.hdr = PD_MSGTYPE_REQUEST | PD_NUMOBJ(1) | PD_DATAROLE_UFP | PD_POWERROLE_SINK | PD_SPECREV_2_0; tx.hdr &= ~PD_HDR_MESSAGEID; tx.hdr |= (tx_id_count % 8) << PD_HDR_MESSAGEID_SHIFT; int current = 1; tx.obj[0] = PD_RDO_FV_MAX_CURRENT_SET(current) | PD_RDO_FV_CURRENT_SET(current) | PD_RDO_NO_USB_SUSPEND | PD_RDO_OBJPOS_SET(power_objects); // FIXME fusb_send_message(&tx); printf("# [pd] state 2, request sent.\n"); tx_id_count++; t = 0; request_sent = 1; state = 1; } else if (state == 3) { gpio_put(PIN_LED_R, 1); gpio_put(PIN_USB_SRC_ENABLE, 0); charger_configure(); // charging sleep_ms(1); // running if (t>2000) { printf("# [pd] state 3.\n"); float input_voltage = charger_dump(); max_dump(); if (input_voltage < 6) { printf("# [pd] input voltage below 6v, renegotiate.\n"); state = 0; } t = 0; } } t++; t_report++; } return 0; }