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

1314 lines
33 KiB
C++

/*
* tle8888.c
*
* TLE8888 Engine Machine System IC driver
*
* This has worked on a bench - see https://youtu.be/yjs5dh_NKo4
* All SPI and CS pin in OM_DEFAULT mode
*
* @date Mar 25, 2019
* @author Andrey Belomutskiy, (c) 2012-2020
*
* 3.2 Pin Definitions and Functions
*
* IN1-4 Parallel input; Input pin for direct control of power stage OUT1-4
*
* IN5-8 Parallel input; Input pin for direct control of push pull state IGN1-IGN4
*
* Table 24 Direct Drive Input Assignment to Output Stages
* IN9 to IN12 OUT5 to OUT24
*
* Masks/inputs bits:
* 0..3 - OUT1 .. 4 - INJ - OpenDrain: 2.2A - direct
* 4..6 - OUT5 .. 7 - OpenDrain: 4.5A
* 7..12 - OUT8 ..13 - Push/Pull: 20mA - quite weak
* 13..19 - OUT14..20 - OpenDrain: 0.6A
* 20..23 - OUT21..24 - Push/Pull: 0.6A - half bridge (GND or +12v)
* 24..27 - IGN1 .. 4 - Push/Pull: 20mA - direct
*
* Andrey Gusakov, (c) 2019
*/
#include "pch.h"
#include "gpio/tle8888.h"
#if (BOARD_TLE8888_COUNT > 0)
#include "persistent_configuration.h"
#include "hardware.h"
#include "gpio/gpio_ext.h"
static Timer diagResponse;
/*
* TODO list:
*/
/*==========================================================================*/
/* Driver local definitions. */
/*==========================================================================*/
#define DRIVER_NAME "tle8888"
typedef enum {
TLE8888_DISABLED = 0,
TLE8888_WAIT_INIT,
TLE8888_READY,
TLE8888_FAILED
} tle8888_drv_state;
/* SPI communication helpers */
/* C0 */
#define CMD_READ (0 << 0)
#define CMD_WRITE (1 << 0)
/* C7:1 */
#define CMD_REG_ADDR(a) (((a) & 0x7f) << 1)
/* CD7:0 */
#define CMD_REG_DATA(d) (((d) & 0xff) << 8)
#define CMD_W(a, d) (static_cast<uint16_t>((CMD_WRITE | CMD_REG_ADDR(a) | CMD_REG_DATA(d))))
#define CMD_R(a) (static_cast<uint16_t>((CMD_READ | CMD_REG_ADDR(a))))
#define REG_INVALID 0x00
/* Command registers */
#define CMD_CMD0(d) CMD_W(0x01, d)
#define REG_CMD0_MRSE BIT(0)
#define REG_CMD0_MRON BIT(1)
/* Window watchdog open WWDOWT window time = 12.8 mS - fixed value for TLE8888QK */
#define CMD_WWDSERVICECMD CMD_W(0x15, 0x03)
#define CMD_FWDRESPCMD(d) CMD_W(0x16, d)
#define CMD_FWDRESPSYNCCMD(d) CMD_W(0x17, d)
#define CMD_SR_CODE 0x1a
#define CMD_SR CMD_W(CMD_SR_CODE, 0x03)
#define CMD_OE_SET CMD_W(0x1c, 0x02)
#define CMD_OE_CLR CMD_W(0x1c, 0x01)
#define CMD_CHIP_UNLOCK CMD_W(0x1e, 0x01)
//#define CMD_CHIP_LOCK CMD_W(0x1e, 0x02)
/* Diagnostic registers */
#define REG_DIAG(n) (0x20 + ((n) & 0x01))
#define CMD_DIAG(n) CMD_R(REG_DIAG(n))
#define CMD_VRSDIAG(n) CMD_R(0x22 + ((n) & 0x01))
#define CMD_COMDIAG CMD_R(0x24)
#define CMD_OUTDIAG(n) CMD_R(0x25 + ((n) & 0x07))
#define CMD_PPOVDIAG CMD_R(0x2a)
#define CMD_BRIDIAG(n) CMD_R(0x2b + ((n) & 0x01))
#define CMD_IGNDIAG CMD_R(0x2d)
#define CMD_WDDIAG CMD_R(0x2e)
/* Status registers */
#define REG_OPSTAT(n) (0x34 + ((n) & 0x01))
#define CMD_OPSTAT(n) CMD_R(REG_OPSTAT(n))
#define REG_OPSTAT_MR BIT(3)
#define REG_OPSTAT_WAKE BIT(1)
#define REG_OPSTAT_KEY BIT(0)
#define REG_WWDSTAT 0x36
#define CMD_WWDSTAT CMD_R(REG_WWDSTAT)
#define REG_FWDSTAT(n) (0x37 + ((n) & 0x01))
#define CMD_FWDSTAT(n) CMD_R(REG_FWDSTAT(n))
#define REG_TECSTAT 0x39
#define CMD_TECSTAT CMD_R(REG_TECSTAT)
/* Configuration registers */
#define CMD_OUTCONFIG(n, d) CMD_W(0x40 + (n), d)
#define CMD_BRICONFIG(n, d) CMD_W(0x46 + ((n) & 0x01), d)
#define CMD_IGNCONFIG(d) CMD_W(0x48, d)
#define CMD_VRSCONFIG(n, d) CMD_W(0x49 + ((n) & 0x03), d)
#define CMD_OPCONFIG0(d) CMD_W(0x4e, d)
#define CMD_INCONFIG(n, d) CMD_W(0x53 + ((n) & 0x03), d)
#define CMD_DDCONFIG(n, d) CMD_W(0x57 + ((n) & 0x03), d)
#define CMD_OECONFIG(n, d) CMD_W(0x5b + ((n) & 0x03), d)
/* Control registers */
#define CMD_CONT(n, d) CMD_W(0x7b + ((n) & 0x03), d)
/* Looks like reset value is 113.6ms? 1.6ms * 0x47 */
#define FWD_PERIOD_MS (20)
/* Default closed window time is 0b0011.1111 * 1.6 = 100.8mS
* Default open window time is 0b0011 * 3.2 = 12.8 mS */
#define WWD_PERIOD_MS (100.8 + (12.8 / 2))
/* TODO: add irq support */
#define DIAG_PERIOD_MS (7)
const uint8_t tle8888_fwd_responses[16][4] = {
/* Reverse order:
* RESP3,RESP2,RESP1,RESP0 */
{0xFF, 0x0F, 0xF0, 0x00},
{0xB0, 0x40, 0xBF, 0x4F},
{0xE9, 0x19, 0xE6, 0x16},
{0xA6, 0x56, 0xA9, 0x59},
{0x75, 0x85, 0x7A, 0x8A},
{0x3A, 0xCA, 0x35, 0xC5},
{0x63, 0x93, 0x6C, 0x9C},
{0x2C, 0xDC, 0x23, 0xD3},
{0xD2, 0x22, 0xDD, 0x2D},
{0x9D, 0x6D, 0x92, 0x62},
{0xC4, 0x34, 0xCB, 0x3B},
{0x8B, 0x7B, 0x84, 0x74},
{0x58, 0xA8, 0x57, 0xA7},
{0x17, 0xE7, 0x18, 0xE8},
{0x4E, 0xBE, 0x41, 0xB1},
{0x01, 0xF1, 0x0E, 0xFE}
};
/*==========================================================================*/
/* Driver exported variables. */
/*==========================================================================*/
/*==========================================================================*/
/* Driver local variables and types. */
/*==========================================================================*/
/* Driver private data */
struct Tle8888 : public GpioChip {
int init() override;
int deinit() override;
int setPadMode(size_t pin, iomode_t mode) override;
int writePad(size_t pin, int value) override;
int readPad(size_t pin) override;
brain_pin_diag_e getDiag(size_t pin) override;
// internal functions
void read_reg(uint16_t reg, uint16_t* val);
int spi_rw(uint16_t tx, uint16_t *rx_ptr);
int spi_validate(uint16_t rx);
int spi_rw_array(const uint16_t *tx, uint16_t *rx, int n);
int update_status_and_diag();
int update_output();
int update_direct_output(size_t pin, int value);
int wake_driver();
int chip_reset();
int chip_init();
int wwd_feed();
int fwd_feed();
int wd_get_status();
int wd_feed();
int calc_sleep_interval();
brain_pin_diag_e getOutputDiag(size_t pin);
brain_pin_diag_e getInputDiag(size_t pin);
int chip_init_data();
const tle8888_config *cfg;
/* thread stuff */
thread_t *thread;
THD_WORKING_AREA(thread_wa, 256);
semaphore_t wake;
/* state to be sent to chip */
uint32_t o_state;
/* direct driven output mask */
uint32_t o_direct_mask;
/* output enabled mask */
uint32_t o_oe_mask;
/* push-pull enabled mask (for OUT21..OUT24 only) */
/* this is overhead to store 4 bits in uint32_t
* but I don't want any magic shift math */
uint32_t o_pp_mask;
/* cached output registers state - value last send to chip */
uint32_t o_data_cached;
tle8888_drv_state drv_state;
/* direct drive mapping registers */
uint8_t InConfig[TLE8888_DIRECT_MISC];
/* last accessed register, for validation on next SPI access */
uint8_t last_reg;
/* diagnostic registers */
uint8_t OutDiag[5];
uint8_t PPOVDiag;
uint8_t BriDiag[2];
uint8_t IgnDiag;
/* status registers */
uint8_t OpStat[2];
/* last diagnostic was read */
systime_t diag_ts;
/* WD stuff */
uint8_t wwd_err_cnt;
uint8_t fwd_err_cnt;
uint8_t tot_err_cnt;
uint8_t wd_diag;
bool wd_happy;
systime_t wwd_ts;
systime_t fwd_ts;
/* chip needs reintialization due to some critical issue */
bool need_init;
/* main relay output */
bool mr_manual;
/* statistic */
int por_cnt;
int wdr_cnt;
int comfe_cnt;
int init_cnt;
int init_req_cnt;
int spi_cnt;
uint16_t tx;
uint16_t rx;
};
static Tle8888 chips[BOARD_TLE8888_COUNT];
static const char* tle8888_pin_names[TLE8888_SIGNALS] = {
"TLE8888.INJ1", "TLE8888.INJ2", "TLE8888.INJ3", "TLE8888.INJ4",
"TLE8888.OUT5", "TLE8888.OUT6", "TLE8888.OUT7", "TLE8888.OUT8",
"TLE8888.OUT9", "TLE8888.OUT10", "TLE8888.OUT11", "TLE8888.OUT12",
"TLE8888.OUT13", "TLE8888.OUT14", "TLE8888.OUT15", "TLE8888.OUT16",
"TLE8888.OUT17", "TLE8888.OUT18", "TLE8888.OUT19", "TLE8888.OUT20",
"TLE8888.OUT21", "TLE8888.OUT22", "TLE8888.OUT23", "TLE8888.OUT24",
"TLE8888.IGN1", "TLE8888.IGN2", "TLE8888.IGN3", "TLE8888.IGN4",
"TLE8888.MR", "TLE8888.KEY", "TLE8888.WAKE"
};
#if EFI_TUNER_STUDIO
// set debug_mode 31
void tle8888PostState() {
Tle8888 *chip = &chips[0];
engine->outputChannels.debugIntField1 = chip->wwd_err_cnt;
engine->outputChannels.debugIntField2 = chip->fwd_err_cnt;
engine->outputChannels.debugIntField3 = chip->tot_err_cnt;
//engine->outputChannels.debugIntField1 = chip->spi_cnt;
//engine->outputChannels.debugIntField2 = chip->tx;
//engine->outputChannels.debugIntField3 = chip->rx;
engine->outputChannels.debugIntField5 = chip->init_cnt;
engine->outputChannels.debugFloatField3 = chip->OpStat[1];
engine->outputChannels.debugFloatField4 = chip->por_cnt * 1000000 + chip->init_req_cnt * 10000;
engine->outputChannels.debugFloatField5 = 0;
engine->outputChannels.debugFloatField6 = 0;
}
#endif /* EFI_TUNER_STUDIO */
/*==========================================================================*/
/* Driver local functions. */
/*==========================================================================*/
int Tle8888::spi_validate(uint16_t rx)
{
uint8_t reg = getRegisterFromResponse(rx);
if ((last_reg != REG_INVALID) && (last_reg != reg)) {
/* unexpected SPI answers */
if (reg == REG_OPSTAT(0)) {
/* after power on reset: the address and the content of the
* status register OpStat0 is transmitted with the next SPI
* transmission */
por_cnt++;
} else if (reg == REG_FWDSTAT(1)) {
/* after watchdog reset: the address and the content of the
* diagnosis register FWDStat1 is transmitted with the first
* SPI transmission after the low to high transition of RST */
wdr_cnt++;
} else if (reg == REG_DIAG(0)) {
/* after an invalid communication frame: the address and the
* content of the diagnosis register Diag0 is transmitted
* with the next SPI transmission and the bit COMFE in
* diagnosis register ComDiag is set to "1" */
comfe_cnt++;
}
/* during power on reset: SPI commands are ignored, SDO is always
* tristate */
/* during watchdog reset: SPI commands are ignored, SDO has the
* value of the status flag */
need_init = true;
return -1;
}
return 0;
}
/**
* @returns -1 in case of communication error
*/
int Tle8888::spi_rw(uint16_t tx, uint16_t *rx_ptr)
{
int ret;
uint16_t rx;
SPIDriver *spi = cfg->spi_bus;
/**
* 15.1 SPI Protocol
*
* after a read or write command: the address and content of the selected register
* is transmitted with the next SPI transmission (for not existing addresses or
* wrong access mode the data is always 0)
*/
/* Acquire ownership of the bus. */
spiAcquireBus(spi);
/* Setup transfer parameters. */
spiStart(spi, &cfg->spi_config);
/* Slave Select assertion. */
spiSelect(spi);
/* Atomic transfer operations. */
rx = spiPolledExchange(spi, tx);
/* Slave Select de-assertion. */
spiUnselect(spi);
/* Ownership release. */
spiReleaseBus(spi);
/* statisctic and debug */
this->tx = tx;
this->rx = rx;
this->spi_cnt++;
if (rx_ptr)
*rx_ptr = rx;
/* validate reply and save last accessed register */
ret = spi_validate(rx);
last_reg = getRegisterFromResponse(tx);
/* no errors for now */
return ret;
}
/**
* @return -1 in case of communication error
*/
int Tle8888::spi_rw_array(const uint16_t *tx, uint16_t *rx, int n)
{
int ret = 0;
SPIDriver *spi = cfg->spi_bus;
if (n <= 0) {
return -2;
}
/**
* 15.1 SPI Protocol
*
* after a read or write command: the address and content of the selected register
* is transmitted with the next SPI transmission (for not existing addresses or
* wrong access mode the data is always 0)
*/
/* Acquire ownership of the bus. */
spiAcquireBus(spi);
/* Setup transfer parameters. */
spiStart(spi, &cfg->spi_config);
for (int i = 0; i < n; i++) {
/* 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);
/* statistic and debug */
this->tx = tx[i];
this->rx = rxdata;
this->spi_cnt++;
/* validate reply and save last accessed register */
ret = spi_validate(rxdata);
last_reg = getRegisterFromResponse(tx[i]);
if (ret < 0)
break;
}
/* Ownership release. */
spiReleaseBus(spi);
/* no errors for now */
return ret;
}
/**
* @brief TLE8888 send output registers data.
* @details Sends ORed data to register.
*/
int Tle8888::update_output()
{
int i;
int ret;
uint8_t briconfig0 = 0;
/* calculate briconfig0 */
for (i = 20; i < 24; i++) {
if (o_pp_mask & BIT(i)) {
if (o_state & BIT(i)) {
/* low-side switch mode */
} else {
/* else enable high-side switch mode */
briconfig0 |= BIT((i - 20) * 2);
}
}
}
/* TODO: set freewheeling bits in briconfig0? */
/* TODO: apply hi-Z mask when support will be added */
/* set value only for non-direct driven pins */
uint32_t o_data = o_state & ~o_direct_mask;
/* output for push-pull pins is allways enabled
* (at least until we start supporting hi-Z state) */
o_data |= o_pp_mask;
uint16_t tx[] = {
/* bridge config */
CMD_BRICONFIG(0, briconfig0),
/* output enables */
CMD_CONT(0, o_data >> 0),
CMD_CONT(1, o_data >> 8),
CMD_CONT(2, o_data >> 16),
CMD_CONT(3, o_data >> 24),
/* Main Relay output: manual vs auto-mode */
CMD_CMD0((mr_manual ? REG_CMD0_MRSE : 0x0) |
((o_data & BIT(TLE8888_OUTPUT_MR)) ? REG_CMD0_MRON : 0x0))
};
ret = spi_rw_array(tx, NULL, ARRAY_SIZE(tx));
if (ret == 0) {
/* atomic */
o_data_cached = o_data;
}
return ret;
}
/**
* @brief read TLE8888 diagnostic registers data.
* @details Chained read of several registers
*/
int Tle8888::update_status_and_diag()
{
int ret = 0;
const uint16_t tx[] = {
CMD_OUTDIAG(0),
CMD_OUTDIAG(1),
CMD_OUTDIAG(2),
CMD_OUTDIAG(3),
CMD_OUTDIAG(4),
CMD_PPOVDIAG,
CMD_BRIDIAG(0),
CMD_BRIDIAG(1),
CMD_IGNDIAG,
CMD_OPSTAT(0),
CMD_OPSTAT(1),
CMD_OPSTAT(1)
};
uint16_t rx[ARRAY_SIZE(tx)];
ret = spi_rw_array(tx, rx, ARRAY_SIZE(tx));
if (ret == 0) {
/* the address and content of the selected register is transmitted with the
* next SPI transmission */
OutDiag[0] = getDataFromResponse(rx[0 + 1]);
OutDiag[1] = getDataFromResponse(rx[1 + 1]);
OutDiag[2] = getDataFromResponse(rx[2 + 1]);
OutDiag[3] = getDataFromResponse(rx[3 + 1]);
OutDiag[4] = getDataFromResponse(rx[4 + 1]);
PPOVDiag = getDataFromResponse(rx[5 + 1]);
BriDiag[0] = getDataFromResponse(rx[6 + 1]);
BriDiag[1] = getDataFromResponse(rx[7 + 1]);
IgnDiag = getDataFromResponse(rx[8 + 1]);
OpStat[0] = getDataFromResponse(rx[9 + 1]);
OpStat[1] = getDataFromResponse(rx[10 + 1]);
}
return ret;
}
/**
* @brief Drives natve MCU pins connected to TLE8888 inputs
* @details This is faster than updating Cont registers over SPI
*/
int Tle8888::update_direct_output(size_t pin, int value)
{
int index = -1;
if (pin < 4) {
/* OUT1..4 */
index = pin;
} else if (pin >= 24) {
/* IGN1..4 */
index = (pin - 24) + 4;
} else {
/* find remapable direct drive gpio */
for (int i = 0; i < TLE8888_DIRECT_MISC; i++) {
/* again: outputs in cfg counted starting from 1 - hate this */
if (cfg->direct_maps[i].output == pin + 1) {
index = 8 + i;
break;
}
}
}
/* direct gpio not found */
if (index < 0)
return -1;
if (value)
palSetPort(cfg->direct_gpio[index].port,
PAL_PORT_BIT(cfg->direct_gpio[index].pad));
else
palClearPort(cfg->direct_gpio[index].port,
PAL_PORT_BIT(cfg->direct_gpio[index].pad));
return 0;
}
/**
* @brief TLE8888 chip driver wakeup.
* @details Wake up driver. Will cause output register update
*/
int Tle8888::wake_driver()
{
/* Entering a reentrant critical zone.*/
chibios_rt::CriticalSectionLocker csl;
chSemSignalI(&wake);
if (!port_is_isr_context()) {
/**
* chSemSignalI above requires rescheduling
* interrupt handlers have implicit rescheduling
*/
chSchRescheduleS();
}
return 0;
}
static brain_pin_diag_e tle8888_2b_to_diag_no_temp(unsigned int bits)
{
if (bits == 0x01)
return PIN_SHORT_TO_BAT;
if (bits == 0x02)
return PIN_OPEN;
if (bits == 0x03)
return PIN_SHORT_TO_GND;
return PIN_OK;
}
static brain_pin_diag_e tle8888_2b_to_diag_with_temp(unsigned int bits)
{
int diag = tle8888_2b_to_diag_no_temp(bits);
if (diag == PIN_SHORT_TO_BAT)
diag |= PIN_DRIVER_OVERTEMP;
return static_cast<brain_pin_diag_e>(diag);
}
int Tle8888::chip_reset() {
int ret;
ret = spi_rw(CMD_SR, NULL);
/**
* Table 8. Reset Times. All reset times not more than 20uS
*/
chThdSleepMilliseconds(3);
last_reg = REG_INVALID;
return ret;
}
int Tle8888::chip_init()
{
int ret;
/* statistic */
init_cnt++;
uint16_t tx[] = {
/* unlock */
CMD_CHIP_UNLOCK,
/* set INCONFIG - aux input mapping */
CMD_INCONFIG(0, InConfig[0]),
CMD_INCONFIG(1, InConfig[1]),
CMD_INCONFIG(2, InConfig[2]),
CMD_INCONFIG(3, InConfig[3]),
/* Diagnnostic settings */
/* Enable open load detection and disable switch off
* in case of overcurrent for OUTPUT1..4 */
CMD_OUTCONFIG(0, BIT(7) | BIT(5) | BIT(3) | BIT(1)),
/* Enable open load detection and disable switch off
* in case of overcurrent for OUTPUT5..7 */
CMD_OUTCONFIG(1, BIT(5) | BIT(3) | BIT(1)),
#if 1
/* MRE 0.5.? share same outputs between OUTPUT8..13
* and analog inputs. Disable diagnostic pull-down
* not to affect analog inputs.
* Disable open load detection and set short to bat
* thresholt to 125 mV (default) for OUTPUT8..13 */
CMD_OUTCONFIG(2, (0x0 << 6) | 0x00),
#else
/* Enable open load detection and set short to bat
* thresholt to 125 mV (default) for OUTPUT8..13 */
CMD_OUTCONFIG(2, (0x0 << 6) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)),
#endif
/* Enable open load detection and disable switch off
* in case of overcurrent for OUTPUT14
* Set short to bat threshold to 125mV (default) for
* OUTPUT12..13 and OUTPUT10..11 */
CMD_OUTCONFIG(3, BIT(5) | (0x0 << 2) | (0x0 << 0)),
/* No delayed off function for OUTPUT17
* Enable open load detection and disable switch off
* in case of overcurrent for OUTPUT15..17 */
CMD_OUTCONFIG(4, BIT(5) | BIT(3) | BIT(1)),
/* Enable open load detection and disable switch off
* in case of overcurrent for OUTPUT18..20 */
CMD_OUTCONFIG(5, BIT(5) | BIT(3) | BIT(1)),
/* set OE and DD registers */
CMD_OECONFIG(0, o_oe_mask >> 0),
CMD_DDCONFIG(0, o_direct_mask >> 0),
CMD_OECONFIG(1, o_oe_mask >> 8),
CMD_DDCONFIG(1, o_direct_mask >> 8),
CMD_OECONFIG(2, o_oe_mask >> 16),
CMD_DDCONFIG(2, o_direct_mask >> 16),
CMD_OECONFIG(3, o_oe_mask >> 24),
CMD_DDCONFIG(3, o_direct_mask >> 24),
/* set VR mode: VRS/Hall */
CMD_VRSCONFIG(1, (0 << 4) |
(cfg->mode << 2) |
(0 << 0)),
/* enable outputs */
CMD_OE_SET
};
ret = spi_rw_array(tx, NULL, ARRAY_SIZE(tx));
if (ret == 0) {
/* enable pins */
if (cfg->ign_en.port)
palSetPort(cfg->ign_en.port, PAL_PORT_BIT(cfg->ign_en.pad));
if (cfg->inj_en.port)
palSetPort(cfg->inj_en.port, PAL_PORT_BIT(cfg->inj_en.pad));
}
if (engineConfiguration->verboseTLE8888) {
tle8888_dump_regs();
}
return ret;
}
int Tle8888::wwd_feed() {
spi_rw(CMD_WWDSERVICECMD, NULL);
return 0;
}
int Tle8888::fwd_feed() {
uint16_t reg;
spi_rw(CMD_FWDSTAT(1), NULL);
/* here we get response of the 'FWDStat1' above */
spi_rw(CMD_WDDIAG, &reg);
uint8_t data = getDataFromResponse(reg);
uint8_t fwdquest = data & 0xF;
uint8_t fwdrespc = (data >> 4) & 3;
/* Table lines are filled in reverse order (like in DS) */
uint8_t response = tle8888_fwd_responses[fwdquest][3 - fwdrespc];
if (fwdrespc != 0) {
spi_rw(CMD_FWDRESPCMD(response), NULL);
} else {
/* to restart heartbeat timer, sync command should be used for response 0 */
spi_rw(CMD_FWDRESPSYNCCMD(response), NULL);
}
return 0;
}
int Tle8888::wd_get_status() {
uint16_t reg;
spi_rw(CMD_WDDIAG, NULL);
spi_rw(CMD_WDDIAG, &reg);
wd_diag = getDataFromResponse(reg);
if (wd_diag & 0x70) {
/* Reset caused by TEC
* Reset caused by FWD
* Reset caused by WWD */
return -1;
}
if (wd_diag & 0x0f) {
/* Some error in WD handling */
return 1;
}
return 0;
}
int Tle8888::wd_feed() {
bool update_status;
if (wwd_ts <= chVTGetSystemTimeX()) {
update_status = true;
if (wwd_feed() == 0) {
wwd_ts = chTimeAddX(chVTGetSystemTimeX(), TIME_MS2I(WWD_PERIOD_MS));
}
}
if (fwd_ts <= chVTGetSystemTimeX()) {
update_status = true;
if (fwd_feed() == 0) {
fwd_ts = chTimeAddX(chVTGetSystemTimeX(), TIME_MS2I(FWD_PERIOD_MS));
}
}
if (update_status) {
uint16_t wwd_reg, fwd_reg, tec_reg;
spi_rw(CMD_WWDSTAT, NULL);
spi_rw(CMD_FWDSTAT(0), &wwd_reg);
spi_rw(CMD_TECSTAT, &fwd_reg);
spi_rw(CMD_TECSTAT, &tec_reg);
wwd_err_cnt = getDataFromResponse(wwd_reg) & 0x7f;
fwd_err_cnt = getDataFromResponse(fwd_reg) & 0x7f;
tot_err_cnt = getDataFromResponse(tec_reg) & 0x7f;
wd_happy = ((wwd_err_cnt == 0) &&
(fwd_err_cnt == 0));
return wd_get_status();
} else {
return 0;
}
}
int Tle8888::calc_sleep_interval() {
systime_t now = chVTGetSystemTimeX();
sysinterval_t wwd_delay = chTimeDiffX(now, wwd_ts);
sysinterval_t fwd_delay = chTimeDiffX(now, fwd_ts);
sysinterval_t diag_delay = chTimeDiffX(now, diag_ts);
if ((diag_delay <= wwd_delay) && (diag_delay <= fwd_delay))
return diag_delay;
if (fwd_delay <= wwd_delay)
return fwd_delay;
return wwd_delay;
}
/*==========================================================================*/
/* Driver thread. */
/*==========================================================================*/
static THD_FUNCTION(tle8888_driver_thread, p) {
Tle8888 *chip = reinterpret_cast<Tle8888*>(p);
sysinterval_t poll_interval = 0;
chRegSetThreadName(DRIVER_NAME);
while (1) {
int ret;
msg_t msg = chSemWaitTimeout(&chip->wake, poll_interval);
/* should we care about msg == MSG_TIMEOUT? */
(void)msg;
/* default polling interval */
poll_interval = TIME_MS2I(DIAG_PERIOD_MS);
if ((chip->cfg == NULL) ||
(chip->drv_state == TLE8888_DISABLED) ||
(chip->drv_state == TLE8888_FAILED))
continue;
bool wd_happy = chip->wd_happy;
/* update outputs only if WD is happy */
if ((wd_happy) || (1)) {
ret = chip->update_output();
if (ret) {
/* set state to TLE8888_FAILED? */
}
}
ret = chip->wd_feed();
if (ret < 0) {
/* WD is not happy */
continue;
}
/* happiness state has changed! */
if ((chip->wd_happy != wd_happy) && (chip->wd_happy)) {
chip->need_init = true;
}
if (chip->need_init) {
/* clear first, as flag can be raised again during init */
chip->need_init = false;
/* re-init chip! */
chip->chip_init();
/* sync pins state */
chip->update_output();
}
if (chip->diag_ts <= chVTGetSystemTimeX()) {
/* this is expensive call, will do a lot of spi transfers... */
ret = chip->update_status_and_diag();
if (ret) {
/* set state to TLE8888_FAILED or force reinit? */
} else {
diagResponse.reset();
}
/* TODO:
* Procedure to switch on after failure condition occurred:
* - Read out of diagnosis bits
* - Second read out to verify that the failure conditions are not
* remaining
* - Set of the dedicated output enable bit of the affected channel
* if the diagnosis bit is not active anymore
* - Switch on of the channel */
chip->diag_ts = chTimeAddX(chVTGetSystemTimeX(), TIME_MS2I(DIAG_PERIOD_MS));
}
poll_interval = chip->calc_sleep_interval();
}
}
/*==========================================================================*/
/* Driver interrupt handlers. */
/*==========================================================================*/
/*==========================================================================*/
/* Driver exported functions. */
/*==========================================================================*/
int Tle8888::setPadMode(unsigned int pin, iomode_t mode) {
if (pin >= TLE8888_SIGNALS)
return -1;
/* if someone has requested MR pin - switch it to manual mode */
if (pin == TLE8888_OUTPUT_MR) {
mr_manual = true;
}
/* do not enalbe PP mode yet */
#if 0
/* only OUT21..OUT24 support mode change: PP vs OD */
if ((pin < 20) || (pin > 23))
return 0;
/* this is absolutly confusing... we pass STM32 specific
* values to tle8888 driver... But this is how gpios
* currently implemented */
if ((mode & PAL_STM32_OTYPE_MASK) == PAL_STM32_OTYPE_OPENDRAIN) {
o_pp_mask &= ~BIT(pin);
} else {
o_pp_mask |= BIT(pin);
}
#else
(void)mode;
#endif
return 0;
}
int Tle8888::writePad(unsigned int pin, int value) {
if (pin >= TLE8888_OUTPUTS)
return -1;
{
chibios_rt::CriticalSectionLocker csl;
if (value) {
o_state |= BIT(pin);
} else {
o_state &= ~BIT(pin);
}
}
/* direct driven? */
if (o_direct_mask & BIT(pin)) {
return update_direct_output(pin, value);
} else {
return wake_driver();
}
return 0;
}
int Tle8888::readPad(size_t pin) {
if (pin >= TLE8888_OUTPUTS)
return -1;
if (pin < TLE8888_OUTPUTS_REGULAR) {
/* return output state */
/* TODO: check that pins is disabled by diagnostic? */
return !!(o_data_cached & BIT(pin));
} else if (pin == TLE8888_OUTPUT_MR) {
/* Main relay can be enabled by KEY input, so report real state */
return !!(OpStat[0] & REG_OPSTAT_MR);
} else if (pin == TLE8888_INPUT_KEY) {
return !!(OpStat[0] & REG_OPSTAT_KEY);
} if (pin == TLE8888_INPUT_WAKE) {
return !!(OpStat[0] & REG_OPSTAT_WAKE);
}
/* unknown pin */
return -1;
}
brain_pin_diag_e Tle8888::getOutputDiag(size_t pin) {
if (diagResponse.hasElapsedMs(500)) {
// has been to long since we've recieved diagnostics
return PIN_DRIVER_OFF;
}
/* OUT1..OUT4, indexes 0..3 */
if (pin < 4)
return tle8888_2b_to_diag_with_temp((OutDiag[0] >> ((pin - 0) * 2)) & 0x03);
/* OUT5..OUT7, indexes 4..6 */
if (pin < 7) {
return tle8888_2b_to_diag_with_temp((OutDiag[1] >> ((pin - 4) * 2)) & 0x03);
}
/* OUT8 to OUT13, indexes 7..12 */
if (pin < 13) {
int ret;
/* OUT8 */
if (pin == 7)
ret = tle8888_2b_to_diag_no_temp((OutDiag[1] >> 6) & 0x03);
/* OUT9..OUT12 */
else if (pin < 12)
ret = tle8888_2b_to_diag_no_temp((OutDiag[2] >> ((pin - 8) * 2)) & 0x03);
/* OUT13 */
else /* if (pin == 12) */
ret = tle8888_2b_to_diag_no_temp((OutDiag[3] >> 0) & 0x03);
/* overvoltage bit */
if (PPOVDiag & BIT(pin - 7))
ret |= PIN_SHORT_TO_BAT;
return static_cast<brain_pin_diag_e>(ret);
}
/* OUT14 to OUT16, indexes 13..15 */
if (pin < 16)
return tle8888_2b_to_diag_with_temp((OutDiag[3] >> ((pin - 13 + 1) * 2)) & 0x03);
/* OUT17 to OUT20, indexes 16..19 */
if (pin < 20)
return tle8888_2b_to_diag_with_temp((OutDiag[4] >> ((pin - 16) * 2)) & 0x03);
/* OUT21..OUT24, indexes 20..23 */
if (pin < 24) {
/* half bridges */
int diag;
diag = tle8888_2b_to_diag_no_temp((BriDiag[0] >> ((pin - 20) * 2)) & 0x03);
if (((pin == 22) || (pin == 23)) &&
(BriDiag[1] & BIT(5)))
diag |= PIN_DRIVER_OVERTEMP;
if (((pin == 20) || (pin == 21)) &&
(BriDiag[1] & BIT(4)))
diag |= PIN_DRIVER_OVERTEMP;
if (BriDiag[1] & BIT(pin - 20))
diag |= PIN_OVERLOAD; /* overcurrent */
return static_cast<brain_pin_diag_e>(diag);
}
if (pin < 28)
return tle8888_2b_to_diag_with_temp((IgnDiag >> ((pin - 24) * 2)) & 0x03);
return PIN_OK;
}
brain_pin_diag_e Tle8888::getInputDiag(unsigned int pin)
{
(void)pin;
return PIN_OK;
}
brain_pin_diag_e Tle8888::getDiag(size_t pin)
{
if (pin >= TLE8888_SIGNALS)
return PIN_INVALID;
if (pin < TLE8888_OUTPUTS)
return getOutputDiag(pin);
else
return getInputDiag(pin);
}
int Tle8888::chip_init_data() {
int i;
int ret = 0;
o_direct_mask = 0;
o_oe_mask = 0;
o_pp_mask = 0;
/* mark pins used */
if (cfg->reset.port != NULL) {
ret |= gpio_pin_markUsed(cfg->reset.port, cfg->reset.pad, DRIVER_NAME " RST");
palSetPadMode(cfg->reset.port, cfg->reset.pad, PAL_MODE_OUTPUT_PUSHPULL);
palSetPort(cfg->reset.port, PAL_PORT_BIT(cfg->reset.pad));
}
if (cfg->ign_en.port != NULL) {
ret |= gpio_pin_markUsed(cfg->ign_en.port, cfg->ign_en.pad, DRIVER_NAME " IGN EN");
palSetPadMode(cfg->ign_en.port, cfg->ign_en.pad, PAL_MODE_OUTPUT_PUSHPULL);
palClearPort(cfg->ign_en.port, PAL_PORT_BIT(cfg->ign_en.pad));
}
if (cfg->inj_en.port != NULL) {
ret |= gpio_pin_markUsed(cfg->inj_en.port, cfg->inj_en.pad, DRIVER_NAME " INJ EN");
palSetPadMode(cfg->inj_en.port, cfg->inj_en.pad, PAL_MODE_OUTPUT_PUSHPULL);
palClearPort(cfg->inj_en.port, PAL_PORT_BIT(cfg->inj_en.pad));
}
for (i = 0; i < TLE8888_DIRECT_MISC; i++) {
/* Set some invalid default OUT number...
* Keeping this register default (0) will map one of input signals
* to OUT5 and no control over SPI for this pin will be possible.
* If some other pin is also mapped to OUT5 both inputs should be
* high (logical AND) to enable OUT5.
* Set non-exist output in case no override is provided in config.
* See code below */
InConfig[i] = 25 - 1 - 4;
}
for (i = 0; i < TLE8888_DIRECT_OUTPUTS; i++) {
int out = -1;
uint32_t mask;
if (i < 4) {
/* injector */
out = i;
} else if (i < 8) {
/* ignition */
out = (i - 4) + 24;
} else {
/* remappable */
/* in config counted from 1 */
out = cfg->direct_maps[i - 8].output - 1;
}
if ((out < 0) || (cfg->direct_gpio[i].port == NULL)) {
/* now this is safe, InConfig[] is inited with some non-exist output */
continue;
}
/* TODO: implement PP pin driving throught direct gpio */
if ((cfg->stepper) && (out >= 20) && (out <= 23)) {
/* now this is safe, InConfig[] is inited with some non-exist output */
continue;
}
/* calculate mask */
mask = BIT(out);
/* check if output already occupied */
if (o_direct_mask & mask) {
/* incorrect config? */
ret = -1;
goto err_gpios;
}
/* configure source gpio */
ret = gpio_pin_markUsed(cfg->direct_gpio[i].port, cfg->direct_gpio[i].pad, DRIVER_NAME " DIRECT IO");
if (ret) {
ret = -1;
goto err_gpios;
}
palSetPadMode(cfg->direct_gpio[i].port, cfg->direct_gpio[i].pad, PAL_MODE_OUTPUT_PUSHPULL);
palClearPort(cfg->direct_gpio[i].port, PAL_PORT_BIT(cfg->direct_gpio[i].pad));
/* enable direct drive */
o_direct_mask |= mask;
/* calculate INCONFIG - aux input mapping for IN9..IN12 */
if (i >= 8) {
if ((out < 4) || (out >= 24)) {
ret = -1;
goto err_gpios;
}
InConfig[i - 8] = out - 4;
}
}
/* Enable Push-Pull mode for OUT21..OUT24 */
if (cfg->stepper) {
o_pp_mask |= BIT(20) | BIT(21) | BIT(22) | BIT(23);
}
/* enable all direct driven */
o_oe_mask |= o_direct_mask;
/* enable all ouputs
* TODO: add API to enable/disable? */
o_oe_mask |= 0x0ffffff0;
return 0;
err_gpios:
/* unmark pins */
if (cfg->inj_en.port != NULL)
gpio_pin_markUnused(cfg->inj_en.port, cfg->inj_en.pad);
if (cfg->ign_en.port != NULL)
gpio_pin_markUnused(cfg->ign_en.port, cfg->ign_en.pad);
if (cfg->reset.port != NULL)
gpio_pin_markUnused(cfg->reset.port, cfg->reset.pad);
for (int i = 0; i < TLE8888_DIRECT_OUTPUTS; i++) {
if (cfg->direct_gpio[i].port) {
gpio_pin_markUnused(cfg->direct_gpio[i].port, cfg->direct_gpio[i].pad);
}
}
return ret;
}
int Tle8888::init()
{
int ret;
/* check for multiple init */
if (drv_state != TLE8888_WAIT_INIT)
return -1;
ret = chip_reset();
if (ret)
return ret;
ret = chip_init_data();
if (ret)
return ret;
/* force init from driver thread */
need_init = true;
/* instance is ready */
drv_state = TLE8888_READY;
/* init semaphore */
chSemObjectInit(&wake, 10);
/* start thread */
thread = chThdCreateStatic(thread_wa, sizeof(thread_wa),
PRIO_GPIOCHIP, tle8888_driver_thread, this);
return 0;
}
int Tle8888::deinit()
{
/* disable pins */
if (cfg->ign_en.port)
palClearPort(cfg->ign_en.port, PAL_PORT_BIT(cfg->ign_en.pad));
if (cfg->inj_en.port)
palClearPort(cfg->inj_en.port, PAL_PORT_BIT(cfg->inj_en.pad));
/* stop thread */
chThdTerminate(thread);
return 0;
}
/**
* @brief TLE8888 driver add.
* @details Checks for valid config
* @return return gpio chip base
*/
int tle8888_add(brain_pin_e base, unsigned int index, const tle8888_config *cfg) {
efiAssert(OBD_PCM_Processor_Fault, cfg != NULL, "8888CFG", 0)
/* no config or no such chip */
if ((!cfg) || (!cfg->spi_bus) || (index >= BOARD_TLE8888_COUNT))
return -1;
/* check for valid chip select.
* TODO: remove this check? CS can be driven by SPI */
if (cfg->spi_config.ssport == NULL)
return -1;
Tle8888* chip = &chips[index];
/* already initted? */
if (chip->cfg)
return -1;
chip->cfg = cfg;
chip->o_state = 0;
chip->o_direct_mask = 0;
chip->o_data_cached = 0;
chip->drv_state = TLE8888_WAIT_INIT;
/* register */
int ret = gpiochip_register(base, DRIVER_NAME, *chip, TLE8888_OUTPUTS);
if (ret < 0)
return ret;
/* set default pin names, board init code can rewrite */
gpiochips_setPinNames(base, tle8888_pin_names);
return ret;
}
/*==========================================================================*/
/* Driver exported debug functions. */
/*==========================================================================*/
void Tle8888::read_reg(uint16_t reg, uint16_t *val)
{
spi_rw(CMD_R(reg), val);
}
void tle8888_req_init() {
auto& tle = chips[0];
tle.need_init = true;
tle.init_req_cnt++;
}
void tle8888_dump_regs() {
auto& chip = chips[0];
// since responses are always in the NEXT transmission we will have this one first
chip.read_reg(0, NULL);
efiPrintf("register: data");
for (int request = 0; request <= 0x7e + 1; request++) {
uint16_t tmp;
chip.read_reg(request < (0x7e + 1) ? request : 0x7e, &tmp);
uint8_t reg = getRegisterFromResponse(tmp);
uint8_t data = getDataFromResponse(tmp);
efiPrintf("%02x: %02x", reg, data);
}
}
#else /* BOARD_TLE8888_COUNT > 0 */
int tle8888_add(brain_pin_e base, unsigned int index, const tle8888_config *cfg)
{
(void)base; (void)index; (void)cfg;
return -1;
}
#endif /* (BOARD_TLE8888_COUNT > 0) */