IndustrialDeviceController/Software/MT3620_IDC_RTApp/lib/I2CMaster.c (442 lines of code) (raw):

/* Copyright (c) Codethink Ltd. All rights reserved. Licensed under the MIT License. */ #include "I2CMaster.h" #include "NVIC.h" #include "mt3620/i2c.h" #include "mt3620/dma.h" #include <stdbool.h> static inline void fifoClear(volatile uint32_t *fifo, bool tx, bool rx) { mt3620_i2c_mm_fifo_con0_t fifo_con0 = { .mask = 0 }; fifo_con0.tx_fifo_clr = tx; fifo_con0.rx_fifo_clr = rx; *fifo = fifo_con0.mask; } static inline void fifoClearMaster(unsigned id, bool tx, bool rx) { fifoClear(&mt3620_i2c[id]->mm_fifo_con0, tx, rx); } static inline void fifoClearSubordinate(unsigned id, bool tx, bool rx) { fifoClear(&mt3620_i2c[id]->s_fifo_con0, tx, rx); } struct I2CMaster { bool open; uint32_t id; void (*callback) (int32_t, uintptr_t); void (*callbackUser)(int32_t, uintptr_t, void*); void *userData; uintptr_t txQueued, rxQueued; const I2C_Transfer *transfer; uint32_t count; bool useDMA; volatile bool error; }; static I2CMaster context[MT3620_I2C_COUNT] = {0}; // TODO: Clean this up. #define I2C_PRIORITY 2 static inline unsigned I2CMaster_UnitToID(Platform_Unit unit) { if ((unit < MT3620_UNIT_ISU0) || (unit > MT3620_UNIT_ISU5)) { return MT3620_I2C_COUNT; } return (unit - MT3620_UNIT_ISU0); } I2CMaster *I2CMaster_Open(Platform_Unit unit) { unsigned id = I2CMaster_UnitToID(unit); if (id >= MT3620_I2C_COUNT) { return NULL; } if (context[id].open) { return NULL; } // Detect if interface is in-use as a subordinate device. if (MT3620_I2C_FIELD_READ(id, s_con0, slave_en)) { return NULL; } // TODO: Find more atomic way of separating master and subordinate device drivers. // Enable master mode immediately to reduce risk of collision with subordinate device. MT3620_I2C_FIELD_WRITE(id, mm_con0, master_en, true); context[id].id = id; context[id].open = true; context[id].callback = NULL; context[id].callbackUser = NULL; context[id].userData = NULL; context[id].txQueued = 0; context[id].rxQueued = 0; context[id].transfer = NULL; context[id].count = 0; context[id].useDMA = false; context[id].error = false; // Enable Sync mode. MT3620_I2C_FIELD_WRITE(id, mm_pad_con0, sync_en, true); I2CMaster_SetBusSpeed(&context[id], I2C_BUS_SPEED_STANDARD); fifoClearMaster(id, true, true); fifoClearSubordinate(id, true, true); mt3620_dma_global->ch_en_set = (1U << MT3620_I2C_DMA_TX(id)); MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_TX(id), start, str, false); mt3620_dma_con_t dma_con_tx = { .mask = mt3620_dma[MT3620_I2C_DMA_TX(id)].con }; dma_con_tx.dir = 0; dma_con_tx.wpen = false; dma_con_tx.wpsd = 0; dma_con_tx.iten = false; dma_con_tx.hiten = false; dma_con_tx.dreq = true; dma_con_tx.dinc = 0; dma_con_tx.sinc = 1; dma_con_tx.size = 0; mt3620_dma[MT3620_I2C_DMA_TX(id)].con = dma_con_tx.mask; mt3620_dma[MT3620_I2C_DMA_TX(id)].fixaddr = (uint32_t *)&mt3620_i2c[id]->mm_fifo_data; mt3620_dma_global->ch_en_set = (1U << MT3620_I2C_DMA_RX(id)); MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_RX(id), start, str, false); mt3620_dma_con_t dma_con_rx = { .mask = mt3620_dma[MT3620_I2C_DMA_RX(id)].con }; dma_con_rx.dir = 1; dma_con_rx.wpen = false; dma_con_rx.wpsd = 1; dma_con_rx.iten = false; dma_con_rx.hiten = false; dma_con_rx.dreq = true; dma_con_rx.dinc = 1; dma_con_rx.sinc = 0; dma_con_rx.size = 0; mt3620_dma[MT3620_I2C_DMA_RX(id)].con = dma_con_rx.mask; mt3620_dma[MT3620_I2C_DMA_RX(id)].fixaddr = (uint32_t *)&mt3620_i2c[id]->mm_fifo_data; // Enable DMA handshakes for master mode. mt3620_i2c_dma_con0_t dma_con0 = { .mask = mt3620_i2c[id]->dma_con0 }; dma_con0.dma_hs_sel = 0; dma_con0.dma_hs_en = true; mt3620_i2c[id]->dma_con0 = dma_con0.mask; MT3620_I2C_FIELD_WRITE(id, int_ctrl, mm_int_sta, true); // Enable and Set the NVIC interrupt priority. NVIC_EnableIRQ(MT3620_I2C_INTERRUPT(id), I2C_PRIORITY); MT3620_I2C_FIELD_WRITE(id, int_ctrl, mm_int_en, true); return &context[id]; } void I2CMaster_Close(I2CMaster *handle) { if (!handle || !handle->open) { return; } mt3620_dma_global->ch_en_clr = (1U << MT3620_I2C_DMA_TX(handle->id)); mt3620_dma_global->ch_en_clr = (1U << MT3620_I2C_DMA_RX(handle->id)); MT3620_I2C_FIELD_WRITE(handle->id, int_ctrl, mm_int_en, false); MT3620_I2C_FIELD_WRITE(handle->id, mm_con0 , master_en, false); // Disable NVIC interrupts. NVIC_DisableIRQ(MT3620_I2C_INTERRUPT(handle->id)); handle->open = false; } int32_t I2CMaster_SetBusSpeed(I2CMaster *handle, I2C_BusSpeed speed) { if (!handle) { return ERROR_PARAMETER; } if (!handle->open) { return ERROR_HANDLE_CLOSED; } if ((speed == 0) || (speed > MT3620_I2C_MAX_SPEED)) { return ERROR_UNSUPPORTED; } // TODO: Update this to use additional logic from Linux driver. uint32_t period = MT3620_I2C_CLOCK / speed; if (period >= (255 * 4)) { return ERROR_UNSUPPORTED; } mt3620_i2c_vec4_t phl = { .mask = mt3620_i2c[handle->id]->mm_cnt_val_phl }; mt3620_i2c_vec4_t phh = { .mask = mt3620_i2c[handle->id]->mm_cnt_val_phh }; phl.x = ((period + 1) / 4); phl.y = ((period + 0) / 4); phh.x = ((period + 2) / 4); phh.y = ((period + 3) / 4); mt3620_i2c[handle->id]->mm_cnt_val_phl = phl.mask; mt3620_i2c[handle->id]->mm_cnt_val_phh = phh.mask; return ERROR_NONE; } int32_t I2CMaster_GetBusSpeed(const I2CMaster *handle, I2C_BusSpeed *speed) { if (!handle) { return ERROR_PARAMETER; } if (!handle->open) { return ERROR_HANDLE_CLOSED; } mt3620_i2c_vec4_t phl = { .mask = mt3620_i2c[handle->id]->mm_cnt_val_phl }; mt3620_i2c_vec4_t phh = { .mask = mt3620_i2c[handle->id]->mm_cnt_val_phh }; unsigned period = phl.x + phl.y + phh.x + phh.y; if (period == 0) { return ERROR_HARDWARE_STATE; } if (speed) { *speed = (MT3620_I2C_CLOCK / period); } return ERROR_NONE; } // TODO: Move this to a dedicated/common memory/sram header. // TODO: Reference datasheet for address ranges. static bool addrOnBus(const void* ptr) { uintptr_t addr = (uintptr_t)ptr; if ((addr >= 0x00010000) && (addr < 0x10000000)) { return false; } if ((addr >= 0x20000000) && (addr < 0x20100000)) { return false; } if ((addr >= 0xE000E000) && (addr < 0xE000F000)) { return false; } return true; } int32_t I2CMaster_TransferSequentialAsync_UserData( I2CMaster *handle, uint16_t address, const I2C_Transfer *transfer, uint32_t count, void (*callback)(int32_t status, uintptr_t count, void* userData), void *userData) { if (!handle) { return ERROR_PARAMETER; } if (!handle->open) { return ERROR_HANDLE_CLOSED; } if (handle->transfer) { return ERROR_BUSY; } // We don't support more than 7-bit addressing. if ((address >> 7) != 0) { return ERROR_UNSUPPORTED; } if (!transfer || (count == 0)) { return ERROR_PARAMETER; } // It's up to the user to group transfers of the same type if (count > MT3620_I2C_QUEUE_DEPTH) { return ERROR_UNSUPPORTED; } bool onBus = true; unsigned wcnt = 0, rcnt = 0; unsigned wdata = 0, rdata = 0; unsigned i; for (i = 0; i < count; i++) { if (transfer[i].length > MT3620_I2C_PACKET_SIZE_MAX) { return ERROR_UNSUPPORTED; } // Transfer must be a read or a write. if (!transfer[i].writeData && !transfer[i].readData) { return ERROR_PARAMETER; } // I2C doesn't support duplex transfers. if (transfer[i].writeData && transfer[i].readData) { return ERROR_UNSUPPORTED; } if (transfer[i].writeData) { if (!addrOnBus(transfer[i].writeData)) { onBus = false; } wcnt++; wdata += transfer[i].length; } if (transfer[i].readData) { if (!addrOnBus(transfer[i].readData)) { onBus = false; } rcnt++; rdata += transfer[i].length; } } if (MT3620_I2C_FIELD_READ(handle->id, mm_status, bus_busy)) { return ERROR_BUSY; } bool useDMA = false; if ((wdata > MT3620_I2C_TX_FIFO_DEPTH) || (rdata > MT3620_I2C_RX_FIFO_DEPTH)) { // DMA can only access data on the bus (i.e. not TCM). if (!onBus) { return ERROR_DMA_SOURCE; } // DMA can queue a maximum of two transactions in each direction. if ((wcnt > 2) || (rcnt > 2)) { return ERROR_UNSUPPORTED; } useDMA = true; } handle->callbackUser = callback; handle->userData = userData; handle->useDMA = useDMA; handle->txQueued = wdata; handle->rxQueued = rdata; handle->transfer = transfer; handle->count = count; mt3620_i2c[handle->id]->mm_slave_id = address; for (i = 0; i < count; i++) { mt3620_i2c[handle->id]->mm_cnt_byte_val_pk[i] = transfer[i].length; } mt3620_i2c_mm_pack_con0_t mm_pack_con0 = { .mask = mt3620_i2c[handle->id]->mm_pack_con0 }; mm_pack_con0.mm_pack_rw0 = (transfer[0].readData != NULL); if (count >= 2) mm_pack_con0.mm_pack_rw1 = (transfer[1].readData != NULL); if (count >= 3) mm_pack_con0.mm_pack_rw2 = (transfer[2].readData != NULL); mm_pack_con0.mm_pack_val = (count - 1); mt3620_i2c[handle->id]->mm_pack_con0 = mm_pack_con0.mask; if (useDMA) { volatile mt3620_dma_t * const tx_dma = &mt3620_dma[MT3620_I2C_DMA_TX(handle->id)]; volatile mt3620_dma_t * const rx_dma = &mt3620_dma[MT3620_I2C_DMA_RX(handle->id)]; unsigned w = 0, r = 0; for (i = 0; i < count; i++) { if (transfer[i].writeData) { if (w++ == 0) { tx_dma->pgmaddr = (uint32_t *)transfer[i].writeData; tx_dma->wppt = transfer[i].length; tx_dma->count = transfer[i].length; } else { tx_dma->wpto = (uint32_t *)transfer[i].writeData; tx_dma->count += transfer[i].length; } } else { if (r++ == 0) { rx_dma->pgmaddr = transfer[i].readData; rx_dma->wppt = transfer[i].length; rx_dma->count = transfer[i].length; } else { rx_dma->wpto = transfer[i].readData; rx_dma->count += transfer[i].length; } } } if (wcnt > 0) { MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_TX(handle->id), con, wpen, (wcnt > 1)); // Start DMA TX transfer. MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_TX(handle->id), start, str, true); } if (rcnt > 0) { MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_RX(handle->id), con, wpen, (rcnt > 1)); // Start DMA RX transfer. MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_RX(handle->id), start, str, true); } } else { // Pre-fill TX buffer with data. for (i = 0; i < count; i++) { const uint8_t *writeData = transfer[i].writeData; if (writeData) { unsigned j; for (j = 0; j < transfer[i].length; j++) { mt3620_i2c[handle->id]->mm_fifo_data = writeData[j]; } } } } // Wait until I2C is ready after setting config while (!MT3620_I2C_FIELD_READ(handle->id, mm_status, mm_start_ready)) { // Do nothing. } mt3620_i2c_mm_con0_t mm_con0 = { .mask = mt3620_i2c[handle->id]->mm_con0 }; mm_con0.mm_gmode = true; mm_con0.mm_start_en = true; mt3620_i2c[handle->id]->mm_con0 = mm_con0.mask; return ERROR_NONE; } int32_t I2CMaster_TransferSequentialAsync( I2CMaster *handle, uint16_t address, const I2C_Transfer *transfer, uint32_t count, void (*callback)(int32_t status, uintptr_t count)) { if (!handle || !callback) { return ERROR_PARAMETER; } handle->callback = callback; int32_t error = I2CMaster_TransferSequentialAsync_UserData( handle, address, transfer, count, NULL, NULL); if (error != ERROR_NONE) { handle->callback = NULL; } return error; } typedef struct { volatile bool ready; int32_t status; int32_t count; } I2CMaster_TransferSequentialSync_State; static void I2CMaster_TransferSequentialSync_Callback( int32_t status, uintptr_t count, void *userData) { if (!userData) { return; } I2CMaster_TransferSequentialSync_State *state = userData; state->status = status; state->count = count; state->ready = true; } int32_t I2CMaster_TransferSequentialSync( I2CMaster *handle, uint16_t address, const I2C_Transfer *transfer, uint32_t count) { I2CMaster_TransferSequentialSync_State state = {0}; int32_t status = I2CMaster_TransferSequentialAsync_UserData( handle, address, transfer, count, I2CMaster_TransferSequentialSync_Callback, &state); if (status != ERROR_NONE) { return status; } while (!state.ready) { __asm__("wfi"); } return state.status; } static void I2CMaster_IRQ(Platform_Unit unit) { unsigned id = I2CMaster_UnitToID(unit); if (id >= MT3620_I2C_COUNT) { return; } MT3620_I2C_FIELD_WRITE(id, int_ctrl, mm_int_sta, true); I2CMaster* handle = &context[id]; // This should never happen if (!handle->open) { return; } int32_t status = ERROR_NONE; if ((MT3620_I2C_FIELD_READ(id, mm_ack_val, mm_ack_id) & 0x1) != 0) { status = ERROR_I2C_ADDRESS_NACK; } else if (MT3620_I2C_FIELD_READ(id, mm_status, mm_arb_had_lose)) { MT3620_I2C_FIELD_WRITE(id, mm_status, mm_arb_had_lose, 1); status = ERROR_I2C_ARBITRATION_LOST; } uintptr_t txRemain = 0; uintptr_t rxRemain = 0; if (handle->useDMA) { MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_TX(id), start, str, false); MT3620_DMA_FIELD_WRITE(MT3620_I2C_DMA_RX(id), start, str, false); txRemain += mt3620_dma[MT3620_I2C_DMA_TX(id)].rlct; rxRemain += mt3620_dma[MT3620_I2C_DMA_RX(id)].rlct; } else { unsigned i; for (i = 0; i < handle->count; i++) { uint8_t *readData = handle->transfer[i].readData; if (readData) { unsigned j; for (j = 0; j < handle->transfer[i].length; j++) { if (!MT3620_I2C_FIELD_READ(id, mm_fifo_status, rx_fifo_emp)) { readData[j] = mt3620_i2c[handle->id]->mm_fifo_data; } else { rxRemain++; } } } } } mt3620_i2c_fifo_status_t fifo_status = { .mask = mt3620_i2c[id]->mm_fifo_status }; mt3620_i2c_fifo_ptr_t fifo_ptr = { .mask = mt3620_i2c[id]->mm_fifo_ptr }; bool txClear = !fifo_status.tx_fifo_emp; if (txClear) { txRemain += (fifo_status.tx_fifo_full ? MT3620_I2C_TX_FIFO_DEPTH : (fifo_ptr.tx_fifo_wptr - fifo_ptr.tx_fifo_rptr)); } bool rxClear = !fifo_status.rx_fifo_emp; if (rxClear) { rxRemain += (fifo_status.rx_fifo_full ? MT3620_I2C_RX_FIFO_DEPTH : (fifo_ptr.rx_fifo_wptr - fifo_ptr.rx_fifo_rptr)); } if (txClear || rxClear) { fifoClearMaster(id, txClear, rxClear); if (status == ERROR_NONE) status = ERROR_I2C_TRANSFER_INCOMPLETE; } uintptr_t txCount = (txRemain > handle->txQueued ? 0 : (handle->txQueued - txRemain)); uintptr_t rxCount = (rxRemain > handle->rxQueued ? 0 : (handle->rxQueued - rxRemain)); uintptr_t dataCount = (txCount + rxCount); if (handle->callback) { handle->callback(status, dataCount); } else if (handle->callbackUser) { handle->callbackUser(status, dataCount, handle->userData); } handle->error = (status != ERROR_NONE); handle->transfer = NULL; handle->count = 0; handle->txQueued = 0; handle->rxQueued = 0; handle->callback = NULL; handle->callbackUser = NULL; handle->userData = NULL; } void isu_g0_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU0); } void isu_g1_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU1); } void isu_g2_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU2); } void isu_g3_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU3); } void isu_g4_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU4); } void isu_g5_i2c_irq(void) { I2CMaster_IRQ(MT3620_UNIT_ISU5); }