/* * mc33810.c * * Automotive Engine Control IC * MCZ33810 * * @date Dec 29, 2018 * @author Andrey Belomutskiy, (c) 2012-2020 * * @date Jan 03, 2020 * @author Andrey Gusakov , (c) 2020 */ #include "pch.h" #include "gpio/gpio_ext.h" #include "gpio/mc33810.h" #include "os_util.h" #if (BOARD_MC33810_COUNT > 0) /* * TODO list: * */ /*==========================================================================*/ /* Driver local definitions. */ /*==========================================================================*/ #define DRIVER_NAME "mc33810" static bool drv_task_ready = false; typedef enum { MC33810_DISABLED = 0, MC33810_WAIT_INIT, MC33810_READY, MC33810_FAILED } mc33810_drv_state; #define MC_CMD_READ_REG(reg) (0x0a00 | (((reg) & 0x0f) << 4)) #define MC_CMD_SPI_CHECK (0x0f00) #define MC_CMD_MODE_SELECT(mode) (0x1000 | ((mode) & 0x0fff)) #define MC_CMD_LSD_FAULT(en) (0x2000 | ((en) & 0x0fff)) #define MC_CMD_DRIVER_EN(en) (0x3000 | ((en) & 0x00ff)) #define MC_CMD_SPARK(spark) (0x4000 | ((spark) & 0x0fff)) #define MC_CMD_END_SPARK_FILTER(filt) (0x5000 | ((filt) & 0x0003)) #define MC_CMD_DAC(dac) (0x6000 | ((dac) & 0x0fff)) #define MC_CMD_GPGD_SHORT_THRES(sh) (0x7000 | ((sh) & 0x0fff)) #define MC_CMD_GPGD_SHORT_DUR(dur) (0x8000 | ((dur) & 0x0fff)) #define MC_CMD_GPGD_FAULT_OP(op) (0x9000 | ((op) & 0x0f0f)) #define MC_CMD_PWM(pwm) (0xa000 | ((pwm) & 0x0fff)) #define MC_CMD_CLK_CALIB (0xe000) /* get command code from TX value */ #define TX_GET_CMD(v) (((v) >> 12) & 0x000f) /* enum? */ #define REG_ALL_STAT (0x0) #define REG_OUT10_FAULT (0x1) #define REG_OUT32_FAULT (0x2) #define REG_GPGD_FAULT (0x3) #define REG_IGN_FAULT (0x4) #define REG_MODE_CMD (0x5) #define REG_LSD_FAULT_CMD (0x6) #define REG_DRIVER_EN (0x7) #define REG_SPARK_CMD (0x8) #define REG_END_SPARK_FILTER (0x9) #define REG_DAC_CMD (0xa) #define REG_GPGD_SHORT_THRES (0xb) #define REG_GPGD_SHORT_TIMER (0xc) #define REG_GPGD_FAULT_OP (0xd) #define REG_PWM (0xe) #define REG_REV (0xf) /* 0000.1101.0000.1010b */ #define SPI_CHECK_ACK (0x0d0a) /*==========================================================================*/ /* Driver exported variables. */ /*==========================================================================*/ /*==========================================================================*/ /* Driver local variables and types. */ /*==========================================================================*/ /* OS */ SEMAPHORE_DECL(mc33810_wake, 10 /* or BOARD_MC33810_COUNT ? */); static THD_WORKING_AREA(mc33810_thread_1_wa, 256); /* Driver */ struct Mc33810 : public GpioChip { int init() override; int writePad(size_t pin, int value) override; brain_pin_diag_e getDiag(size_t pin) override; // internal functions int spi_rw(uint16_t tx, uint16_t* rx); int update_output_and_diag(); int chip_init(); void wake_driver(); const mc33810_config *cfg; /* cached output state - state last send to chip */ uint8_t o_state_cached; /* state to be sended to chip */ uint8_t o_state; /* direct driven output mask */ uint8_t o_direct_mask; /* ALL STATUS RESPONSE value and flags */ bool all_status_requested; bool all_status_updated; uint16_t all_status_value; /* OUTx fault registers */ uint16_t out_fault[2]; /* GPGD mode fault register */ uint16_t gpgd_fault; /* IGN mode fault register */ uint16_t ign_fault; mc33810_drv_state drv_state; }; static Mc33810 chips[BOARD_MC33810_COUNT]; static const char* mc33810_pin_names[MC33810_OUTPUTS] = { "mc33810.OUT1", "mc33810.OUT2", "mc33810.OUT3", "mc33810.OUT4", "mc33810.GD0", "mc33810.GD1", "mc33810.GD2", "mc33810.GD3", }; /*==========================================================================*/ /* Driver local functions. */ /*==========================================================================*/ /** * @brief MC33810 send and receive routine. * @details Sends and receives 16 bits. CS asserted before and released * after transaction. */ int Mc33810::spi_rw(uint16_t tx, uint16_t *rx) { uint16_t rxb; SPIDriver *spi = cfg->spi_bus; /* Acquire ownership of the bus. */ spiAcquireBus(spi); /* Setup transfer parameters. */ spiStart(spi, &cfg->spi_config); /* Slave Select assertion. */ spiSelect(spi); /* Atomic transfer operations. */ /* TODO: check why spiExchange transfers invalid data on STM32F7xx, DMA issue? */ //spiExchange(spi, 2, &tx, &rxb); rxb = spiPolledExchange(spi, tx); /* Slave Select de-assertion. */ spiUnselect(spi); /* Ownership release. */ spiReleaseBus(spi); if (rx) *rx = rxb; /* if reply on previous command is ALL STATUS RESPONSE */ if (all_status_requested) { all_status_value = rxb; all_status_updated = true; } /* if next reply will be ALL STATUS RESPONSE */ all_status_requested = (((TX_GET_CMD(tx) >= 0x1) && (TX_GET_CMD(tx) <= 0xa)) || (tx == MC_CMD_READ_REG(REG_ALL_STAT))); /* no errors for now */ return 0; } /** * @brief MC33810 send output state data. * @details Sends ORed data to register, also receive diagnostic. */ int Mc33810::update_output_and_diag() { int ret = 0; /* TODO: lock? */ /* we need to get updates status */ all_status_updated = false; /* if any pin is driven over SPI */ if (o_direct_mask != 0xff) { uint16_t out_data; out_data = o_state & (~o_direct_mask); ret = spi_rw(MC_CMD_DRIVER_EN(out_data), NULL); if (ret) return ret; o_state_cached = o_state; } /* this comlicated logic to save few spi transfers in case we will receive status as reply on other command */ if (!all_status_requested) { ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), NULL); if (ret) return ret; } /* get reply */ if (!all_status_updated) { ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), NULL); if (ret) return ret; } /* now we have updated ALL STATUS register in chip data */ /* check OUT (injectors) first */ if (all_status_value & 0x000f) { /* request diagnostic of OUT0 and OUT1 */ ret = spi_rw(MC_CMD_READ_REG(REG_OUT10_FAULT), NULL); if (ret) return ret; /* get diagnostic for OUT0 and OUT1 and request diagnostic for OUT2 and OUT3 */ ret = spi_rw(MC_CMD_READ_REG(REG_OUT32_FAULT), &out_fault[0]); if (ret) return ret; /* get diagnostic for OUT2 and OUT2 and requset ALL STATUS */ ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), &out_fault[1]); if (ret) return ret; } /* check GPGD - mode not supported yet */ if (all_status_value & 0x00f0) { /* request diagnostic of GPGD */ ret = spi_rw(MC_CMD_READ_REG(REG_GPGD_FAULT), NULL); if (ret) return ret; /* get diagnostic for GPGD and requset ALL STATUS */ ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), &gpgd_fault); if (ret) return ret; } /* check IGN */ if (all_status_value & 0x0f00) { /* request diagnostic of IGN */ ret = spi_rw(MC_CMD_READ_REG(REG_IGN_FAULT), NULL); if (ret) return ret; /* get diagnostic for IGN and requset ALL STATUS */ ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), &ign_fault); if (ret) return ret; } /* TODO: unlock? */ return ret; } /** * @brief MC33810 chip init. * @details Checks communication. Check chip presense. */ int Mc33810::chip_init() { int n; int ret; uint16_t rx; /* mark pins used */ //ret = gpio_pin_markUsed(cfg->spi_config.ssport, cfg->spi_config.sspad, DRIVER_NAME " CS"); ret = 0; if (cfg->en.port) { ret |= gpio_pin_markUsed(cfg->en.port, cfg->en.pad, DRIVER_NAME " EN"); } for (n = 0; n < MC33810_DIRECT_OUTPUTS; n++) { if (cfg->direct_io[n].port) { ret |= gpio_pin_markUsed(cfg->direct_io[n].port, cfg->direct_io[n].pad, DRIVER_NAME " DIRECT IO"); } } if (ret) { ret = -1; goto err_gpios; } /* check SPI communication */ /* 0. set echo mode, chip number - don't care */ ret = spi_rw(MC_CMD_SPI_CHECK, NULL); /* 1. check loopback */ ret |= spi_rw(MC_CMD_READ_REG(REG_REV), &rx); if (ret) { ret = -1; goto err_gpios; } if (rx != SPI_CHECK_ACK) { //print(DRIVER_NAME " spi loopback test failed\n"); ret = -2; goto err_gpios; } /* 2. read revision */ ret = spi_rw(MC_CMD_READ_REG(REG_ALL_STAT), &rx); if (ret) { ret = -1; goto err_gpios; } if (rx & BIT(14)) { //print(DRIVER_NAME " spi COR status\n"); ret = -3; goto err_gpios; } /* TODO: * - setup * - enable output drivers * - read diagnostic */ { uint16_t spark_settings = //(3 << 9) | /* max dwell is 16 mS */ (2 << 9) | /* max dwell is 8 mS */ BIT(8) | /* enable max dwell control */ (3 << 2) | /* Open Secondary OSFLT = 100 uS, default */ (1 << 0) | /* End Spark THreshold: VPWR +5.5V, defaul */ 0; ret = spi_rw(MC_CMD_SPARK(spark_settings), NULL); if (ret) { goto err_gpios; } /* set IGN/GP outputs to GP mode - to be fixed! * disable retry after recovering from under/overvoltage */ ret = spi_rw(MC_CMD_MODE_SELECT((0xf << 8) | (1 << 6)) , NULL); if (ret) { goto err_gpios; } } /* n. set EN pin low - active */ if (cfg->en.port) { palClearPort(cfg->en.port, PAL_PORT_BIT(cfg->en.pad)); } return 0; err_gpios: /* unmark pins */ //gpio_pin_markUnused(cfg->spi_config.ssport, cfg->spi_config.sspad); if (cfg->en.port) { /* disable and mark unused */ palSetPort(cfg->en.port, PAL_PORT_BIT(cfg->en.pad)); gpio_pin_markUnused(cfg->en.port, cfg->en.pad); } for (n = 0; n < MC33810_DIRECT_OUTPUTS; n++) { if (cfg->direct_io[n].port) { gpio_pin_markUnused(cfg->direct_io[n].port, cfg->direct_io[n].pad); } } return ret; } /** * @brief MC33810 chip driver wakeup. * @details Wake up driver. Will cause output register and * diagnostic update. */ void Mc33810::wake_driver() { /* Entering a reentrant critical zone.*/ chibios_rt::CriticalSectionLocker csl; chSemSignalI(&mc33810_wake); if (!port_is_isr_context()) { /** * chSemSignalI above requires rescheduling * interrupt handlers have implicit rescheduling */ chSchRescheduleS(); } } /*==========================================================================*/ /* Driver thread. */ /*==========================================================================*/ static THD_FUNCTION(mc33810_driver_thread, p) { int i; msg_t msg; (void)p; chRegSetThreadName(DRIVER_NAME); while(1) { msg = chSemWaitTimeout(&mc33810_wake, TIME_MS2I(MC33810_POLL_INTERVAL_MS)); /* should we care about msg == MSG_TIMEOUT? */ (void)msg; for (i = 0; i < BOARD_MC33810_COUNT; i++) { auto chip = &chips[i]; if ((chip->cfg == NULL) || (chip->drv_state == MC33810_DISABLED) || (chip->drv_state == MC33810_FAILED)) continue; /* TODO: implemet indirect driven gpios */ int ret = chip->update_output_and_diag(); if (ret) { /* set state to MC33810_FAILED? */ } } } } /*==========================================================================*/ /* Driver interrupt handlers. */ /*==========================================================================*/ /* TODO: add IRQ support */ /*==========================================================================*/ /* Driver exported functions. */ /*==========================================================================*/ int Mc33810::writePad(size_t pin, int value) { if (pin >= MC33810_OUTPUTS){ return -1; } { // mutate driver state under lock chibios_rt::CriticalSectionLocker csl; if (value) o_state |= BIT(pin); else o_state &= ~BIT(pin); } /* direct driven? */ if (o_direct_mask & BIT(pin)) { /* TODO: ensure that output driver enabled */ if (value) palSetPort(cfg->direct_io[pin].port, PAL_PORT_BIT(cfg->direct_io[pin].pad)); else palClearPort(cfg->direct_io[pin].port, PAL_PORT_BIT(cfg->direct_io[pin].pad)); } else { wake_driver(); } return 0; } brain_pin_diag_e Mc33810::getDiag(size_t pin) { int val; int diag = PIN_OK; if (pin >= MC33810_DIRECT_OUTPUTS) return PIN_INVALID; if (pin < 4) { /* OUT drivers */ val = out_fault[pin < 2 ? 0 : 1] >> (4 * (pin & 0x01)); /* ON open fault */ if (val & BIT(0)) diag |= PIN_OPEN; /* OFF open fault */ if (val & BIT(1)) diag |= PIN_OPEN; if (val & BIT(2)) diag |= PIN_SHORT_TO_BAT; if (val & BIT(3)) diag |= PIN_DRIVER_OVERTEMP; } else { /* INJ drivers, GPGD mode is not supported */ val = ign_fault >> (3 * (pin - 4)); /* open load */ if (val & BIT(0)) diag |= PIN_OPEN; /* max Dwell fault - too long coil charge time */ if (val & BIT(1)) diag |= PIN_OVERLOAD; /* MAXI fault - too high coil current */ if (val & BIT(2)) diag |= PIN_OVERLOAD; } /* convert to some common enum? */ return static_cast(diag); } int Mc33810::init() { int ret; ret = chip_init(); if (ret) return ret; drv_state = MC33810_READY; if (!drv_task_ready) { chThdCreateStatic(mc33810_thread_1_wa, sizeof(mc33810_thread_1_wa), PRIO_GPIOCHIP, mc33810_driver_thread, nullptr); drv_task_ready = true; } return 0; } /** * @brief MC33810 driver add. * @details Checks for valid config */ int mc33810_add(brain_pin_e base, unsigned int index, const mc33810_config *cfg) { int i; int ret; /* no config or no such chip */ if ((!cfg) || (!cfg->spi_bus) || (index >= BOARD_MC33810_COUNT)) return -1; /* check for valid cs. * TODO: remove this check? CS can be driven by SPI */ //if (cfg->spi_config.ssport == NULL) // return -1; Mc33810& chip = chips[index]; /* already initted? */ if (chip.cfg != NULL) return -1; chip.cfg = cfg; chip.o_state = 0; chip.o_state_cached = 0; chip.o_direct_mask = 0; chip.drv_state = MC33810_WAIT_INIT; for (i = 0; i < MC33810_DIRECT_OUTPUTS; i++) { if (cfg->direct_io[i].port != 0) chip.o_direct_mask |= BIT(i); } /* GPGD mode is not supported yet, ignition mode does not support spi on/off commands * so ignition signals should be directly driven */ if ((chip.o_direct_mask & 0xf0) != 0xf0) return -1; /* register, return gpio chip base */ ret = gpiochip_register(base, DRIVER_NAME, chip, MC33810_OUTPUTS); if (ret < 0) return ret; /* set default pin names, board init code can rewrite */ gpiochips_setPinNames(static_cast(ret), mc33810_pin_names); chip.drv_state = MC33810_WAIT_INIT; return ret; } #else /* BOARD_MC33810_COUNT > 0 */ int mc33810_add(brain_pin_e base, unsigned int index, const mc33810_config *cfg) { (void)base; (void)index; (void)cfg; return -1; } #endif /* BOARD_MC33810_COUNT */