| /* |
| * Copyright (c) 2017, Intel Corporation |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. Neither the name of the Intel Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL CORPORATION OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <string.h> |
| #include "qm_i2c.h" |
| #include "clk.h" |
| |
| #define TX_TL (2) |
| #define RX_TL (5) |
| #define SPK_LEN_SS (1) |
| #define SPK_LEN_FS_FSP (2) |
| |
| /* Number of retries before giving up on disabling the controller. */ |
| #define I2C_POLL_COUNT (1000000) |
| #define I2C_POLL_MICROSECOND (1) |
| |
| #ifndef UNIT_TEST |
| #if (QUARK_SE) |
| qm_i2c_reg_t *qm_i2c[QM_I2C_NUM] = {(qm_i2c_reg_t *)QM_I2C_0_BASE, |
| (qm_i2c_reg_t *)QM_I2C_1_BASE}; |
| #elif(QUARK_D2000) |
| qm_i2c_reg_t *qm_i2c[QM_I2C_NUM] = {(qm_i2c_reg_t *)QM_I2C_0_BASE}; |
| #endif |
| #endif |
| |
| static volatile const qm_i2c_transfer_t *i2c_transfer[QM_I2C_NUM]; |
| static volatile uint32_t i2c_write_pos[QM_I2C_NUM], i2c_read_pos[QM_I2C_NUM], |
| i2c_read_cmd_send[QM_I2C_NUM]; |
| |
| /* True if user buffers have been updated. */ |
| static volatile bool transfer_ongoing; |
| static volatile bool first_start; |
| /* |
| * Keep track of activity if addressed. |
| * There is no register which keeps track of the internal state machine status, |
| * whether it is addressed, transmitting or receiving. |
| * The only way to keep track of this is to save the information that the driver |
| * received one the following interrupts: |
| * - General call interrupt |
| * - Read request |
| * - RX FIFO full. |
| * Also, if no interrupt has been received during an RX transaction, the driver |
| * can check the controller has been addressed if data has been effectively |
| * received. |
| */ |
| static volatile bool is_addressed = false; |
| |
| /* |
| * I2C DMA controller configuration descriptor. |
| */ |
| typedef struct { |
| qm_i2c_t i2c; /* I2C controller. */ |
| qm_dma_t dma_controller_id; |
| qm_dma_channel_id_t dma_tx_channel_id; /* TX channel ID. */ |
| qm_dma_transfer_t dma_tx_transfer_config; /* Configuration for TX. */ |
| qm_dma_channel_id_t dma_rx_channel_id; /* RX channel ID. */ |
| qm_dma_transfer_t dma_rx_transfer_config; /* Configuration for RX. */ |
| /* Configuration for writing READ commands during an RX operation. */ |
| qm_dma_transfer_t dma_cmd_transfer_config; |
| volatile bool ongoing_dma_tx_operation; /* Keep track of ongoing TX. */ |
| volatile bool ongoing_dma_rx_operation; /* Keep track of oingoing RX. */ |
| int tx_abort_status; |
| int i2c_error_code; |
| } i2c_dma_context_t; |
| |
| /* DMA context. */ |
| i2c_dma_context_t i2c_dma_context[QM_I2C_NUM]; |
| |
| /* Define the DMA interfaces. */ |
| #if (QUARK_SE) |
| qm_dma_handshake_interface_t i2c_dma_interfaces[QM_I2C_NUM][3] = { |
| {-1, DMA_HW_IF_I2C_MASTER_0_TX, DMA_HW_IF_I2C_MASTER_0_RX}, |
| {-1, DMA_HW_IF_I2C_MASTER_1_TX, DMA_HW_IF_I2C_MASTER_1_RX}}; |
| #endif |
| #if (QUARK_D2000) |
| qm_dma_handshake_interface_t i2c_dma_interfaces[QM_I2C_NUM][3] = { |
| {-1, DMA_HW_IF_I2C_MASTER_0_TX, DMA_HW_IF_I2C_MASTER_0_RX}}; |
| #endif |
| |
| static void i2c_dma_transmit_callback(void *callback_context, uint32_t len, |
| int error_code); |
| static void i2c_dma_receive_callback(void *callback_context, uint32_t len, |
| int error_code); |
| static int i2c_start_dma_read(const qm_i2c_t i2c); |
| void *i2c_dma_callbacks[] = {NULL, i2c_dma_transmit_callback, |
| i2c_dma_receive_callback}; |
| |
| static void controller_enable(const qm_i2c_t i2c); |
| static int controller_disable(const qm_i2c_t i2c); |
| |
| /* |
| * Empty RX FIFO. |
| * Try to empty FIFO to user buffer. If RX buffer is full, trigger callback. |
| * If user does not update buffer when requested, empty FIFO without storing |
| * received data. |
| */ |
| static void empty_rx_fifo(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| while (controller->ic_status & QM_I2C_IC_STATUS_RFNE) { |
| if (!transfer_ongoing) { |
| /* Dummy read. */ |
| controller->ic_data_cmd; |
| } else { |
| if (transfer->rx_len > i2c_read_pos[i2c]) { |
| transfer->rx[i2c_read_pos[i2c]++] = |
| controller->ic_data_cmd & 0xFF; |
| } |
| |
| if (transfer->rx_len == i2c_read_pos[i2c]) { |
| /* |
| * End user transfer if user does not update |
| * buffers. |
| */ |
| transfer_ongoing = false; |
| |
| if (transfer->callback) { |
| transfer->callback( |
| transfer->callback_data, 0, |
| QM_I2C_RX_FULL, transfer->rx_len); |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * Fill TX FIFO. |
| * Try to fill the FIFO with user data. If TX buffer is empty, trigger callback. |
| * If user does not update buffer when requested, fill the FIFO with dummy |
| * data. |
| */ |
| static void slave_fill_tx_fifo(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| while ((controller->ic_status & QM_I2C_IC_STATUS_TNF) && |
| (!(controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_ABRT))) { |
| if (!transfer_ongoing) { |
| /* Dummy write. */ |
| controller->ic_data_cmd = 0; |
| |
| } else { |
| if (transfer->tx_len > i2c_write_pos[i2c]) { |
| controller->ic_data_cmd = |
| transfer->tx[i2c_write_pos[i2c]++]; |
| } |
| |
| if (transfer->tx_len == i2c_write_pos[i2c]) { |
| /* |
| * End user transfer if user does not update |
| * buffers. |
| */ |
| transfer_ongoing = false; |
| |
| if (transfer->callback) { |
| transfer->callback( |
| transfer->callback_data, 0, |
| QM_I2C_TX_EMPTY, transfer->tx_len); |
| } |
| } |
| } |
| } |
| } |
| |
| static __inline__ int handle_tx_abrt_common(qm_i2c_reg_t *const controller, |
| qm_i2c_status_t *status) |
| { |
| int rc = 0; |
| |
| QM_ASSERT(!(controller->ic_tx_abrt_source & |
| QM_I2C_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT)); |
| |
| *status = QM_I2C_TX_ABORT; |
| |
| /* Get source of TX_ABRT interrupt. */ |
| *status |= |
| (controller->ic_tx_abrt_source & QM_I2C_IC_TX_ABRT_SOURCE_ALL_MASK); |
| |
| /* Clear TX ABORT interrupt. */ |
| controller->ic_clr_tx_abrt; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| return rc = (*status & QM_I2C_TX_ABRT_USER_ABRT) ? -ECANCELED : -EIO; |
| } |
| |
| static __inline__ void |
| handle_irq_tx_abrt(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| qm_i2c_status_t status = 0; |
| int rc = 0; |
| |
| rc = handle_tx_abrt_common(controller, &status); |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, rc, status, |
| i2c_write_pos[i2c]); |
| } |
| } |
| |
| static __inline__ void handle_dma_tx_abrt(const qm_i2c_t i2c, |
| qm_i2c_reg_t *const controller) |
| { |
| qm_i2c_status_t status = 0; |
| int rc = 0; |
| |
| rc = handle_tx_abrt_common(controller, &status); |
| |
| /* In DMA mode, therefore raise a flag and stop the channels. */ |
| i2c_dma_context[i2c].tx_abort_status = status; |
| i2c_dma_context[i2c].i2c_error_code = rc; |
| /* |
| * When terminating the DMA transfer, the DMA controller calls |
| * the TX or RX callback, which will trigger the error callback. |
| * This will disable the I2C controller. |
| */ |
| qm_i2c_dma_transfer_terminate(i2c); |
| } |
| static __inline__ void |
| i2c_isr_slave_handler(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| /* Save register to speed up process in interrupt. */ |
| uint32_t ic_intr_stat = controller->ic_intr_stat; |
| |
| /* |
| * Order of interrupt handling: |
| * - Stop interrupts |
| * - Start interrupts |
| * - RX Status interrupts |
| * - TX Status interrupts (RD_REQ, RX_DONE, TX_EMPTY) |
| * - General call (will only appear after few SCL clock cycles after |
| * start interrupt). |
| */ |
| |
| /* Stop condition detected. */ |
| if (ic_intr_stat & QM_I2C_IC_INTR_STAT_STOP_DETECTED) { |
| /* Empty RX FIFO. */ |
| empty_rx_fifo(i2c, transfer, controller); |
| |
| /* |
| * Stop transfer if single transfer asked and controller has |
| * been addressed. |
| * Driver only knows it has been addressed if: |
| * - It already triggered an interrupt on TX_EMPTY or RX_FULL |
| * - Data was read from RX FIFO. |
| */ |
| if ((transfer->stop == true) && |
| (is_addressed || (i2c_read_pos[i2c] != 0))) { |
| controller_disable(i2c); |
| } |
| |
| if (transfer->callback) { |
| transfer->callback( |
| transfer->callback_data, 0, QM_I2C_STOP_DETECTED, |
| (transfer_ongoing) ? i2c_read_pos[i2c] : 0); |
| } |
| i2c_write_pos[i2c] = 0; |
| i2c_read_pos[i2c] = 0; |
| |
| controller->ic_intr_mask &= ~QM_I2C_IC_INTR_MASK_TX_EMPTY; |
| |
| is_addressed = false; |
| |
| /* Clear stop interrupt. */ |
| controller->ic_clr_stop_det; |
| |
| /* |
| * Read again the interrupt status in case of a start interrupt |
| * has been triggered in the meantime. |
| */ |
| ic_intr_stat = controller->ic_intr_stat; |
| first_start = true; |
| } |
| |
| /* |
| * START or RESTART condition detected. |
| * The RESTART_DETECTED interrupt is not used as it is redundant with |
| * the START_DETECTED interrupt. |
| */ |
| if (ic_intr_stat & QM_I2C_IC_INTR_STAT_START_DETECTED) { |
| if (!first_start) { |
| empty_rx_fifo(i2c, transfer, controller); |
| } |
| if (transfer->callback) { |
| transfer->callback( |
| transfer->callback_data, 0, QM_I2C_START_DETECTED, |
| (transfer_ongoing) ? i2c_read_pos[i2c] : 0); |
| } |
| transfer_ongoing = true; |
| i2c_write_pos[i2c] = 0; |
| i2c_read_pos[i2c] = 0; |
| |
| /* Clear Start detected interrupt. */ |
| controller->ic_clr_start_det; |
| first_start = false; |
| } |
| |
| /* |
| * Check RX status. |
| * Master write (TX), slave read (RX). |
| * |
| * Interrupts handled for RX status: |
| * - RX FIFO Overflow |
| * - RX FIFO Full (interrupt remains active until FIFO emptied) |
| * |
| * RX FIFO overflow must always be checked though, in case of an |
| * overflow happens during RX_FULL interrupt handling. |
| */ |
| /* RX FIFO Overflow. */ |
| if (ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_OVER) { |
| controller->ic_clr_rx_over; |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, 0, |
| QM_I2C_RX_OVER, 0); |
| } |
| } |
| |
| /* RX FIFO FULL. */ |
| if (ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_FULL) { |
| /* Empty RX FIFO. */ |
| empty_rx_fifo(i2c, transfer, controller); |
| |
| /* Track activity of controller when addressed. */ |
| is_addressed = true; |
| } |
| |
| /* |
| * Check TX status. |
| * Master read (RX), slave write (TX). |
| * |
| * Interrupts handled for TX status: |
| * - Read request |
| * - RX done (actually a TX state: RX done by master) |
| * - TX FIFO empty. |
| * |
| * TX FIFO empty interrupt must be handled after RX DONE interrupt: when |
| * RX DONE is triggered, TX FIFO is flushed (thus emptied) creating a |
| * TX_ABORT interrupt and a TX_EMPTY condition. TX_ABORT shall be |
| * cleared and TX_EMPTY interrupt disabled. |
| */ |
| else if (ic_intr_stat & QM_I2C_IC_INTR_STAT_RD_REQ) { |
| /* Clear read request interrupt. */ |
| controller->ic_clr_rd_req; |
| |
| /* Track activity of controller when addressed. */ |
| is_addressed = true; |
| |
| slave_fill_tx_fifo(i2c, transfer, controller); |
| |
| /* Enable TX EMPTY interrupts. */ |
| controller->ic_intr_mask |= QM_I2C_IC_INTR_MASK_TX_EMPTY; |
| } else if (ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_DONE) { |
| controller->ic_clr_rx_done; |
| /* Clear TX ABORT as it is triggered when FIFO is flushed. */ |
| controller->ic_clr_tx_abrt; |
| |
| /* Disable TX EMPTY interrupt. */ |
| controller->ic_intr_mask &= ~QM_I2C_IC_INTR_MASK_TX_EMPTY; |
| |
| /* |
| * Read again the interrupt status in case of a stop or a start |
| * interrupt has been triggered in the meantime. |
| */ |
| ic_intr_stat = controller->ic_intr_stat; |
| |
| } else if (ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_EMPTY) { |
| slave_fill_tx_fifo(i2c, transfer, controller); |
| } |
| |
| /* General call detected. */ |
| else if (ic_intr_stat & QM_I2C_IC_INTR_STAT_GEN_CALL_DETECTED) { |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, 0, |
| QM_I2C_GEN_CALL_DETECTED, 0); |
| } |
| #if (FIX_1) |
| /* |
| * Workaround. |
| * The interrupt may not actually be cleared when register is |
| * read too early. |
| */ |
| while (controller->ic_intr_stat & |
| QM_I2C_IC_INTR_STAT_GEN_CALL_DETECTED) { |
| /* Clear General call interrupt. */ |
| controller->ic_clr_gen_call; |
| } |
| #else |
| controller->ic_clr_gen_call; |
| #endif |
| |
| /* Track activity of controller when addressed. */ |
| is_addressed = true; |
| } |
| } |
| |
| static uint32_t |
| master_fill_tx_fifo(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| uint32_t ic_data_cmd, count_tx = (QM_I2C_FIFO_SIZE - TX_TL); |
| uint32_t write_buffer_remaining = transfer->tx_len - i2c_write_pos[i2c]; |
| uint32_t read_buffer_remaining = transfer->rx_len - i2c_read_pos[i2c]; |
| |
| while ((count_tx) && write_buffer_remaining) { |
| count_tx--; |
| write_buffer_remaining--; |
| |
| /* |
| * Write command -IC_DATA_CMD[8] = 0. |
| * Fill IC_DATA_CMD[7:0] with the data. |
| */ |
| ic_data_cmd = transfer->tx[i2c_write_pos[i2c]]; |
| |
| /* |
| * If transfer is a combined transfer, only send stop at |
| * end of the transfer sequence. |
| */ |
| if (transfer->stop && (write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| |
| ic_data_cmd |= QM_I2C_IC_DATA_CMD_STOP_BIT_CTRL; |
| } |
| |
| /* Write data. */ |
| controller->ic_data_cmd = ic_data_cmd; |
| i2c_write_pos[i2c]++; |
| |
| /* |
| * TX_EMPTY INTR is autocleared when the buffer levels |
| * goes above the threshold. |
| */ |
| } |
| |
| return write_buffer_remaining; |
| } |
| |
| static __inline__ void |
| i2c_isr_master_handler(const qm_i2c_t i2c, |
| const volatile qm_i2c_transfer_t *const transfer, |
| qm_i2c_reg_t *const controller) |
| { |
| uint32_t count_tx; |
| uint32_t read_buffer_remaining = transfer->rx_len - i2c_read_pos[i2c]; |
| uint32_t write_buffer_remaining = transfer->tx_len - i2c_write_pos[i2c]; |
| uint32_t missing_bytes; |
| |
| /* RX read from buffer. */ |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_FULL) { |
| |
| while (read_buffer_remaining && controller->ic_rxflr) { |
| transfer->rx[i2c_read_pos[i2c]] = |
| controller->ic_data_cmd; |
| read_buffer_remaining--; |
| i2c_read_pos[i2c]++; |
| |
| if (read_buffer_remaining == 0) { |
| /* |
| * Mask RX full interrupt if transfer |
| * complete. |
| */ |
| controller->ic_intr_mask &= |
| ~(QM_I2C_IC_INTR_MASK_RX_FULL | |
| QM_I2C_IC_INTR_MASK_TX_EMPTY); |
| |
| if (transfer->stop) { |
| controller_disable(i2c); |
| } |
| |
| if (transfer->callback) { |
| transfer->callback( |
| transfer->callback_data, 0, |
| QM_I2C_IDLE, i2c_read_pos[i2c]); |
| } |
| } |
| } |
| |
| if (read_buffer_remaining > 0 && |
| read_buffer_remaining < (RX_TL + 1)) { |
| /* |
| * Adjust the RX threshold so the next 'RX_FULL' |
| * interrupt is generated when all the remaining |
| * data are received. |
| */ |
| controller->ic_rx_tl = read_buffer_remaining - 1; |
| } |
| |
| /* |
| * RX_FULL INTR is autocleared when the buffer levels goes below |
| * the threshold. |
| */ |
| } |
| |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_EMPTY) { |
| |
| if ((controller->ic_status & QM_I2C_IC_STATUS_TFE) && |
| (transfer->tx != NULL) && (write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| |
| controller->ic_intr_mask &= |
| ~QM_I2C_IC_INTR_MASK_TX_EMPTY; |
| |
| /* |
| * If this is not a combined transaction, disable the |
| * controller now. |
| */ |
| if (transfer->stop) { |
| controller_disable(i2c); |
| } |
| |
| /* Callback. */ |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, 0, |
| QM_I2C_IDLE, |
| i2c_write_pos[i2c]); |
| } |
| } |
| |
| write_buffer_remaining = |
| master_fill_tx_fifo(i2c, transfer, controller); |
| |
| /* |
| * If missing_bytes is not null, then that means we are already |
| * waiting for some bytes after sending read request on the |
| * previous interruption. We have to take into account this |
| * value in order to not send too much request so we won't fall |
| * into rx overflow. |
| */ |
| missing_bytes = read_buffer_remaining - i2c_read_cmd_send[i2c]; |
| |
| /* |
| * Sanity check: The number of read data but not processed |
| * cannot be more than the number of expected bytes. |
| */ |
| QM_ASSERT(controller->ic_rxflr <= missing_bytes); |
| |
| /* Count_tx is the remaining size in the FIFO. */ |
| count_tx = QM_I2C_FIFO_SIZE - controller->ic_txflr; |
| |
| if (count_tx > missing_bytes) { |
| count_tx -= missing_bytes; |
| } else { |
| count_tx = 0; |
| } |
| |
| while (i2c_read_cmd_send[i2c] && |
| (write_buffer_remaining == 0) && count_tx) { |
| count_tx--; |
| i2c_read_cmd_send[i2c]--; |
| |
| /* |
| * If transfer is a combined transfer, only send stop at |
| * end of the transfer sequence. |
| */ |
| if (transfer->stop && (i2c_read_cmd_send[i2c] == 0)) { |
| controller->ic_data_cmd = |
| QM_I2C_IC_DATA_CMD_READ | |
| QM_I2C_IC_DATA_CMD_STOP_BIT_CTRL; |
| } else { |
| controller->ic_data_cmd = |
| QM_I2C_IC_DATA_CMD_READ; |
| } |
| } |
| |
| /* Generate a tx_empty interrupt when TX FIFO is fully empty. */ |
| if ((write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| controller->ic_tx_tl = 0; |
| } |
| } |
| } |
| |
| static void i2c_isr_irq_handler(const qm_i2c_t i2c) |
| { |
| const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Check TX_OVER error. */ |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_OVER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_tx_over; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, -EIO, |
| QM_I2C_TX_OVER, i2c_write_pos[i2c]); |
| } |
| } |
| |
| /* Check for RX_UNDER error. */ |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_UNDER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_rx_under; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, -EIO, |
| QM_I2C_RX_UNDER, i2c_write_pos[i2c]); |
| } |
| } |
| |
| /* |
| * TX ABORT interrupt. |
| * Avoid spurious interrupts by checking RX DONE interrupt: RX_DONE |
| * interrupt also trigger a TX_ABORT interrupt when flushing FIFO. |
| */ |
| if ((controller->ic_intr_stat & |
| (QM_I2C_IC_INTR_STAT_TX_ABRT | QM_I2C_IC_INTR_STAT_RX_DONE)) == |
| QM_I2C_IC_INTR_STAT_TX_ABRT) { |
| handle_irq_tx_abrt(i2c, transfer, controller); |
| } |
| |
| /* Master mode. */ |
| if (controller->ic_con & QM_I2C_IC_CON_MASTER_MODE) { |
| /* Check for RX_OVER error. */ |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_OVER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_rx_over; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, |
| -EIO, QM_I2C_RX_OVER, |
| i2c_write_pos[i2c]); |
| } |
| } |
| i2c_isr_master_handler(i2c, transfer, controller); |
| } |
| /* Slave mode. */ |
| else { |
| i2c_isr_slave_handler(i2c, transfer, controller); |
| } |
| } |
| |
| static void i2c_isr_dma_handler(const qm_i2c_t i2c) |
| { |
| const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Check for errors. */ |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_OVER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_tx_over; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, -EIO, |
| QM_I2C_TX_OVER, i2c_write_pos[i2c]); |
| } |
| } |
| |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_UNDER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_rx_under; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, -EIO, |
| QM_I2C_RX_UNDER, i2c_write_pos[i2c]); |
| } |
| } |
| |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_OVER) { |
| /* Clear interrupt. */ |
| controller->ic_clr_rx_over; |
| |
| /* Mask interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_disable(i2c); |
| if (transfer->callback) { |
| transfer->callback(transfer->callback_data, -EIO, |
| QM_I2C_RX_OVER, i2c_write_pos[i2c]); |
| } |
| } |
| |
| /* |
| * TX ABORT interrupt. |
| * Avoid spurious interrupts by checking RX DONE interrupt. |
| */ |
| |
| if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_ABRT) { |
| handle_dma_tx_abrt(i2c, controller); |
| } |
| } |
| |
| QM_ISR_DECLARE(qm_i2c_0_irq_isr) |
| { |
| i2c_isr_irq_handler(QM_I2C_0); |
| QM_ISR_EOI(QM_IRQ_I2C_0_INT_VECTOR); |
| } |
| |
| QM_ISR_DECLARE(qm_i2c_0_dma_isr) |
| { |
| i2c_isr_dma_handler(QM_I2C_0); |
| QM_ISR_EOI(QM_IRQ_I2C_0_INT_VECTOR); |
| } |
| |
| #if (QUARK_SE) |
| QM_ISR_DECLARE(qm_i2c_1_irq_isr) |
| { |
| i2c_isr_irq_handler(QM_I2C_1); |
| QM_ISR_EOI(QM_IRQ_I2C_1_INT_VECTOR); |
| } |
| |
| QM_ISR_DECLARE(qm_i2c_1_dma_isr) |
| { |
| i2c_isr_dma_handler(QM_I2C_1); |
| QM_ISR_EOI(QM_IRQ_I2C_1_INT_VECTOR); |
| } |
| #endif |
| |
| static uint32_t get_lo_cnt(uint32_t lo_time_ns) |
| { |
| return (((get_i2c_clk_freq_in_mhz() * lo_time_ns) / 1000) - 1); |
| } |
| |
| static uint32_t get_hi_cnt(qm_i2c_t i2c, uint32_t hi_time_ns) |
| { |
| return ((((get_i2c_clk_freq_in_mhz() * hi_time_ns) / 1000) - 7 - |
| QM_I2C[i2c]->ic_fs_spklen) + |
| 1); |
| } |
| |
| int qm_i2c_set_config(const qm_i2c_t i2c, const qm_i2c_config_t *const cfg) |
| { |
| uint32_t lcnt = 0, hcnt = 0, min_lcnt = 0, lcnt_diff = 0, ic_con = 0; |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(cfg != NULL, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| i2c_dma_context[i2c].ongoing_dma_rx_operation = false; |
| i2c_dma_context[i2c].ongoing_dma_tx_operation = false; |
| /* Mask all interrupts. */ |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| /* Disable controller. */ |
| if (controller_disable(i2c)) { |
| return -EBUSY; |
| } |
| |
| switch (cfg->mode) { |
| case QM_I2C_MASTER: |
| /* Set mode. */ |
| ic_con = QM_I2C_IC_CON_MASTER_MODE | QM_I2C_IC_CON_RESTART_EN | |
| QM_I2C_IC_CON_SLAVE_DISABLE | |
| /* Set 7/10 bit address mode. */ |
| (cfg->address_mode |
| << QM_I2C_IC_CON_10BITADDR_MASTER_OFFSET); |
| |
| /* |
| * Timing generation algorithm: |
| * 1. compute hi/lo count so as to achieve the desired bus speed |
| * at 50% duty cycle |
| * 2. adjust the hi/lo count to ensure that minimum hi/lo |
| * timings are guaranteed as per spec. |
| */ |
| |
| switch (cfg->speed) { |
| case QM_I2C_SPEED_STD: |
| |
| ic_con |= QM_I2C_IC_CON_SPEED_SS; |
| |
| controller->ic_fs_spklen = SPK_LEN_SS; |
| |
| min_lcnt = get_lo_cnt(QM_I2C_MIN_SS_NS); |
| lcnt = get_lo_cnt(QM_I2C_SS_50_DC_NS); |
| hcnt = get_hi_cnt(i2c, QM_I2C_SS_50_DC_NS); |
| break; |
| |
| case QM_I2C_SPEED_FAST: |
| ic_con |= QM_I2C_IC_CON_SPEED_FS_FSP; |
| |
| controller->ic_fs_spklen = SPK_LEN_FS_FSP; |
| |
| min_lcnt = get_lo_cnt(QM_I2C_MIN_FS_NS); |
| lcnt = get_lo_cnt(QM_I2C_FS_50_DC_NS); |
| hcnt = get_hi_cnt(i2c, QM_I2C_FS_50_DC_NS); |
| break; |
| |
| case QM_I2C_SPEED_FAST_PLUS: |
| ic_con |= QM_I2C_IC_CON_SPEED_FS_FSP; |
| |
| controller->ic_fs_spklen = SPK_LEN_FS_FSP; |
| |
| min_lcnt = get_lo_cnt(QM_I2C_MIN_FSP_NS); |
| lcnt = get_lo_cnt(QM_I2C_FSP_50_DC_NS); |
| hcnt = get_hi_cnt(i2c, QM_I2C_FSP_50_DC_NS); |
| break; |
| } |
| |
| if (hcnt > QM_I2C_IC_HCNT_MAX || hcnt < QM_I2C_IC_HCNT_MIN) { |
| return -EINVAL; |
| } |
| |
| if (lcnt > QM_I2C_IC_LCNT_MAX || lcnt < QM_I2C_IC_LCNT_MIN) { |
| return -EINVAL; |
| } |
| |
| /* Increment minimum low count to account for rounding down. */ |
| min_lcnt++; |
| if (lcnt < min_lcnt) { |
| lcnt_diff = (min_lcnt - lcnt); |
| lcnt += (lcnt_diff); |
| hcnt -= (lcnt_diff); |
| } |
| if (QM_I2C_SPEED_STD == cfg->speed) { |
| controller->ic_ss_scl_lcnt = lcnt; |
| controller->ic_ss_scl_hcnt = hcnt; |
| } else { |
| controller->ic_fs_scl_hcnt = hcnt; |
| controller->ic_fs_scl_lcnt = lcnt; |
| } |
| |
| break; |
| |
| case QM_I2C_SLAVE: |
| /* |
| * QM_I2C_IC_CON_MASTER_MODE and QM_I2C_IC_CON_SLAVE_DISABLE are |
| * deasserted. |
| */ |
| |
| /* Set 7/10 bit address mode. */ |
| ic_con = cfg->address_mode |
| << QM_I2C_IC_CON_10BITADDR_SLAVE_OFFSET; |
| |
| if (cfg->stop_detect_behaviour == |
| QM_I2C_SLAVE_INTERRUPT_WHEN_ADDRESSED) { |
| /* Set stop interrupt only when addressed. */ |
| ic_con |= QM_I2C_IC_CON_STOP_DET_IFADDRESSED; |
| } |
| |
| /* Set slave address. */ |
| controller->ic_sar = cfg->slave_addr; |
| break; |
| } |
| |
| controller->ic_con = ic_con; |
| return 0; |
| } |
| |
| int qm_i2c_set_speed(const qm_i2c_t i2c, const qm_i2c_speed_t speed, |
| const uint16_t lo_cnt, const uint16_t hi_cnt) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(hi_cnt < QM_I2C_IC_HCNT_MAX && lo_cnt > QM_I2C_IC_HCNT_MIN, |
| -EINVAL); |
| QM_CHECK(lo_cnt < QM_I2C_IC_LCNT_MAX && lo_cnt > QM_I2C_IC_LCNT_MIN, |
| -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| uint32_t ic_con = controller->ic_con; |
| ic_con &= ~QM_I2C_IC_CON_SPEED_MASK; |
| |
| switch (speed) { |
| case QM_I2C_SPEED_STD: |
| ic_con |= QM_I2C_IC_CON_SPEED_SS; |
| controller->ic_ss_scl_lcnt = lo_cnt; |
| controller->ic_ss_scl_hcnt = hi_cnt; |
| controller->ic_fs_spklen = SPK_LEN_SS; |
| break; |
| |
| case QM_I2C_SPEED_FAST: |
| case QM_I2C_SPEED_FAST_PLUS: |
| ic_con |= QM_I2C_IC_CON_SPEED_FS_FSP; |
| controller->ic_fs_scl_lcnt = lo_cnt; |
| controller->ic_fs_scl_hcnt = hi_cnt; |
| controller->ic_fs_spklen = SPK_LEN_FS_FSP; |
| break; |
| } |
| |
| controller->ic_con = ic_con; |
| |
| return 0; |
| } |
| |
| int qm_i2c_get_status(const qm_i2c_t i2c, qm_i2c_status_t *const status) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(status != NULL, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| *status = QM_I2C_IDLE; |
| |
| /* Check if slave or master are active. */ |
| if (controller->ic_status & QM_I2C_IC_STATUS_BUSY_MASK) { |
| *status = QM_I2C_BUSY; |
| } |
| |
| /* Check for abort status. */ |
| *status |= |
| (controller->ic_tx_abrt_source & QM_I2C_IC_TX_ABRT_SOURCE_ALL_MASK); |
| |
| return 0; |
| } |
| |
| int qm_i2c_master_write(const qm_i2c_t i2c, const uint16_t slave_addr, |
| const uint8_t *const data, uint32_t len, |
| const bool stop, qm_i2c_status_t *const status) |
| { |
| uint8_t *d = (uint8_t *)data; |
| uint32_t ic_data_cmd = 0; |
| int rc = 0; |
| |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(slave_addr <= QM_I2C_IC_TAR_MASK, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Write slave address to TAR. */ |
| controller->ic_tar &= ~QM_I2C_IC_TAR_MASK; |
| controller->ic_tar |= slave_addr; |
| |
| /* Enable controller. */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| |
| /* Wait if FIFO is full. */ |
| while (!(controller->ic_status & QM_I2C_IC_STATUS_TNF)) |
| ; |
| |
| /* |
| * Write command -IC_DATA_CMD[8] = 0. |
| * Fill IC_DATA_CMD[7:0] with the data. |
| */ |
| ic_data_cmd = *d; |
| |
| /* Send stop after last byte. */ |
| if (len == 0 && stop) { |
| ic_data_cmd |= QM_I2C_IC_DATA_CMD_STOP_BIT_CTRL; |
| } |
| |
| controller->ic_data_cmd = ic_data_cmd; |
| d++; |
| } |
| |
| /* |
| * This is a blocking call, wait until FIFO is empty or tx abrt error. |
| */ |
| while (!(controller->ic_status & QM_I2C_IC_STATUS_TFE)) |
| ; |
| |
| if (controller->ic_tx_abrt_source & QM_I2C_IC_TX_ABRT_SOURCE_ALL_MASK) { |
| rc = -EIO; |
| } |
| |
| /* Disable controller. */ |
| if (true == stop) { |
| if (controller_disable(i2c)) { |
| rc = -EBUSY; |
| } |
| } |
| |
| if (status != NULL) { |
| qm_i2c_get_status(i2c, status); |
| } |
| |
| /* |
| * Clear abort status. |
| * The controller flushes/resets/empties the TX FIFO whenever this bit |
| * is set. The TX FIFO remains in this flushed state until the |
| * register IC_CLR_TX_ABRT is read. |
| */ |
| controller->ic_clr_tx_abrt; |
| |
| return rc; |
| } |
| |
| int qm_i2c_master_read(const qm_i2c_t i2c, const uint16_t slave_addr, |
| uint8_t *const data, uint32_t len, const bool stop, |
| qm_i2c_status_t *const status) |
| { |
| uint8_t *d = (uint8_t *)data; |
| int rc = 0; |
| |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(slave_addr <= QM_I2C_IC_TAR_MASK, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Write slave address to TAR. */ |
| controller->ic_tar &= ~QM_I2C_IC_TAR_MASK; |
| controller->ic_tar |= slave_addr; |
| |
| /* Enable controller. */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| if (len == 0 && stop) { |
| controller->ic_data_cmd = |
| QM_I2C_IC_DATA_CMD_READ | |
| QM_I2C_IC_DATA_CMD_STOP_BIT_CTRL; |
| } |
| |
| else { |
| /* Read command -IC_DATA_CMD[8] = 1. */ |
| controller->ic_data_cmd = QM_I2C_IC_DATA_CMD_READ; |
| } |
| |
| /* Wait if RX FIFO is empty, break if TX empty and error. */ |
| while (!(controller->ic_status & QM_I2C_IC_STATUS_RFNE)) { |
| |
| if (controller->ic_raw_intr_stat & |
| QM_I2C_IC_RAW_INTR_STAT_TX_ABRT) { |
| break; |
| } |
| } |
| |
| if (controller->ic_tx_abrt_source & |
| QM_I2C_IC_TX_ABRT_SOURCE_ALL_MASK) { |
| rc = -EIO; |
| break; |
| } |
| /* IC_DATA_CMD[7:0] contains received data. */ |
| *d = controller->ic_data_cmd; |
| d++; |
| } |
| |
| /* Disable controller. */ |
| if (true == stop) { |
| if (controller_disable(i2c)) { |
| rc = -EBUSY; |
| } |
| } |
| |
| if (status != NULL) { |
| qm_i2c_get_status(i2c, status); |
| } |
| |
| /* |
| * Clear abort status. |
| * The controller flushes/resets/empties the TX FIFO whenever this bit |
| * is set. The TX FIFO remains in this flushed state until the |
| * register IC_CLR_TX_ABRT is read. |
| */ |
| controller->ic_clr_tx_abrt; |
| |
| return rc; |
| } |
| |
| int qm_i2c_master_irq_transfer(const qm_i2c_t i2c, |
| const qm_i2c_transfer_t *const xfer, |
| const uint16_t slave_addr) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(NULL != xfer, -EINVAL); |
| QM_CHECK(slave_addr <= QM_I2C_IC_TAR_MASK, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Write slave address to TAR. */ |
| controller->ic_tar &= ~QM_I2C_IC_TAR_MASK; |
| controller->ic_tar |= slave_addr; |
| |
| i2c_write_pos[i2c] = 0; |
| i2c_read_pos[i2c] = 0; |
| i2c_read_cmd_send[i2c] = xfer->rx_len; |
| i2c_transfer[i2c] = xfer; |
| |
| /* Set threshold. */ |
| controller->ic_tx_tl = TX_TL; |
| if (xfer->rx_len > 0 && xfer->rx_len < (RX_TL + 1)) { |
| /* |
| * If 'rx_len' is less than the default threshold, we have to |
| * change the threshold value so the 'RX FULL' interrupt is |
| * generated once all data from the transfer is received. |
| */ |
| controller->ic_rx_tl = xfer->rx_len - 1; |
| } else { |
| controller->ic_rx_tl = RX_TL; |
| } |
| |
| /* Mask interrupts. */ |
| QM_I2C[i2c]->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| /* Enable controller. */ |
| controller_enable(i2c); |
| |
| /* Start filling tx fifo. */ |
| master_fill_tx_fifo(i2c, xfer, controller); |
| |
| /* Unmask interrupts. */ |
| controller->ic_intr_mask |= |
| QM_I2C_IC_INTR_MASK_RX_UNDER | QM_I2C_IC_INTR_MASK_RX_OVER | |
| QM_I2C_IC_INTR_MASK_RX_FULL | QM_I2C_IC_INTR_MASK_TX_OVER | |
| QM_I2C_IC_INTR_MASK_TX_EMPTY | QM_I2C_IC_INTR_MASK_TX_ABORT; |
| |
| return 0; |
| } |
| |
| int qm_i2c_slave_irq_transfer(const qm_i2c_t i2c, |
| volatile const qm_i2c_transfer_t *const xfer) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(xfer != NULL, -EINVAL); |
| |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| /* Assign common properties. */ |
| i2c_transfer[i2c] = xfer; |
| i2c_write_pos[i2c] = 0; |
| i2c_read_pos[i2c] = 0; |
| |
| transfer_ongoing = false; |
| is_addressed = false; |
| first_start = true; |
| |
| /* Set threshold. */ |
| controller->ic_tx_tl = TX_TL; |
| controller->ic_rx_tl = RX_TL; |
| |
| controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL; |
| |
| controller_enable(i2c); |
| |
| /* |
| * Almost all interrupts must be active to handle everything from the |
| * driver, for the controller not to be stuck in a specific state. |
| * Only TX_EMPTY must be set when needed, otherwise it will be triggered |
| * everytime, even when it is not required to fill the TX FIFO. |
| */ |
| controller->ic_intr_mask = |
| QM_I2C_IC_INTR_MASK_RX_UNDER | QM_I2C_IC_INTR_MASK_RX_OVER | |
| QM_I2C_IC_INTR_MASK_RX_FULL | QM_I2C_IC_INTR_MASK_TX_ABORT | |
| QM_I2C_IC_INTR_MASK_RX_DONE | QM_I2C_IC_INTR_MASK_STOP_DETECTED | |
| QM_I2C_IC_INTR_MASK_START_DETECTED | QM_I2C_IC_INTR_MASK_RD_REQ | |
| QM_I2C_IC_INTR_MASK_GEN_CALL_DETECTED; |
| |
| return 0; |
| } |
| |
| int qm_i2c_slave_irq_transfer_update( |
| const qm_i2c_t i2c, volatile const qm_i2c_transfer_t *const xfer) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(xfer != NULL, -EINVAL); |
| |
| /* Assign common properties. */ |
| i2c_transfer[i2c] = xfer; |
| i2c_write_pos[i2c] = 0; |
| i2c_read_pos[i2c] = 0; |
| |
| /* Tell the ISR we still have data to transfer. */ |
| transfer_ongoing = true; |
| |
| return 0; |
| } |
| |
| static void controller_enable(const qm_i2c_t i2c) |
| { |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| |
| if (!(controller->ic_enable_status & QM_I2C_IC_ENABLE_STATUS_IC_EN)) { |
| /* Enable controller. */ |
| controller->ic_enable |= QM_I2C_IC_ENABLE_CONTROLLER_EN; |
| |
| /* Wait until controller is enabled. */ |
| while (!(controller->ic_enable_status & |
| QM_I2C_IC_ENABLE_STATUS_IC_EN)) |
| ; |
| } |
| |
| /* Be sure that all interrupts flag are cleared. */ |
| controller->ic_clr_intr; |
| } |
| |
| static int controller_disable(const qm_i2c_t i2c) |
| { |
| qm_i2c_reg_t *const controller = QM_I2C[i2c]; |
| int poll_count = I2C_POLL_COUNT; |
| |
| /* Disable controller. */ |
| controller->ic_enable &= ~QM_I2C_IC_ENABLE_CONTROLLER_EN; |
| |
| /* Wait until controller is disabled. */ |
| while ((controller->ic_enable_status & QM_I2C_IC_ENABLE_STATUS_IC_EN) && |
| poll_count--) { |
| clk_sys_udelay(I2C_POLL_MICROSECOND); |
| } |
| |
| /* Returns 0 if ok, meaning controller is disabled. */ |
| return (controller->ic_enable_status & QM_I2C_IC_ENABLE_STATUS_IC_EN); |
| } |
| |
| int qm_i2c_irq_transfer_terminate(const qm_i2c_t i2c) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| |
| /* Abort: |
| * In response to an ABORT, the controller issues a STOP and flushes the |
| * Tx FIFO after completing the current transfer, then sets the TX_ABORT |
| * interrupt after the abort operation. The ABORT bit is cleared |
| * automatically by hardware after the abort operation. |
| */ |
| QM_I2C[i2c]->ic_enable |= QM_I2C_IC_ENABLE_CONTROLLER_ABORT; |
| |
| return 0; |
| } |
| |
| /* |
| * Stops DMA channel and terminates I2C transfer. |
| */ |
| int qm_i2c_dma_transfer_terminate(const qm_i2c_t i2c) |
| { |
| int rc = 0; |
| |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| |
| if (i2c_dma_context[i2c].ongoing_dma_tx_operation) { |
| /* First terminate the DMA transfer. */ |
| rc = qm_dma_transfer_terminate( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_tx_channel_id); |
| } |
| |
| if (i2c_dma_context[i2c].ongoing_dma_rx_operation) { |
| /* First terminate the DMA transfer. */ |
| rc |= qm_dma_transfer_terminate( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_rx_channel_id); |
| } |
| |
| /* Check if any of the calls failed. */ |
| if (rc != 0) { |
| rc = -EIO; |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * Disable TX and/or RX and call user error callback if provided. |
| */ |
| static void i2c_dma_transfer_error_callback(uint32_t i2c, int error_code, |
| uint32_t len) |
| { |
| const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| |
| if (error_code != 0) { |
| if (i2c_dma_context[i2c].ongoing_dma_tx_operation == true) { |
| /* Disable DMA transmit. */ |
| QM_I2C[i2c]->ic_dma_cr &= ~QM_I2C_IC_DMA_CR_TX_ENABLE; |
| i2c_dma_context[i2c].ongoing_dma_tx_operation = false; |
| } |
| |
| if (i2c_dma_context[i2c].ongoing_dma_rx_operation == true) { |
| /* Disable DMA receive. */ |
| QM_I2C[i2c]->ic_dma_cr &= ~QM_I2C_IC_DMA_CR_RX_ENABLE; |
| i2c_dma_context[i2c].ongoing_dma_rx_operation = false; |
| } |
| |
| /* Disable the controller. */ |
| controller_disable(i2c); |
| |
| /* If the user has provided a callback, let's call it. */ |
| if (transfer->callback != NULL) { |
| transfer->callback(transfer->callback_data, error_code, |
| i2c_dma_context[i2c].tx_abort_status, |
| len); |
| } |
| } |
| } |
| |
| /* |
| * After a TX operation, a stop condition may need to be issued along the last |
| * data byte, so that byte is handled here. DMA TX mode does also need to be |
| * disabled. |
| */ |
| static void i2c_dma_transmit_callback(void *callback_context, uint32_t len, |
| int error_code) |
| { |
| qm_i2c_status_t status; |
| |
| qm_i2c_t i2c = ((i2c_dma_context_t *)callback_context)->i2c; |
| const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| |
| if ((error_code == 0) && (i2c_dma_context[i2c].i2c_error_code == 0)) { |
| /* Disable DMA transmit. */ |
| QM_I2C[i2c]->ic_dma_cr &= ~QM_I2C_IC_DMA_CR_TX_ENABLE; |
| i2c_dma_context[i2c].ongoing_dma_tx_operation = false; |
| |
| /* |
| * As the callback is used for both real TX and read command |
| * "TX" during an RX operation, we need to know which case we |
| * are in. |
| */ |
| if (i2c_dma_context[i2c].ongoing_dma_rx_operation == false) { |
| /* Write last byte */ |
| uint32_t data_command = |
| QM_I2C_IC_DATA_CMD_LSB_MASK & |
| ((uint8_t *)i2c_dma_context[i2c] |
| .dma_tx_transfer_config.source_address) |
| [i2c_dma_context[i2c] |
| .dma_tx_transfer_config.block_size]; |
| |
| /* |
| * Check if we must issue a stop condition and it's not |
| * a combined transaction, or bytes transfered are less |
| * than expected. |
| */ |
| if (((transfer->stop == true) && |
| (transfer->rx_len == 0)) || |
| (len != transfer->tx_len - 1)) { |
| data_command |= |
| QM_I2C_IC_DATA_CMD_STOP_BIT_CTRL; |
| } |
| |
| /* Wait if FIFO is full */ |
| while (!(QM_I2C[i2c]->ic_status & QM_I2C_IC_STATUS_TNF)) |
| ; |
| /* Write last byte and increase len count */ |
| QM_I2C[i2c]->ic_data_cmd = data_command; |
| len++; |
| |
| /* |
| * Check if there is a pending read operation, meaning |
| * this is a combined transaction, and transfered data |
| * length is the expected. |
| */ |
| if ((transfer->rx_len > 0) && |
| (len == transfer->tx_len)) { |
| i2c_start_dma_read(i2c); |
| } else { |
| /* |
| * Let's disable the I2C controller if we are |
| * done. |
| */ |
| if ((transfer->stop == true) || |
| (len != transfer->tx_len)) { |
| /* |
| * This callback is called when DMA is |
| * done, but I2C can still be |
| * transmitting, so let's wait until all |
| * data is sent. |
| */ |
| |
| while (!(QM_I2C[i2c]->ic_status & |
| QM_I2C_IC_STATUS_TFE)) { |
| } |
| controller_disable(i2c); |
| } |
| /* |
| * If user provided a callback, it'll be called |
| * only if this is a TX only operation, not in a |
| * combined transaction. |
| */ |
| if (transfer->callback != NULL) { |
| qm_i2c_get_status(i2c, &status); |
| transfer->callback( |
| transfer->callback_data, error_code, |
| status, len); |
| } |
| } |
| } |
| } else { |
| /* |
| * If error code is 0, a multimaster arbitration loss has |
| * happened, so use it as error code. |
| */ |
| if (error_code == 0) { |
| error_code = i2c_dma_context[i2c].i2c_error_code; |
| } |
| |
| i2c_dma_transfer_error_callback(i2c, error_code, len); |
| } |
| } |
| |
| /* |
| * After an RX operation, we need to disable DMA RX mode. |
| */ |
| static void i2c_dma_receive_callback(void *callback_context, uint32_t len, |
| int error_code) |
| { |
| qm_i2c_status_t status; |
| |
| qm_i2c_t i2c = ((i2c_dma_context_t *)callback_context)->i2c; |
| const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| |
| if ((error_code == 0) && (i2c_dma_context[i2c].i2c_error_code == 0)) { |
| /* Disable DMA receive */ |
| QM_I2C[i2c]->ic_dma_cr &= ~QM_I2C_IC_DMA_CR_RX_ENABLE; |
| i2c_dma_context[i2c].ongoing_dma_rx_operation = false; |
| |
| /* Let's disable the I2C controller if we are done. */ |
| if (transfer->stop == true) { |
| controller_disable(i2c); |
| } |
| |
| /* If the user has provided a callback, let's call it. */ |
| if (transfer->callback != NULL) { |
| qm_i2c_get_status(i2c, &status); |
| transfer->callback(transfer->callback_data, error_code, |
| status, len); |
| } |
| } else { |
| /* |
| * Only call the error callback on RX error. |
| * Arbitration loss errors are handled on the TX callback. |
| */ |
| if (error_code != 0) { |
| i2c_dma_transfer_error_callback(i2c, error_code, len); |
| } |
| } |
| } |
| |
| /* |
| * Effectively starts a previously configured read operation. |
| * For doing this, 2 DMA channels are needed, one for writting READ commands to |
| * the I2C controller and the other to get the read data from the I2C controller |
| * to memory. Thus, a TX operation is also needed in order to perform an RX. |
| */ |
| static int i2c_start_dma_read(const qm_i2c_t i2c) |
| { |
| int rc = 0; |
| |
| /* Enable DMA transmit and receive. */ |
| QM_I2C[i2c]->ic_dma_cr = |
| QM_I2C_IC_DMA_CR_RX_ENABLE | QM_I2C_IC_DMA_CR_TX_ENABLE; |
| |
| /* Enable controller. */ |
| controller_enable(i2c); |
| |
| /* A RX operation need to read and write. */ |
| i2c_dma_context[i2c].ongoing_dma_rx_operation = true; |
| i2c_dma_context[i2c].ongoing_dma_tx_operation = true; |
| /* Configure DMA TX for writing READ commands. */ |
| rc = qm_dma_transfer_set_config( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_tx_channel_id, |
| &(i2c_dma_context[i2c].dma_cmd_transfer_config)); |
| if (rc == 0) { |
| /* Configure DMA RX. */ |
| rc = qm_dma_transfer_set_config( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_rx_channel_id, |
| &(i2c_dma_context[i2c].dma_rx_transfer_config)); |
| if (rc == 0) { |
| /* Start both transfers "at once". */ |
| rc = qm_dma_transfer_start( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_tx_channel_id); |
| if (rc == 0) { |
| rc = qm_dma_transfer_start( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_rx_channel_id); |
| } |
| } |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * Configures given DMA channel with the appropriate width and |
| * length and the right handshaking interface and callback depending on the |
| * direction. |
| */ |
| int qm_i2c_dma_channel_config(const qm_i2c_t i2c, |
| const qm_dma_t dma_controller_id, |
| const qm_dma_channel_id_t channel_id, |
| const qm_dma_channel_direction_t direction) |
| { |
| qm_dma_channel_config_t dma_channel_config = {0}; |
| int rc = 0; |
| |
| /* Test input values. */ |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL); |
| QM_CHECK(dma_controller_id < QM_DMA_NUM, -EINVAL); |
| QM_CHECK(direction <= QM_DMA_PERIPHERAL_TO_MEMORY, -EINVAL); |
| QM_CHECK(direction >= QM_DMA_MEMORY_TO_PERIPHERAL, -EINVAL); |
| |
| /* Set DMA channel configuration. */ |
| dma_channel_config.handshake_interface = |
| i2c_dma_interfaces[i2c][direction]; |
| dma_channel_config.handshake_polarity = QM_DMA_HANDSHAKE_POLARITY_HIGH; |
| dma_channel_config.channel_direction = direction; |
| dma_channel_config.source_transfer_width = QM_DMA_TRANS_WIDTH_8; |
| dma_channel_config.destination_transfer_width = QM_DMA_TRANS_WIDTH_8; |
| /* Burst length is set to half the FIFO for performance */ |
| dma_channel_config.source_burst_length = QM_DMA_BURST_TRANS_LENGTH_8; |
| dma_channel_config.destination_burst_length = |
| QM_DMA_BURST_TRANS_LENGTH_4; |
| dma_channel_config.client_callback = i2c_dma_callbacks[direction]; |
| dma_channel_config.transfer_type = QM_DMA_TYPE_SINGLE; |
| |
| /* Hold the channel IDs and controller ID in the DMA context. */ |
| if (direction == QM_DMA_PERIPHERAL_TO_MEMORY) { |
| i2c_dma_context[i2c].dma_rx_channel_id = channel_id; |
| } else { |
| i2c_dma_context[i2c].dma_tx_channel_id = channel_id; |
| } |
| i2c_dma_context[i2c].dma_controller_id = dma_controller_id; |
| i2c_dma_context[i2c].i2c = i2c; |
| dma_channel_config.callback_context = &i2c_dma_context[i2c]; |
| |
| /* Configure DMA channel. */ |
| rc = qm_dma_channel_set_config(dma_controller_id, channel_id, |
| &dma_channel_config); |
| |
| return rc; |
| } |
| |
| /* |
| * Setups and starts a DMA transaction, wether it's read, write or combined one. |
| * In case of combined transaction, it sets up both operations and starts the |
| * write one; the read operation will be started in the read operation |
| * callback. |
| */ |
| int qm_i2c_master_dma_transfer(const qm_i2c_t i2c, |
| qm_i2c_transfer_t *const xfer, |
| const uint16_t slave_addr) |
| { |
| int rc = 0; |
| uint32_t i; |
| |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(NULL != xfer, -EINVAL); |
| QM_CHECK(0 < xfer->tx_len ? xfer->tx != NULL : 1, -EINVAL); |
| QM_CHECK(0 < xfer->rx_len ? xfer->rx != NULL : 1, -EINVAL); |
| QM_CHECK(0 == xfer->rx_len ? xfer->tx_len != 0 : 1, -EINVAL); |
| QM_CHECK(slave_addr <= QM_I2C_IC_TAR_MASK, -EINVAL); |
| |
| /* Disable all IRQs but the TX abort one. */ |
| QM_I2C[i2c]->ic_intr_mask = QM_I2C_IC_INTR_MASK_TX_ABORT; |
| |
| /* Write slave address to TAR. */ |
| QM_I2C[i2c]->ic_tar &= ~QM_I2C_IC_TAR_MASK; |
| QM_I2C[i2c]->ic_tar |= slave_addr; |
| |
| i2c_read_cmd_send[i2c] = xfer->rx_len; |
| i2c_transfer[i2c] = xfer; |
| |
| /* Set DMA TX and RX watermark levels. */ |
| QM_I2C[i2c]->ic_dma_tdlr = (QM_I2C_FIFO_SIZE / 8); |
| /* RDLR value is desired watermark-1, according to I2C datasheet section |
| 3.17.7 */ |
| QM_I2C[i2c]->ic_dma_rdlr = (QM_I2C_FIFO_SIZE / 2) - 1; |
| |
| i2c_dma_context[i2c].i2c_error_code = 0; |
| |
| /* Setup RX if something to receive. */ |
| if (xfer->rx_len > 0) { |
| i2c_dma_context[i2c].dma_rx_transfer_config.block_size = |
| xfer->rx_len; |
| i2c_dma_context[i2c].dma_rx_transfer_config.source_address = |
| (uint32_t *)&(QM_I2C[i2c]->ic_data_cmd); |
| i2c_dma_context[i2c] |
| .dma_rx_transfer_config.destination_address = |
| (uint32_t *)(xfer->rx); |
| |
| /* |
| * For receiving, READ commands need to be written, a TX |
| * transfer is needed for writting them. |
| */ |
| i2c_dma_context[i2c].dma_cmd_transfer_config.block_size = |
| xfer->rx_len; |
| i2c_dma_context[i2c].dma_cmd_transfer_config.source_address = |
| (uint32_t *)(xfer->rx); |
| /* |
| * RX buffer will be filled with READ commands and use it as |
| * source for the READ command write operation. As READ commands |
| * are written, data will be read overwriting the already |
| * written READ commands. |
| */ |
| for ( |
| i = 0; |
| i < i2c_dma_context[i2c].dma_cmd_transfer_config.block_size; |
| i++) { |
| ((uint8_t *)xfer->rx)[i] = |
| DATA_COMMAND_READ_COMMAND_BYTE; |
| } |
| /* |
| * The STOP condition will be issued on the last READ command. |
| */ |
| if (xfer->stop) { |
| ((uint8_t *)xfer |
| ->rx)[(i2c_dma_context[i2c] |
| .dma_cmd_transfer_config.block_size - |
| 1)] |= DATA_COMMAND_STOP_BIT_BYTE; |
| } |
| /* Only the second byte of IC_DATA_CMD register is written. */ |
| i2c_dma_context[i2c] |
| .dma_cmd_transfer_config.destination_address = |
| (uint32_t *)(((uint32_t) & (QM_I2C[i2c]->ic_data_cmd)) + 1); |
| |
| /* |
| * Start the RX operation in case of RX transaction only. If TX |
| * is specified, it's a combined transaction and RX will start |
| * once TX is done. |
| */ |
| if (xfer->tx_len == 0) { |
| rc = i2c_start_dma_read(i2c); |
| } |
| } |
| |
| /* Setup TX if something to transmit. */ |
| if (xfer->tx_len > 0) { |
| /* |
| * Last byte is handled manually as it may need to be sent with |
| * a STOP condition. |
| */ |
| i2c_dma_context[i2c].dma_tx_transfer_config.block_size = |
| xfer->tx_len - 1; |
| i2c_dma_context[i2c].dma_tx_transfer_config.source_address = |
| (uint32_t *)xfer->tx; |
| i2c_dma_context[i2c] |
| .dma_tx_transfer_config.destination_address = |
| (uint32_t *)&(QM_I2C[i2c]->ic_data_cmd); |
| |
| /* Enable DMA transmit. */ |
| QM_I2C[i2c]->ic_dma_cr = QM_I2C_IC_DMA_CR_TX_ENABLE; |
| |
| /* Enable controller. */ |
| controller_enable(i2c); |
| |
| /* Setup the DMA transfer. */ |
| rc = qm_dma_transfer_set_config( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_tx_channel_id, |
| &(i2c_dma_context[i2c].dma_tx_transfer_config)); |
| if (rc == 0) { |
| /* Mark the TX operation as ongoing. */ |
| i2c_dma_context[i2c].ongoing_dma_rx_operation = false; |
| i2c_dma_context[i2c].ongoing_dma_tx_operation = true; |
| rc = qm_dma_transfer_start( |
| i2c_dma_context[i2c].dma_controller_id, |
| i2c_dma_context[i2c].dma_tx_channel_id); |
| } |
| } |
| |
| return rc; |
| } |
| |
| #if (ENABLE_RESTORE_CONTEXT) |
| int qm_i2c_save_context(const qm_i2c_t i2c, qm_i2c_context_t *const ctx) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(ctx != NULL, -EINVAL); |
| |
| qm_i2c_reg_t *const regs = QM_I2C[i2c]; |
| |
| ctx->con = regs->ic_con; |
| ctx->sar = regs->ic_sar; |
| ctx->ss_scl_hcnt = regs->ic_ss_scl_hcnt; |
| ctx->ss_scl_lcnt = regs->ic_ss_scl_lcnt; |
| ctx->fs_scl_hcnt = regs->ic_fs_scl_hcnt; |
| ctx->fs_scl_lcnt = regs->ic_fs_scl_lcnt; |
| ctx->fs_spklen = regs->ic_fs_spklen; |
| ctx->ic_intr_mask = regs->ic_intr_mask; |
| ctx->enable = regs->ic_enable; |
| ctx->rx_tl = regs->ic_rx_tl; |
| ctx->tx_tl = regs->ic_tx_tl; |
| |
| return 0; |
| } |
| |
| int qm_i2c_restore_context(const qm_i2c_t i2c, |
| const qm_i2c_context_t *const ctx) |
| { |
| QM_CHECK(i2c < QM_I2C_NUM, -EINVAL); |
| QM_CHECK(ctx != NULL, -EINVAL); |
| |
| qm_i2c_reg_t *const regs = QM_I2C[i2c]; |
| |
| regs->ic_con = ctx->con; |
| regs->ic_sar = ctx->sar; |
| regs->ic_ss_scl_hcnt = ctx->ss_scl_hcnt; |
| regs->ic_ss_scl_lcnt = ctx->ss_scl_lcnt; |
| regs->ic_fs_scl_hcnt = ctx->fs_scl_hcnt; |
| regs->ic_fs_scl_lcnt = ctx->fs_scl_lcnt; |
| regs->ic_fs_spklen = ctx->fs_spklen; |
| regs->ic_intr_mask = ctx->ic_intr_mask; |
| regs->ic_enable = ctx->enable; |
| regs->ic_rx_tl = ctx->rx_tl; |
| regs->ic_tx_tl = ctx->tx_tl; |
| |
| return 0; |
| } |
| #else |
| int qm_i2c_save_context(const qm_i2c_t i2c, qm_i2c_context_t *const ctx) |
| { |
| (void)i2c; |
| (void)ctx; |
| |
| return 0; |
| } |
| |
| int qm_i2c_restore_context(const qm_i2c_t i2c, |
| const qm_i2c_context_t *const ctx) |
| { |
| (void)i2c; |
| (void)ctx; |
| |
| return 0; |
| } |
| #endif /* ENABLE_RESTORE_CONTEXT */ |