grblHAL-myversion/grblHAL_Teensy4/src/i2c.c

541 lines
15 KiB
C

/*
i2c.c - driver code for IMXRT1062 processor (on Teensy 4.0 board)
Part of grblHAL
Some parts of this code is Copyright (c) 2020-2021 Terje Io
Some parts are derived/pulled from WireIMXRT.cpp in the Teensyduino Core Library (no copyright header)
*/
#include "driver.h"
#ifdef I2C_PORT
#include <string.h>
#include "i2c.h"
#if EEPROM_ENABLE
#include "eeprom/eeprom.h"
#endif
#define i2cIsBusy (!(i2c.state == I2CState_Idle || i2c.state == I2CState_Error) || (port->MSR & (LPI2C_MSR_BBF|LPI2C_MSR_MBF)))
// Timeout if a device stretches SCL this long, in microseconds
#define CLOCK_STRETCH_TIMEOUT 15000
#define PINCONFIG (IOMUXC_PAD_ODE | IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(4) | IOMUXC_PAD_SPEED(1) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3))
typedef struct {
volatile uint32_t *clock_gate_register;
uint32_t clock_gate_mask;
pin_info_t sda_pin;
pin_info_t scl_pin;
IMXRT_LPI2C_t *port;
enum IRQ_NUMBER_t irq;
} i2c_hardware_t;
static const i2c_hardware_t i2c1_hardware = {
.clock_gate_register = &CCM_CCGR2,
.clock_gate_mask = CCM_CCGR2_LPI2C1(CCM_CCGR_ON),
.port = &IMXRT_LPI2C1,
.irq = IRQ_LPI2C1,
.sda_pin = {
.pin = 18,
.mux_val = 3 | 0x10,
.select_reg = &IOMUXC_LPI2C1_SDA_SELECT_INPUT,
.select_val = 1
},
.scl_pin = {
.pin = 19,
.mux_val = 3 | 0x10,
.select_reg = &IOMUXC_LPI2C1_SCL_SELECT_INPUT,
.select_val = 1
}
};
// NOTE: port 3 has alternative mapping to pin 36 and 37
static const i2c_hardware_t i2c3_hardware = {
.clock_gate_register = &CCM_CCGR2,
.clock_gate_mask = CCM_CCGR2_LPI2C3(CCM_CCGR_ON),
.port = &IMXRT_LPI2C3,
.irq = IRQ_LPI2C3,
.sda_pin = {
.pin = 16,
.mux_val = 1 | 0x10,
.select_reg = &IOMUXC_LPI2C3_SDA_SELECT_INPUT,
.select_val = 1
},
.scl_pin = {
.pin = 17,
.mux_val = 1 | 0x10,
.select_reg = &IOMUXC_LPI2C3_SCL_SELECT_INPUT,
.select_val = 1
}
};
static const i2c_hardware_t i2c4_hardware = {
.clock_gate_register = &CCM_CCGR6,
.clock_gate_mask = CCM_CCGR6_LPI2C4_SERIAL(CCM_CCGR_ON),
.port = &IMXRT_LPI2C4,
.irq = IRQ_LPI2C4,
.sda_pin = {
.pin = 25,
.mux_val = 0 | 0x10,
.select_reg = &IOMUXC_LPI2C4_SDA_SELECT_INPUT,
.select_val = 1
},
.scl_pin = {
.pin = 24,
.mux_val = 0 | 0x10,
.select_reg = &IOMUXC_LPI2C4_SCL_SELECT_INPUT,
.select_val = 1
}
};
static bool force_clock (const i2c_hardware_t *hardware)
{
bool ret = false;
uint32_t sda_pin = hardware->sda_pin.pin;
uint32_t scl_pin = hardware->scl_pin.pin;
uint32_t sda_mask = digitalPinToBitMask(sda_pin);
uint32_t scl_mask = digitalPinToBitMask(scl_pin);
// take control of pins with GPIO
*portConfigRegister(sda_pin) = 5 | 0x10;
*portSetRegister(sda_pin) = sda_mask;
*portModeRegister(sda_pin) |= sda_mask;
*portConfigRegister(scl_pin) = 5 | 0x10;
*portSetRegister(scl_pin) = scl_mask;
*portModeRegister(scl_pin) |= scl_mask;
delayMicroseconds(10);
for (int i=0; i < 9; i++) {
if ((*portInputRegister(sda_pin) & sda_mask) && (*portInputRegister(scl_pin) & scl_mask)) {
// success, both pins are high
ret = true;
break;
}
*portClearRegister(scl_pin) = scl_mask;
delayMicroseconds(5);
*portSetRegister(scl_pin) = scl_mask;
delayMicroseconds(5);
}
// return control of pins to I2C
*(portConfigRegister(sda_pin)) = hardware->sda_pin.mux_val;
*(portConfigRegister(scl_pin)) = hardware->scl_pin.mux_val;
return ret;
}
static void set_clock (IMXRT_LPI2C_t *port, uint32_t frequency)
{
port->MCR = 0;
if (frequency < 400000) {
// 100 kHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(55) | LPI2C_MCCR0_CLKLO(59) | LPI2C_MCCR0_DATAVD(25) | LPI2C_MCCR0_SETHOLD(40);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(1);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(5) | LPI2C_MCFGR2_FILTSCL(5) | LPI2C_MCFGR2_BUSIDLE(3000); // idle timeout 250 us
port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 12 / 256 + 1);
} else if (frequency < 1000000) {
// 400 kHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(26) | LPI2C_MCCR0_CLKLO(28) | LPI2C_MCCR0_DATAVD(12) | LPI2C_MCCR0_SETHOLD(18);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(2) | LPI2C_MCFGR2_FILTSCL(2) | LPI2C_MCFGR2_BUSIDLE(3600); // idle timeout 150 us
port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);
} else {
// 1 MHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(9) | LPI2C_MCCR0_CLKLO(10) | LPI2C_MCCR0_DATAVD(4) | LPI2C_MCCR0_SETHOLD(7);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) | LPI2C_MCFGR2_BUSIDLE(2400); // idle timeout 100 us
port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);
}
port->MCCR1 = port->MCCR0;
port->MCFGR0 = 0;
port->MFCR = LPI2C_MFCR_RXWATER(0) | LPI2C_MFCR_TXWATER(1);
port->MCR = LPI2C_MCR_MEN;
}
typedef enum {
I2CState_Idle = 0,
I2CState_Restart,
I2CState_SendAddr,
I2CState_SendNext,
I2CState_SendLast,
I2CState_AwaitCompletion,
I2CState_ReceiveNext,
I2CState_ReceiveNextToLast,
I2CState_ReceiveLast,
I2CState_Poll,
I2CState_Error
} i2c_state_t;
typedef struct {
volatile i2c_state_t state;
uint8_t addr;
volatile uint16_t count;
volatile uint16_t rcount;
volatile uint8_t acount;
uint8_t *data;
uint8_t regaddr[2];
#if KEYPAD_ENABLE
keycode_callback_ptr keycode_callback;
#endif
uint8_t buffer[8];
} i2c_trans_t;
static i2c_trans_t i2c;
static uint8_t tx_fifo_size;
static const i2c_hardware_t *hardware;
static IMXRT_LPI2C_t *port = NULL;
static void I2C_interrupt_handler (void);
void i2c_init (void)
{
static bool init_ok = false;
if(!init_ok) {
init_ok = true;
#ifndef I2C_PORT
#error "I2C port is undefined!"
#endif
#if I2C_PORT == 0
hardware = &i2c1_hardware;
#elif I2C_PORT == 3
hardware = &i2c3_hardware;
#elif I2C_PORT == 4
hardware = &i2c4_hardware;
#else
#error "No such I2C port!"
#endif
port = hardware->port;
tx_fifo_size = 1 << (port->PARAM & 0b1111);
CCM_CSCDR2 = (CCM_CSCDR2 & ~CCM_CSCDR2_LPI2C_CLK_PODF(63)) | CCM_CSCDR2_LPI2C_CLK_SEL;
*hardware->clock_gate_register |= hardware->clock_gate_mask;
port->MCR = LPI2C_MCR_RST;
set_clock(port, 100000);
// Setup SDA register
*(portControlRegister(hardware->sda_pin.pin)) = PINCONFIG;
*(portConfigRegister(hardware->sda_pin.pin)) = hardware->sda_pin.mux_val;
*(hardware->sda_pin.select_reg) = hardware->sda_pin.select_val;
// setup SCL register
*(portControlRegister(hardware->scl_pin.pin)) = PINCONFIG;
*(portConfigRegister(hardware->scl_pin.pin)) = hardware->scl_pin.mux_val;
*(hardware->scl_pin.select_reg) = hardware->scl_pin.select_val;
attachInterruptVector(hardware->irq, I2C_interrupt_handler);
NVIC_SET_PRIORITY(hardware->irq, 1);
NVIC_ENABLE_IRQ(hardware->irq);
}
}
// wait until ready for transfer, try peripheral reset if bus hangs
inline static bool wait_ready (void)
{
while(i2cIsBusy) {
if(port->MSR & LPI2C_MSR_PLTF) {
if(force_clock(hardware)) {
port->MCR = LPI2C_MCR_RST;
set_clock(port, 100000);
} else
return false;
}
}
return true;
}
// get bytes (max 8 if local buffer, else max 255), waits for result
uint8_t *I2C_Receive (uint32_t i2cAddr, uint8_t *buf, uint16_t bytes, bool block)
{
i2c.data = buf ? buf : i2c.buffer;
i2c.count = bytes;
i2c.rcount = 0;
i2c.state = bytes == 1 ? I2CState_ReceiveLast : (bytes == 2 ? I2CState_ReceiveNextToLast : I2CState_ReceiveNext);
port->MSR = 0;
port->MTDR = LPI2C_MTDR_CMD_START | (i2cAddr << 1) | 1;
port->MTDR = LPI2C_MTDR_CMD_RECEIVE | ((uint32_t)i2c.count - 1);
port->MIER = LPI2C_MIER_NDIE|LPI2C_MIER_RDIE;
if(block)
while(i2cIsBusy);
return i2c.buffer;
}
bool I2C_Send (uint32_t i2cAddr, uint8_t *buf, uint16_t bytes, bool block)
{
i2c.count = bytes;
i2c.data = buf ? buf : i2c.buffer;
port->MSR = 0;
port->MTDR = LPI2C_MTDR_CMD_START | (i2cAddr << 1);
while(i2c.count && (port->MFSR & 0b111) < tx_fifo_size) {
port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | (uint32_t)(*i2c.data++);
i2c.count--;
}
port->MIER = LPI2C_MIER_NDIE|LPI2C_MIER_TDIE;
i2c.state = i2c.count == 0 ? I2CState_AwaitCompletion : (i2c.count == 1 ? I2CState_SendLast : I2CState_SendNext);
if(block) {
while(i2cIsBusy) {
if(bytes == 0) {
hal.delay_ms(2, 0);
if(port->MSR & LPI2C_MSR_PLTF) {
wait_ready();
i2c.state = I2CState_Error;
}
}
}
}
return !block || i2c.state != I2CState_Error;
}
uint8_t *I2C_ReadRegister (uint32_t i2cAddr, uint8_t *buf, uint8_t abytes, uint16_t bytes, bool block)
{
while(i2cIsBusy);
i2c.addr = i2cAddr;
i2c.count = bytes;
i2c.rcount = 0;
i2c.acount = abytes;
i2c.data = buf ? buf : i2c.buffer;
i2c.state = I2CState_SendAddr;
port->MSR = 0;
port->MTDR = LPI2C_MTDR_CMD_START | (i2cAddr << 1);
port->MIER = LPI2C_MIER_NDIE|LPI2C_MIER_TDIE;
if(block)
while(i2cIsBusy);
return i2c.buffer;
}
#if EEPROM_ENABLE
nvs_transfer_result_t i2c_nvs_transfer (nvs_transfer_t *transfer, bool read)
{
static uint8_t txbuf[NVS_SIZE + 2];
while(i2cIsBusy);
if(read) {
if(transfer->word_addr_bytes == 1)
i2c.regaddr[0] = transfer->word_addr;
else {
i2c.regaddr[0] = transfer->word_addr & 0xFF;
i2c.regaddr[1] = transfer->word_addr >> 8;
}
I2C_ReadRegister(transfer->address, transfer->data, transfer->word_addr_bytes, transfer->count, true);
} else {
memcpy(&txbuf[transfer->word_addr_bytes], transfer->data, transfer->count);
if(transfer->word_addr_bytes == 1)
txbuf[0] = transfer->word_addr;
else {
txbuf[0] = transfer->word_addr >> 8;
txbuf[1] = transfer->word_addr & 0xFF;
}
I2C_Send(transfer->address, txbuf, transfer->count + transfer->word_addr_bytes, true);
#if !EEPROM_IS_FRAM
hal.delay_ms(7, NULL);
#endif
}
return NVS_TransferResult_OK;
}
#endif
#if KEYPAD_ENABLE
void I2C_GetKeycode (uint32_t i2cAddr, keycode_callback_ptr callback)
{
if(wait_ready()) {
i2c.keycode_callback = callback;
I2C_Receive(i2cAddr, NULL, 1, false);
}
}
#endif
#if TRINAMIC_ENABLE && TRINAMIC_I2C
static TMC2130_status_t I2C_TMC_ReadRegister (TMC2130_t *driver, TMC2130_datagram_t *reg)
{
uint8_t *res, i2creg;
TMC2130_status_t status = {0};
if((i2creg = TMCI2C_GetMapAddress((uint8_t)(driver ? (uint32_t)driver->cs_pin : 0), reg->addr).value) == 0xFF)
return status; // unsupported register
while(i2cIsBusy);
i2c.buffer[0] = i2creg;
i2c.buffer[1] = 0;
i2c.buffer[2] = 0;
i2c.buffer[3] = 0;
i2c.buffer[4] = 0;
res = I2C_ReadRegister(I2C_ADR_I2CBRIDGE, NULL, 5, true);
status.value = (uint8_t)*res++;
reg->payload.value = ((uint8_t)*res++ << 24);
reg->payload.value |= ((uint8_t)*res++ << 16);
reg->payload.value |= ((uint8_t)*res++ << 8);
reg->payload.value |= (uint8_t)*res++;
return status;
}
static TMC2130_status_t I2C_TMC_WriteRegister (TMC2130_t *driver, TMC2130_datagram_t *reg)
{
TMC2130_status_t status = {0};
while(i2cIsBusy);
reg->addr.write = 1;
i2c.buffer[0] = TMCI2C_GetMapAddress((uint8_t)(driver ? (uint32_t)driver->cs_pin : 0), reg->addr).value;
reg->addr.write = 0;
if(i2c.buffer[0] == 0xFF)
return status; // unsupported register
i2c.buffer[1] = (reg->payload.value >> 24) & 0xFF;
i2c.buffer[2] = (reg->payload.value >> 16) & 0xFF;
i2c.buffer[3] = (reg->payload.value >> 8) & 0xFF;
i2c.buffer[4] = reg->payload.value & 0xFF;
I2C_Send(I2C_ADR_I2CBRIDGE, NULL, 5, true);
return status;
}
void I2C_DriverInit (TMC_io_driver_t *driver)
{
i2c_init();
driver->WriteRegister = I2C_TMC_WriteRegister;
driver->ReadRegister = I2C_TMC_ReadRegister;
}
#endif
static void I2C_interrupt_handler (void)
{
uint32_t ifg = port->MSR & 0xFFFF;
port->MSR &= ~ifg;
if((ifg & port->MIER) == 0) return;
//if(port->MSR & LPI2C_MSR_MBF) return;
/*
hal.stream.write("I:");
hal.stream.write(uitoa(ifg));
hal.stream.write(" ");
hal.stream.write(uitoa(i2c.state));
hal.stream.write(ASCII_EOL);
*/
if(ifg & LPI2C_MSR_ALF) {
port->MTDR = LPI2C_MTDR_CMD_STOP;
i2c.state = I2CState_Error;
}
if(ifg & LPI2C_MSR_NDF)
i2c.state = I2CState_Error;
switch(i2c.state) {
case I2CState_Idle:
case I2CState_Error:
port->MIER = 0;
port->MCR |= (LPI2C_MCR_RTF|LPI2C_MCR_RRF);
break;
case I2CState_SendNext:
port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | (uint32_t)(*i2c.data++);
if(--i2c.count == 1)
i2c.state = I2CState_SendLast;
break;
case I2CState_SendLast:
port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | (uint32_t)(*i2c.data++);
i2c.state = I2CState_AwaitCompletion;
break;
case I2CState_AwaitCompletion:
port->MIER &= ~LPI2C_MIER_TDIE;
port->MTDR = LPI2C_MTDR_CMD_STOP;
i2c.count = 0;
i2c.state = I2CState_Idle;
break;
case I2CState_SendAddr:
port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | (uint32_t)(i2c.regaddr[--i2c.acount]);
if(i2c.acount == 0)
i2c.state = I2CState_Restart;
break;
case I2CState_Restart:
if(port->MIER & LPI2C_MIER_TDIE) {
port->MIER &= ~LPI2C_MIER_TDIE;
port->MIER |= LPI2C_MIER_EPIE;
port->MTDR = LPI2C_MTDR_CMD_START | (i2c.addr << 1) | 1;
} else if(port->MIER & LPI2C_MIER_EPIE) {
port->MIER &= ~LPI2C_MIER_EPIE;
port->MIER |= LPI2C_MIER_RDIE;
port->MTDR = LPI2C_MTDR_CMD_RECEIVE | ((uint32_t)i2c.count - 1);
i2c.state = i2c.count == 1 ? I2CState_ReceiveLast : (i2c.count == 2 ? I2CState_ReceiveNextToLast : I2CState_ReceiveNext);
}
break;
case I2CState_ReceiveNext: // superfluous, to be removed...
*i2c.data++ = port->MRDR & 0xFF;
if(--i2c.count == 1) {
i2c.state = I2CState_ReceiveLast;
}
++i2c.rcount;
break;
case I2CState_ReceiveNextToLast:
*i2c.data++ = port->MRDR & 0xFF;
// port->MTDR = LPI2C_MTDR_CMD_STOP;
i2c.count--;
i2c.state = I2CState_ReceiveLast;
break;
case I2CState_ReceiveLast:
*i2c.data = port->MRDR & 0xFF;
i2c.count = 0;
i2c.state = I2CState_Idle;
port->MTDR = LPI2C_MTDR_CMD_STOP;
#if KEYPAD_ENABLE
if(i2c.keycode_callback) {
i2c.keycode_callback(*i2c.data);
i2c.keycode_callback = NULL;
}
#endif
break;
default:
break;
}
}
#endif