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

559 lines
13 KiB
C++

/**
* can_gpio_msiobox.cpp
*/
#include "pch.h"
#include "can_gpio_msiobox.h"
#if EFI_CAN_GPIO
#include "gpio/gpio_ext.h"
#include "can_listener.h"
#include "can_msg_tx.h"
#include <rusefi/endian.h>
/*
* TODO list:
* - implement input reading
* - support PWM out mode
* - support VSS capture
*/
/*==========================================================================*/
/* Driver local definitions. */
/*==========================================================================*/
#define DRIVER_NAME "IOBox"
/*==========================================================================*/
/* Driver local definitions. */
/*==========================================================================*/
/* HW capabilities exported outside */
/* Actualy only 7 outputs, add one for aligment */
#define MSIOBOX_OUTPUTS 8
/* first 4 capable to capture wheel speed of other frequency signal:
* then goes 3 switch inputs.
* SW2, SW3 are shared with VSS3, VSS4. See tachin_mask */
#define MSIOBOX_INPUTS 8
/* total */
#define MSIOBOX_SIGNALS (MSIOBOX_OUTPUTS + MSIOBOX_INPUTS)
/* Actual HW capabilities */
/* On/Off or PWM mode */
#define MSIOBOX_OUT_COUNT 7
/* ADC input count */
#define MSIOBOX_ADC_IN_COUNT 7
/* Speed in sensors */
#define MSIOBOX_TACH_IN_COUNT 4
/* timeouts for different states */
#define MSIOBOX_PING_TIMEOUT 100
#define MSIOBOX_RESTART_TIMEOUT 1000
/*==========================================================================*/
/* Driver exported variables. */
/*==========================================================================*/
/*==========================================================================*/
/* MegaSquirt I/O box protocol definitions. */
/*==========================================================================*/
#define CAN_IOBOX_BASE1 0x200
#define CAN_IOBOX_BASE2 0x220
#define CAN_IOBOX_BASE3 0x240
/* Packets from MS3 to device */
#define CAN_IOBOX_PING 0x00
#define CAN_IOBOX_CONFIG 0x01
#define CAN_IOBOX_SET_PWM(n) (0x02 + ((n) & 0x03))
#define CAN_IOBOX_LAST_IN 0x05
/* Packets from device to MS3 */
#define CAN_IOBOX_WHOAMI 0x08
#define CAN_IOBOX_ADC14 0x09
#define CAN_IOBOX_ADC57 0x0A
#define CAN_IOBOX_TACH1 0x0B
/* ... */
#define CAN_IOBOX_TACH4 0x0E
struct pwm_settings {
beuint16_t on;
beuint16_t off;
} __attribute__ ((packed));
/* Base + 0x00 */
/* "Are you there?" packet with zero payload */
struct iobox_ping {
/* empty */
} __attribute__((packed));
/* Base + 0x01 */
struct iobox_cfg {
uint8_t pwm_mask; // 0 - On/Off, 1 - PWM
uint8_t pad0;
uint8_t tachin_mask;
uint8_t pad1;
uint8_t adc_broadcast_interval; // mS
uint8_t tach_broadcast_interval; // mS
uint8_t pad2[2];
} __attribute__((packed));
/* Base + 0x02, 0x03, 0x04 */
struct iobox_pwm {
pwm_settings ch[2];
} __attribute__ ((packed));
static_assert(sizeof(iobox_pwm) == 8);
/* Base + 0x05 */
struct iobox_pwm_last {
pwm_settings ch[1];
uint8_t out_state;
} __attribute__ ((packed));
static_assert(sizeof(iobox_pwm_last) == 5);
/* Base + 0x08 */
struct iobox_whoami {
uint8_t version;
uint8_t pad[3];
beuint16_t pwm_period; // PWM clock periods in 0.01 uS
beuint16_t tachin_period; // Tach-in clock periods in 0.01 uS
} __attribute__((packed));
static_assert(sizeof(iobox_whoami) == 8);
/* Base + 0x09 */
struct iobox_adc14 {
beuint16_t adc[4];
} __attribute__((packed));
static_assert(sizeof(iobox_adc14) == 8);
/* Base + 0x0A */
struct iobox_adc57 {
uint8_t inputs;
uint8_t pad;
beuint16_t adc[3];
} __attribute__((packed));
static_assert(sizeof(iobox_adc57) == 8);
/* Base + 0x0B..0x0E */
struct iobox_tach {
beuint32_t period;
beuint16_t n_teeth;
beuint16_t total_tooth;
} __attribute__((packed));
static_assert(sizeof(iobox_tach) == 8);
/*==========================================================================*/
/* Driver local variables and types. */
/*==========================================================================*/
typedef enum {
MSIOBOX_DISABLED = 0,
MSIOBOX_WAIT_INIT,
MSIOBOX_WAIT_WHOAMI,
MSIOBOX_READY,
MSIOBOX_FAILED
} msiobox_state;
class MsIoBox final : public GpioChip, public CanListener {
/* gpio stuff */
int writePad(size_t pin, int value) override;
int readPad(size_t pin) override;
int setPadPWM(size_t pin, float frequency, float duty) override;
brain_pin_diag_e getDiag(size_t pin) override;
public:
MsIoBox();
MsIoBox(uint32_t bus, uint32_t base, uint16_t period);
CanListener* request() override;
bool acceptFrame(const CANRxFrame& frame) const override;
int init() override;
int config(uint32_t bus, uint32_t base, uint16_t period);
#if 0
/* pin argument is pin number within gpio chip, not a global number */
int writePad(size_t pin, int value) override {
state[pin] = value;
return 0;
}
#endif
protected:
void decodeFrame(const CANRxFrame& frame, efitick_t nowNt) override;
private:
int ping();
int setup();
int update();
void checkState();
/* PWM output helpers */
void CalcOnOffPeriod(int ch, pwm_settings &pwm);
/* Output states */
uint8_t OutMode; // on/off (0) vs pwm (1) bitfield
uint8_t OutVal; // for on/off outputs
struct {
float frequency;
float duty;
} OutPwm[MSIOBOX_OUT_COUNT];
/* ADC inputs */
uint16_t AdcValue[MSIOBOX_ADC_IN_COUNT];
/* Input mode */
uint8_t InMode; //Tach-in config bitfield
/* Logical inputs */
uint8_t InVal;
/* Wheel speed */
struct {
uint32_t period;
uint16_t teeths;
uint16_t totalTooth;
} Tach[MSIOBOX_TACH_IN_COUNT];
/* Can settings */
uint32_t m_bus;
uint32_t m_base;
uint32_t m_period;
/* IOBox timebase */
/* PWM clock period in 0.01 uS units, default is 5000 */
uint32_t pwmBaseFreq = 1000 * 1000 * 100 / 5000; /* Hz */
/* Tach-in clock period in 0.01 uS units, default is 66 */
uint32_t tachinBaseFreq = 1000 * 1000 * 100 / 66; /* Hz */
/* Misc */
uint8_t version;
/* Flags */
bool needUpdate;
bool needUpdateConfig;
/* */
msiobox_state state;
Timer stateTimer;
};
MsIoBox::MsIoBox()
: CanListener(0), m_bus(0), m_base(0), m_period(20) {
}
MsIoBox::MsIoBox(uint32_t bus, uint32_t base, uint16_t period)
: CanListener(0), m_bus(bus), m_base(base), m_period(period) {
/* init state */
state = MSIOBOX_WAIT_INIT;
stateTimer.reset();
}
int MsIoBox::init()
{
/* TODO: register can listener here */
return 0;
}
int MsIoBox::config(uint32_t bus, uint32_t base, uint16_t period)
{
/* TODO: sanity checks? */
m_bus = bus;
m_base = base;
m_period = period;
/* Force init */
state = MSIOBOX_WAIT_INIT;
stateTimer.reset();
/* TODO: */
//registerCanListener(this);
return 0;
}
bool MsIoBox::acceptFrame(const CANRxFrame& frame) const {
/* 11 bit only */
if (CAN_ISX(frame)) {
return false;
}
uint32_t id = CAN_ID(frame);
/* packets with ID (base + 0) to (base + 5) are received by MSIOBox
* (base + 8) to (base + 14) are emited by MSIOBox */
if ((id >= m_base + 8) && (id <= m_base + 14)) {
return true;
}
return false;
}
/* Ping iobox */
int MsIoBox::ping() {
CanTxTyped<iobox_ping> frame(CanCategory::MEGASQUIRT, m_base + CAN_IOBOX_PING, false, 0);
return 0;
}
/* Send init settings */
int MsIoBox::setup() {
CanTxTyped<iobox_cfg> cfg(CanCategory::MEGASQUIRT, m_base + CAN_IOBOX_CONFIG, false, 0);
cfg->pwm_mask = OutMode;
cfg->tachin_mask = InMode;
cfg->adc_broadcast_interval = m_period;
cfg->tach_broadcast_interval = m_period;
return 0;
}
void MsIoBox::CalcOnOffPeriod(int channel, pwm_settings &pwm)
{
if ((OutMode & BIT(channel)) == 0) {
pwm.on = pwm.off = 0;
return;
}
int period = (pwmBaseFreq + (OutPwm[channel].frequency / 2)) / OutPwm[channel].frequency;
pwm.on = period * OutPwm[channel].duty;
pwm.off = period - pwm.on;
}
/* Send current gpio and pwm states */
int MsIoBox::update() {
/* TODO: protect against OutPwm/OutVal change while we are here */
/* PWM1 .. PWM6 */
for (size_t i = 0; i < 3; i++) {
/* sent only if PWMs is in use */
if ((OutMode & (BIT(i) | BIT(i + 1))) == 0)
continue;
CanTxTyped<iobox_pwm> pwm(CanCategory::MEGASQUIRT, m_base + CAN_IOBOX_SET_PWM(i), false, 0);
for (size_t j = 0; j < 2; j++) {
CalcOnOffPeriod(i + j, pwm->ch[j]);
}
}
/* PWM7 periods and on/off outputs bitfield - sent always */
{
CanTxTyped<iobox_pwm_last> pwm(CanCategory::MEGASQUIRT, m_base + CAN_IOBOX_SET_PWM(3), false, 0);
CalcOnOffPeriod(MSIOBOX_OUT_COUNT - 1, pwm->ch[0]);
pwm->out_state = OutVal;
}
return 0;
}
void MsIoBox::decodeFrame(const CANRxFrame& frame, efitick_t) {
uint32_t id = CAN_ID(frame);
uint32_t offset = id - m_base;
bool handled = true;
if (state == MSIOBOX_READY) {
if (offset == CAN_IOBOX_ADC14) {
auto data = reinterpret_cast<const iobox_adc14*>(&frame.data8[0]);
for (size_t i = 0; i < 4; i++) {
AdcValue[i] = data->adc[i];
}
} else if (offset == CAN_IOBOX_ADC57) {
auto data = reinterpret_cast<const iobox_adc57*>(&frame.data8[0]);
InVal = data->inputs;
for (size_t i = 0; i < 3; i++) {
AdcValue[i + 4] = data->adc[i];
}
} else if ((offset >= CAN_IOBOX_TACH1) && (offset <= CAN_IOBOX_TACH4)) {
size_t i = offset - CAN_IOBOX_TACH1;
auto data = reinterpret_cast<const iobox_tach*>(&frame.data8[0]);
/* TODO: should be atomic, add lock here? */
/* TODO: accumulate totalTooth? */
Tach[i].period = data->period;
Tach[i].teeths = data->n_teeth;
Tach[i].totalTooth = data->total_tooth;
} else {
handled = false;
}
} else if (state == MSIOBOX_WAIT_WHOAMI) {
if (offset == CAN_IOBOX_WHOAMI) {
auto data = reinterpret_cast<const iobox_whoami*>(&frame.data8[0]);
version = data->version;
/* convert from 0.01 uS units to Hz */
pwmBaseFreq = 1000 * 1000 * 100 / data->pwm_period;
tachinBaseFreq = 1000 * 1000 * 100 / data->tachin_period;
/* apply settings and set sync output states */
setup();
update();
/* now we are ready */
state = MSIOBOX_READY;
stateTimer.reset();
} else {
handled = false;
}
/* ignore everything else */
} else {
handled = false;
}
if (handled) {
/* TODO: check that we receive EVERY expected packed? */
stateTimer.reset();
}
}
/* gpio chip stuff */
int MsIoBox::writePad(unsigned int pin, int value) {
if (pin >= MSIOBOX_OUTPUTS)
return -1;
uint8_t OutModeNew = OutMode & (~BIT(pin));
uint8_t OutValNew = OutVal;
if (value) {
OutValNew |= BIT(pin);
} else {
OutValNew &= ~BIT(pin);
}
if (OutValNew != OutVal) {
OutVal = OutValNew;
needUpdate = true;
}
if (OutModeNew != OutMode) {
OutMode = OutModeNew;
needUpdateConfig = true;
}
return 0;
}
int MsIoBox::readPad(size_t pin) {
if ((pin < MSIOBOX_OUTPUTS) || (pin >= MSIOBOX_SIGNALS))
return -1;
pin -= MSIOBOX_OUTPUTS;
if (InMode & BIT(pin)) {
/* pin is configured for VSS */
return -1;
}
return !!(InVal & BIT(pin));
}
int MsIoBox::setPadPWM(size_t pin, float frequency, float duty)
{
if (pin >= MSIOBOX_OUT_COUNT)
return -1;
/* TODO: validate frequency? Validate duty? */
/* Just save values.
* Do calculation in update() as at this point we may not receive
* iobox_whoami packet with pwmPeriodNs */
OutPwm[pin].frequency = frequency;
OutPwm[pin].duty = duty;
if ((OutMode & BIT(pin)) == 0) {
OutMode |= BIT(pin);
needUpdateConfig = true;
}
/* TODO: chech if updated? */
needUpdate = true;
return 0;
}
brain_pin_diag_e MsIoBox::getDiag(size_t pin)
{
if (pin >= MSIOBOX_SIGNALS)
return PIN_UNKNOWN;
if ((state == MSIOBOX_READY) && (!stateTimer.hasElapsedMs(m_period * 3)))
return PIN_OK;
/* find better state */
return PIN_DRIVER_OFF;
}
void MsIoBox::checkState(void)
{
switch (state) {
case MSIOBOX_DISABLED:
/* nop */
break;
case MSIOBOX_WAIT_INIT:
ping();
state = MSIOBOX_WAIT_WHOAMI;
stateTimer.reset();
break;
case MSIOBOX_WAIT_WHOAMI:
if (stateTimer.hasElapsedMs(MSIOBOX_PING_TIMEOUT)) {
state = MSIOBOX_FAILED;
stateTimer.reset();
}
break;
case MSIOBOX_READY:
if (stateTimer.hasElapsedMs(m_period * 3)) {
state = MSIOBOX_FAILED;
stateTimer.reset();
} else {
if (needUpdateConfig) {
setup();
needUpdateConfig = false;
/* Force update */
needUpdate = true;
}
if (needUpdate) {
update();
}
}
break;
case MSIOBOX_FAILED:
if (stateTimer.hasElapsedMs(MSIOBOX_RESTART_TIMEOUT)) {
state = MSIOBOX_WAIT_INIT;
stateTimer.reset();
}
break;
}
}
CanListener* MsIoBox::request(void) {
checkState();
/* return next */
return CanListener::request();
}
static MsIoBox instance[BOARD_CAN_GPIO_COUNT];
int initCanGpioMsiobox() {
if (engineConfiguration->msIoBox0.id == MsIoBoxId::OFF) {
return 0;
}
// MSIOBOX_0_OUT_1
for (size_t i = 0; i < BOARD_CAN_GPIO_COUNT; i++) {
uint32_t can_id = CAN_IOBOX_BASE1 + 0x20 * (static_cast<uint32_t>(engineConfiguration->msIoBox0.id) - static_cast<uint32_t>(MsIoBoxId::ID200));
/* TODO: pick can bus and refresh rate from settings */
if (instance[i].config(0, can_id, 20) == 0) {
registerCanListener(instance[i]);
/* register */
int ret = gpiochip_register(Gpio::MSIOBOX_0_OUT_1, DRIVER_NAME, instance[i], MSIOBOX_SIGNALS);
if (ret < 0)
return ret;
}
}
return 0;
}
#endif // EFI_CAN_GPIO