rusefi/firmware/hw_layer/drivers/gpio/tle9104.cpp

561 lines
12 KiB
C++

#include "pch.h"
#include "gpio/gpio_ext.h"
#include "gpio/tle9104.h"
#if defined(BOARD_TLE9104_COUNT) && BOARD_TLE9104_COUNT > 0
/*
* TODO list:
* - support driving outputs over SPI
* - count communication errors, POR events, etc
* - support WD functionality
* - implement direct-io mapping test. Check if user provides correct direct_io data
*/
/*==========================================================================*/
/* Driver local definitions. */
/*==========================================================================*/
#define DRIVER_NAME "tle9104"
/* TODO: aling with WD settings */
#define TLE9104_POLL_INTERVAL_MS 100
static bool drv_task_ready = false;
typedef enum {
TLE9104_DISABLED = 0,
TLE9104_WAIT_INIT,
TLE9104_READY,
TLE9104_FAILED
} tle9104_drv_state;
/* IN MOSI and MISO a read is defined with a s0 and a write is defined with a 1. */
#define TLE9104_WR_REG(addr, val) (BIT(15) | (((addr) & 0x0f) << 8) | ((val) & 0xff))
#define TLE9104_RD_REG(addr) (((addr) & 0x0f) << 8)
#define TLE9104_GET_VAL(rx) ((rx) & 0xff)
#define TLE9104_GET_ADDR(rx) (((rx) >> 8) & 0x0f)
#define TLE9104_PARITY_MASK(val) ((val) & (~BIT(14)))
#define TLE9104_FAULT_GLOBAL BIT(12)
#define TLE9104_FAULT_COMM BIT(13)
#define TLE9104_REG_CTRL 0x00
#define TLE9104_REG_CFG 0x01
#define TLE9104_REG_OFF_DIAG_CFG 0x02
#define TLE9104_REG_ON_DIAG_CFG 0x03
#define TLE9104_REG_DIAG_OUT_1_2_ON 0x04
#define TLE9104_REG_DIAG_OUT_3_4_ON 0x05
#define TLE9104_REG_DIAG_OFF 0x06
#define TLE9104_REG_GLOBAL_STATUS 0x07
#define TLE9104_REG_ICVID 0x08
/*==========================================================================*/
/* Driver exported variables. */
/*==========================================================================*/
/*==========================================================================*/
/* Driver local variables and types. */
/*==========================================================================*/
struct Tle9104 : public GpioChip {
int init() override;
int writePad(size_t pin, int value) override;
brain_pin_diag_e getDiag(size_t pin) override;
void debug() override;
int updateDiagState();
int updateStatus();
const tle9104_config* cfg;
tle9104_drv_state drv_state;
private:
int spi_validate(uint16_t rx);
int spi_rw_array(const uint16_t *tx, uint16_t *rx, int n);
int spi_rw(uint16_t tx, uint16_t *rx);
int read_reg(uint8_t addr, uint8_t *val);
int write_reg(uint8_t addr, uint8_t val);
int chip_init();
OutputPin m_en;
OutputPin m_resn;
/* diagnostic registers */
uint8_t diag_off;
uint8_t diag_on12;
uint8_t diag_on34;
/* statistic */
int por_cnt;
int wdr_cnt;
int init_req_cnt;
int fault_cnt;
int fault_comm_cnt;
int spi_address_err_cnt;
int spi_cnt;
int spi_parity_err_cnt;
};
static Tle9104 chips[BOARD_TLE9104_COUNT];
/*==========================================================================*/
/* Driver local functions. */
/*==========================================================================*/
static bool parityBit(uint16_t val) {
// (1 + number of bits set) mod 2 = parity bit
int count = 1 + __builtin_popcount(val);
return count % 2;
#if 0
while (val != 0) {
if (val & 0x01) {
count++;
}
val = val >> 1;
}
return (count % 2) == 1;
#endif
}
int Tle9104::spi_validate(uint16_t rx)
{
/* with parity bit included */
bool parityOk = !parityBit(rx);
if (!parityOk) {
spi_parity_err_cnt++;
return -1;
}
if (rx & TLE9104_FAULT_GLOBAL) {
fault_cnt++;
}
if (rx & TLE9104_FAULT_COMM) {
fault_comm_cnt++;
}
return 0;
}
int Tle9104::spi_rw(uint16_t tx, uint16_t *rx) {
SPIDriver *spi = cfg->spi_bus;
spi_cnt++;
// set the parity bit appropriately
tx |= parityBit(tx) << 14;
/* Acquire ownership of the bus. */
spiAcquireBus(spi);
/* Setup transfer parameters. */
spiStart(spi, &cfg->spi_config);
/* Slave Select assertion. */
spiSelect(spi);
/* Atomic transfer operations. */
uint16_t rxd = spiPolledExchange(spi, tx);
/* Slave Select de-assertion. */
spiUnselect(spi);
/* Ownership release. */
spiReleaseBus(spi);
/* with parity bit included */
int ret = spi_validate(rxd);
if (ret < 0) {
return ret;
}
/* TODO: check Fault Global and Fault Communication flags */
// return data
if (rx) {
*rx = rxd;
}
return 0;
}
int Tle9104::spi_rw_array(const uint16_t *tx, uint16_t *rx, int n)
{
int ret = 0;
SPIDriver *spi = cfg->spi_bus;
if ((n <= 0) || (tx == nullptr)) {
return -2;
}
/* Acquire ownership of the bus. */
spiAcquireBus(spi);
/* Setup transfer parameters. */
spiStart(spi, &cfg->spi_config);
for (int i = 0; i < n; i++) {
spi_cnt++;
/* Slave Select assertion. */
spiSelect(spi);
/* data transfer */
uint16_t rxdata = spiPolledExchange(spi, tx[i]);
if (rx)
rx[i] = rxdata;
/* Slave Select de-assertion. */
spiUnselect(spi);
/* validate reply */
ret = spi_validate(rxdata);
if (ret < 0)
break;
if (i >= 1) {
/* validate that we received correct answer to previous tx */
if (TLE9104_GET_ADDR(tx[i - 1] != TLE9104_GET_ADDR(rxdata))) {
spi_address_err_cnt++;
ret = -2;
break;
}
}
}
/* Ownership release. */
spiReleaseBus(spi);
/* no errors for now */
return ret;
}
int Tle9104::read_reg(uint8_t addr, uint8_t *val) {
int ret;
ret = spi_rw(TLE9104_RD_REG(addr), nullptr);
if (ret) {
return ret;
}
uint16_t rxd;
ret = spi_rw(TLE9104_RD_REG(addr), &rxd);
if (ret) {
return ret;
}
if (val) {
*val = TLE9104_GET_VAL(rxd);
}
return 0;
}
int Tle9104::write_reg(uint8_t addr, uint8_t val) {
// R/W bit is 1 for write
return spi_rw(TLE9104_WR_REG(addr, val), nullptr);
}
int Tle9104::chip_init() {
int ret;
init_req_cnt++;
// disable comms watchdog, enable direct drive on all 4 channels
// TODO: should we enable comms watchdog?
ret = write_reg(TLE9104_REG_CFG, 0x0F);
if (ret) {
return ret;
}
// clear any suprious diag states from startup: first call resets, second reads true state
ret = updateDiagState();
if (ret) {
return ret;
}
ret = updateDiagState();
if (ret) {
return ret;
}
// set output enable, clear all other flags
ret = write_reg(TLE9104_REG_GLOBAL_STATUS, BIT(7));
if (ret) {
return ret;
}
drv_state = TLE9104_READY;
return 0;
}
/*==========================================================================*/
/* Driver thread. */
/*==========================================================================*/
SEMAPHORE_DECL(tle9104_wake, 10 /* or BOARD_TLE9104_COUNT ? */);
static THD_WORKING_AREA(tle9104_thread_1_wa, 256);
static THD_FUNCTION(tle9104_driver_thread, p)
{
int i;
msg_t msg;
(void)p;
chRegSetThreadName(DRIVER_NAME);
while(1) {
msg = chSemWaitTimeout(&tle9104_wake, TIME_MS2I(TLE9104_POLL_INTERVAL_MS));
/* should we care about msg == MSG_TIMEOUT? */
(void)msg;
for (i = 0; i < BOARD_TLE9104_COUNT; i++) {
int ret;
Tle9104& chip = chips[i];
if (!chip.cfg ||
(chip.drv_state == TLE9104_DISABLED) ||
(chip.drv_state == TLE9104_FAILED))
continue;
ret = chip.updateDiagState();
if (ret) {
/* set state to TLE6240_FAILED? */
}
ret = chip.updateStatus();
if (ret) {
/* set state to TLE6240_FAILED? */
}
}
}
}
/*==========================================================================*/
/* Driver exported functions. */
/*==========================================================================*/
int Tle9104::writePad(size_t pin, int value) {
// Inverted since TLE9104 is active low (low level to turn on output)
if (value) {
palClearPad(cfg->direct_io[pin].port, cfg->direct_io[pin].pad);
} else {
palSetPad(cfg->direct_io[pin].port, cfg->direct_io[pin].pad);
}
return 0;
}
int Tle9104::updateDiagState() {
int ret;
/* TODO: implement and use spi_rw_array() */
ret = read_reg(TLE9104_REG_DIAG_OUT_1_2_ON, &diag_on12);
if (ret) {
return ret;
}
ret = read_reg(TLE9104_REG_DIAG_OUT_3_4_ON, &diag_on34);
if (ret) {
return ret;
}
ret = read_reg(TLE9104_REG_DIAG_OFF, &diag_off);
if (ret) {
return ret;
}
// clear diag states
ret = write_reg(TLE9104_REG_DIAG_OUT_1_2_ON, 0);
if (ret) {
return ret;
}
ret = write_reg(TLE9104_REG_DIAG_OUT_3_4_ON, 0);
if (ret) {
return ret;
}
ret = write_reg(TLE9104_REG_DIAG_OFF, 0);
if (ret) {
return ret;
}
return 0;
}
int Tle9104::updateStatus() {
int ret;
uint8_t status;
ret = read_reg(TLE9104_REG_GLOBAL_STATUS, &status);
if (ret) {
return ret;
}
/* Device was reset since last cleared */
if (status & BIT(0)) {
por_cnt++;
need_init = true;
}
/* Communication watchdog timeout occurred */
if (status & BIT(2)) {
wdr_cnt++;
need_init = true;
}
if (need_init) {
ret = chip_init();
if (ret == 0) {
need_init = false;
}
}
return 0;
}
brain_pin_diag_e Tle9104::getDiag(size_t pin) {
uint8_t off_diag;
uint8_t on_diag;
switch (pin) {
case 0:
on_diag = diag_on12;
off_diag = diag_off;
break;
case 1:
on_diag = diag_on12 >> 3;
off_diag = diag_off >> 2;
break;
case 2:
on_diag = diag_on34;
off_diag = diag_off >> 4;
break;
case 3:
on_diag = diag_on34 >> 3;
off_diag = diag_off >> 6;
break;
default:
return PIN_UNKNOWN;
}
// on diag has 3 bits
on_diag = on_diag & 0x7;
// of diag has 2 bits
off_diag = off_diag & 0x3;
int result = 0;
// Decode on-state faults
switch (on_diag) {
case 2:
result |= PIN_SHORT_TO_BAT;
break;
case 3:
// overtemp and overcurrent
result |= PIN_DRIVER_OVERTEMP;
[[fallthrough]];
case 4:
result |= PIN_OVERLOAD;
break;
case 5:
result |= PIN_DRIVER_OVERTEMP;
break;
}
// Decode off-state faults
switch (off_diag) {
case 2:
result |= PIN_OPEN;
break;
case 3:
result |= PIN_SHORT_TO_GND;
break;
}
return (brain_pin_diag_e)result;
}
void Tle9104::debug() {
efiPrintf("spi transfers %d with patiry error %d with wrong address %d communication fault counter %d\n",
spi_cnt, spi_parity_err_cnt, spi_address_err_cnt, fault_comm_cnt);
efiPrintf("fault counter %d communication fault counter %d\n",
fault_cnt, fault_comm_cnt);
efiPrintf("POR counter %d reinit counter %d WD counter %d\n",
por_cnt, init_req_cnt, wdr_cnt);
}
int Tle9104::init() {
int ret;
m_resn.initPin(DRIVER_NAME " RESN", cfg->resn);
m_en.initPin(DRIVER_NAME " EN", cfg->en);
// disable outputs
m_en.setValue(false);
// Reset the chip
m_resn.setValue(false);
/* TODO: ensure all direct_io pins valid, otherwise support manipulationg output states over SPI */
for (int i = 0; i < 4; i++) {
gpio_pin_markUsed(cfg->direct_io[i].port, cfg->direct_io[i].pad, DRIVER_NAME " Direct IO");
palSetPadMode(cfg->direct_io[i].port, cfg->direct_io[i].pad, PAL_MODE_OUTPUT_PUSHPULL);
// Ensure all outputs are off
writePad(i, false);
}
if (isBrainPinValid(cfg->resn)) {
chThdSleepMilliseconds(1);
m_resn.setValue(true);
chThdSleepMilliseconds(1);
}
// read ID register
uint8_t id;
ret = read_reg(TLE9104_REG_ICVID, &id);
if (ret) {
return ret;
}
// No chip detected if ID is wrong
if (id != 0xB1) {
return -1;
}
ret = chip_init();
if (ret) {
return ret;
}
// Set hardware enable
m_en.setValue(true);
if (!drv_task_ready) {
chThdCreateStatic(tle9104_thread_1_wa, sizeof(tle9104_thread_1_wa),
PRIO_GPIOCHIP, tle9104_driver_thread, nullptr);
drv_task_ready = true;
}
return 0;
}
int tle9104_add(Gpio base, int index, const tle9104_config* cfg) {
Tle9104& chip = chips[index];
/* already added? */
if (chip.cfg != NULL) {
return -1;
}
chip.cfg = cfg;
chip.drv_state = TLE9104_WAIT_INIT;
return gpiochip_register(base, DRIVER_NAME, chip, 4);
}
#else // BOARD_TLE9104_COUNT > 0
int tle9104_add(Gpio, int, const tle9104_config*) {
return -1;
}
#endif // BOARD_TLE9104_COUNT