278 lines
6.3 KiB
C++
278 lines
6.3 KiB
C++
/*
|
|
* drv8860.c
|
|
*
|
|
* DRV8860 Smart 8/16-Channel Low-Side Switch
|
|
*
|
|
* All channels are controlled via the serial interface (SPI).
|
|
* <200 kHz SPI
|
|
*
|
|
* @date Apr 6, 2020
|
|
*
|
|
* @author andreika, (c) 2020
|
|
* @author Andrey Belomutskiy, (c) 2012-2020
|
|
*/
|
|
|
|
#include "pch.h"
|
|
|
|
#include "gpio/gpio_ext.h"
|
|
#include "gpio/drv8860.h"
|
|
|
|
#if (BOARD_DRV8860_COUNT > 0)
|
|
|
|
/*==========================================================================*/
|
|
/* Driver local definitions. */
|
|
/*==========================================================================*/
|
|
|
|
#define DRIVER_NAME "drv8860"
|
|
|
|
static bool drv_task_ready = false;
|
|
|
|
typedef enum {
|
|
DRV8860_DISABLED = 0,
|
|
DRV8860_WAIT_INIT,
|
|
DRV8860_READY,
|
|
DRV8860_FAILED
|
|
} drv8860_drv_state;
|
|
|
|
/*==========================================================================*/
|
|
/* Driver exported variables. */
|
|
/*==========================================================================*/
|
|
|
|
/*==========================================================================*/
|
|
/* Driver local variables and types. */
|
|
/*==========================================================================*/
|
|
|
|
/* OS */
|
|
SEMAPHORE_DECL(drv8860_wake, 10 /* or BOARD_DRV8860_COUNT ? */);
|
|
static THD_WORKING_AREA(drv8860_thread_1_wa, 256);
|
|
|
|
/* Driver */
|
|
struct Drv8860 : public GpioChip {
|
|
int init() override;
|
|
|
|
int writePad(size_t pin, int value) override;
|
|
brain_pin_diag_e getDiag(size_t pin) override;
|
|
|
|
// Internal helpers
|
|
int chip_init();
|
|
|
|
void spi_send(uint16_t tx);
|
|
|
|
void update_outputs();
|
|
int wake_driver();
|
|
|
|
const drv8860_config *cfg;
|
|
/* cached output state - state last send to chip */
|
|
uint16_t o_state_cached;
|
|
/* state to be sended to chip */
|
|
uint16_t o_state;
|
|
|
|
drv8860_drv_state drv_state;
|
|
};
|
|
|
|
static Drv8860 chips[BOARD_DRV8860_COUNT];
|
|
|
|
static const char* drv8860_pin_names[DRV8860_OUTPUTS] = {
|
|
"drv8860.OUT1", "drv8860.OUT2", "drv8860.OUT3", "drv8860.OUT4",
|
|
"drv8860.OUT5", "drv8860.OUT6", "drv8860.OUT7", "drv8860.OUT8",
|
|
"drv8860.OUT9", "drv8860.OUT10", "drv8860.OUT11", "drv8860.OUT12",
|
|
"drv8860.OUT13", "drv8860.OUT14", "drv8860.OUT15", "drv8860.OUT16",
|
|
};
|
|
|
|
/*==========================================================================*/
|
|
/* Driver local functions. */
|
|
/*==========================================================================*/
|
|
|
|
/**
|
|
* @brief DRV8860 send routine.
|
|
* @details Sends 8/16 bits. CS asserted before and released after transaction.
|
|
*/
|
|
|
|
void Drv8860::spi_send(uint16_t tx) {
|
|
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. */
|
|
spiPolledExchange(spi, tx);
|
|
/* Slave Select de-assertion. */
|
|
spiUnselect(spi);
|
|
/* Ownership release. */
|
|
spiReleaseBus(spi);
|
|
}
|
|
|
|
/**
|
|
* @brief DRV8860 send output data.
|
|
*/
|
|
|
|
void Drv8860::update_outputs() {
|
|
/* TODO: lock? */
|
|
|
|
/* atomic */
|
|
/* set value only for non-direct driven pins */
|
|
spi_send(o_state & 0xffff);
|
|
|
|
/* atomic */
|
|
o_state_cached = o_state;
|
|
|
|
/* TODO: unlock? */
|
|
}
|
|
|
|
/**
|
|
* @brief DRV8860 chip init.
|
|
* @details Marks all used pins.
|
|
* @todo: Checks direct io signals integrity, read initial diagnostic state.
|
|
*/
|
|
|
|
int Drv8860::chip_init() {
|
|
/* upload pin states */
|
|
update_outputs();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief DRV8860 chip driver wakeup.
|
|
* @details Wake up driver. Will cause output register update.
|
|
*/
|
|
|
|
int Drv8860::wake_driver() {
|
|
/* Entering a reentrant critical zone.*/
|
|
chibios_rt::CriticalSectionLocker csl;
|
|
|
|
chSemSignalI(&drv8860_wake);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================================*/
|
|
/* Driver thread. */
|
|
/*==========================================================================*/
|
|
|
|
static THD_FUNCTION(drv8860_driver_thread, p) {
|
|
int i;
|
|
msg_t msg;
|
|
|
|
(void)p;
|
|
|
|
chRegSetThreadName(DRIVER_NAME);
|
|
|
|
while (1) {
|
|
msg = chSemWaitTimeout(&drv8860_wake, TIME_MS2I(DRV8860_POLL_INTERVAL_MS));
|
|
|
|
/* should we care about msg == MSG_TIMEOUT? */
|
|
(void)msg;
|
|
|
|
for (i = 0; i < BOARD_DRV8860_COUNT; i++) {
|
|
auto chip = &chips[i];
|
|
if ((chip->cfg == NULL) ||
|
|
(chip->drv_state == DRV8860_DISABLED) ||
|
|
(chip->drv_state == DRV8860_FAILED))
|
|
continue;
|
|
|
|
chip->update_outputs();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*==========================================================================*/
|
|
/* Driver interrupt handlers. */
|
|
/*==========================================================================*/
|
|
|
|
/* TODO: add IRQ support */
|
|
|
|
/*==========================================================================*/
|
|
/* Driver exported functions. */
|
|
/*==========================================================================*/
|
|
|
|
int Drv8860::writePad(size_t pin, int value) {
|
|
if (pin >= DRV8860_OUTPUTS)
|
|
return -1;
|
|
|
|
/* TODO: lock */
|
|
if (value)
|
|
o_state |= (1 << pin);
|
|
else
|
|
o_state &= ~(1 << pin);
|
|
/* TODO: unlock */
|
|
wake_driver();
|
|
|
|
return 0;
|
|
}
|
|
|
|
brain_pin_diag_e Drv8860::getDiag(size_t /*pin*/) {
|
|
// todo: implement diag
|
|
return PIN_OK;
|
|
}
|
|
|
|
int Drv8860::init() {
|
|
int ret;
|
|
|
|
ret = chip_init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
drv_state = DRV8860_READY;
|
|
|
|
if (!drv_task_ready) {
|
|
chThdCreateStatic(drv8860_thread_1_wa, sizeof(drv8860_thread_1_wa),
|
|
PRIO_GPIOCHIP, drv8860_driver_thread, NULL);
|
|
drv_task_ready = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief DRV8860 driver add.
|
|
* @details Checks for valid config
|
|
*/
|
|
|
|
int drv8860_add(brain_pin_e base, unsigned int index, const drv8860_config *cfg) {
|
|
int ret;
|
|
|
|
/* no config or no such chip */
|
|
if ((!cfg) || (!cfg->spi_bus) || (index >= BOARD_DRV8860_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;
|
|
|
|
auto& chip = chips[index];
|
|
|
|
/* already initted? */
|
|
if (!chip.cfg)
|
|
return -1;
|
|
|
|
chip.cfg = cfg;
|
|
chip.o_state = 0;
|
|
chip.o_state_cached = 0;
|
|
chip.drv_state = DRV8860_WAIT_INIT;
|
|
|
|
/* register, return gpio chip base */
|
|
ret = gpiochip_register(base, DRIVER_NAME, chip, DRV8860_OUTPUTS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* set default pin names, board init code can rewrite */
|
|
gpiochips_setPinNames(static_cast<brain_pin_e>(ret), drv8860_pin_names);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else /* BOARD_DRV8860_COUNT > 0 */
|
|
|
|
int drv8860_add(brain_pin_e base, unsigned int index, const drv8860_config *cfg) {
|
|
(void)base; (void)index; (void)cfg;
|
|
|
|
return -1;
|
|
}
|
|
|
|
#endif /* BOARD_DRV8860_COUNT */
|