| /* |
| * 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_ss_i2c.h" |
| #include "clk.h" |
| |
| #define TX_TL (2) |
| #define RX_TL (5) |
| |
| /* number of retries before giving up on disabling the controller */ |
| #define I2C_POLL_COUNT (1000000) |
| #define I2C_POLL_MICROSECOND (1) |
| |
| static uint32_t i2c_base[QM_SS_I2C_NUM] = {QM_SS_I2C_0_BASE, QM_SS_I2C_1_BASE}; |
| static volatile const qm_ss_i2c_transfer_t *i2c_transfer[QM_SS_I2C_NUM]; |
| static volatile uint32_t i2c_write_pos[QM_SS_I2C_NUM], |
| i2c_read_pos[QM_SS_I2C_NUM], i2c_read_cmd_send[QM_SS_I2C_NUM]; |
| |
| static void controller_enable(const qm_ss_i2c_t i2c); |
| static int controller_disable(const qm_ss_i2c_t i2c); |
| |
| static uint32_t |
| i2c_fill_tx_fifo(const qm_i2c_t i2c, |
| const volatile qm_ss_i2c_transfer_t *const transfer, |
| uint32_t controller) |
| { |
| uint32_t data_cmd = 0, count_tx = (QM_SS_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 */ |
| data_cmd = QM_SS_I2C_DATA_CMD_PUSH | |
| i2c_transfer[i2c]->tx[i2c_write_pos[i2c]]; |
| |
| /* if transfer is a combined transfer, only |
| * send stop at |
| * end of the transfer sequence */ |
| if (i2c_transfer[i2c]->stop && (read_buffer_remaining == 0) && |
| (write_buffer_remaining == 0)) { |
| |
| data_cmd |= QM_SS_I2C_DATA_CMD_STOP; |
| } |
| |
| /* write data */ |
| QM_SS_I2C_WRITE_DATA_CMD(controller, data_cmd); |
| i2c_write_pos[i2c]++; |
| |
| /* TX_EMPTY INTR is autocleared when the buffer |
| * levels goes above the threshold |
| */ |
| } |
| |
| return write_buffer_remaining; |
| } |
| |
| static void handle_i2c_error_interrupt(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| qm_ss_i2c_status_t status = 0; |
| int rc = -EIO; |
| |
| /* Check for TX_OVER error */ |
| if (QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_OVER) { |
| |
| status = QM_SS_I2C_TX_OVER; |
| |
| /* Clear interrupt */ |
| QM_SS_I2C_CLEAR_TX_OVER_INTR(controller); |
| } |
| |
| /* Check for RX_UNDER error */ |
| if (QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_RX_UNDER) { |
| |
| status = QM_SS_I2C_RX_UNDER; |
| |
| /* Clear interrupt */ |
| QM_SS_I2C_CLEAR_RX_UNDER_INTR(controller); |
| } |
| |
| /* Check for RX_OVER error */ |
| if (QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_RX_OVER) { |
| |
| status = QM_SS_I2C_RX_OVER; |
| |
| /* Clear interrupt */ |
| QM_SS_I2C_CLEAR_RX_OVER_INTR(controller); |
| } |
| |
| /* Check for TX_ABRT error */ |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT)) { |
| |
| QM_ASSERT(!(QM_SS_I2C_READ_TX_ABRT_SOURCE(controller) & |
| QM_SS_I2C_TX_ABRT_SBYTE_NORSTRT)); |
| |
| status = QM_SS_I2C_TX_ABORT; |
| |
| status |= (QM_SS_I2C_READ_TX_ABRT_SOURCE(controller) & |
| QM_SS_I2C_TX_ABRT_SOURCE_ALL_MASK); |
| |
| /* Clear interrupt */ |
| QM_SS_I2C_CLEAR_TX_ABRT_INTR(controller); |
| |
| rc = (status & QM_SS_I2C_TX_ABRT_USER_ABRT) ? -ECANCELED : -EIO; |
| } |
| |
| /* Mask interrupts */ |
| QM_SS_I2C_MASK_ALL_INTERRUPTS(controller); |
| |
| controller_disable(i2c); |
| if (i2c_transfer[i2c]->callback) { |
| i2c_transfer[i2c]->callback(i2c_transfer[i2c]->callback_data, |
| rc, status, 0); |
| } |
| } |
| |
| static void handle_i2c_rx_avail_interrupt(const qm_ss_i2c_t i2c) |
| { |
| const volatile qm_ss_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| uint32_t controller = i2c_base[i2c]; |
| uint32_t read_buffer_remaining = transfer->rx_len - i2c_read_pos[i2c]; |
| |
| /* RX read from buffer */ |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_RX_FULL)) { |
| |
| while (read_buffer_remaining && |
| (QM_SS_I2C_READ_RXFLR(controller))) { |
| QM_SS_I2C_READ_RX_FIFO(controller); |
| /* IC_DATA_CMD[7:0] contains received data */ |
| i2c_transfer[i2c]->rx[i2c_read_pos[i2c]] = |
| QM_SS_I2C_READ_DATA_CMD(controller); |
| read_buffer_remaining--; |
| i2c_read_pos[i2c]++; |
| |
| if (read_buffer_remaining == 0) { |
| /* mask rx full interrupt if transfer |
| * complete |
| */ |
| QM_SS_I2C_MASK_INTERRUPT( |
| controller, |
| QM_SS_I2C_INTR_MASK_RX_FULL | |
| QM_SS_I2C_INTR_MASK_TX_EMPTY); |
| |
| if (i2c_transfer[i2c]->stop) { |
| controller_disable(i2c); |
| } |
| |
| if (i2c_transfer[i2c]->callback) { |
| i2c_transfer[i2c]->callback( |
| i2c_transfer[i2c]->callback_data, 0, |
| QM_SS_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. |
| */ |
| QM_SS_I2C_CLEAR_RX_TL(controller); |
| QM_SS_I2C_WRITE_RX_TL(controller, |
| (read_buffer_remaining - 1)); |
| } |
| |
| /* RX_FULL INTR is autocleared when the buffer |
| * levels goes below the threshold |
| */ |
| } |
| } |
| |
| static void handle_i2c_tx_req_interrupt(const qm_ss_i2c_t i2c) |
| { |
| const volatile qm_ss_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| uint32_t controller = i2c_base[i2c]; |
| 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; |
| |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_EMPTY)) { |
| |
| if ((QM_SS_I2C_READ_STATUS(controller) & |
| QM_SS_I2C_STATUS_TFE) && |
| (i2c_transfer[i2c]->tx != NULL) && |
| (write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| |
| QM_SS_I2C_MASK_INTERRUPT(controller, |
| QM_SS_I2C_INTR_MASK_TX_EMPTY); |
| |
| /* if this is not a combined |
| * transaction, disable the controller now |
| */ |
| if (i2c_transfer[i2c]->stop) { |
| controller_disable(i2c); |
| } |
| |
| /* callback */ |
| if (i2c_transfer[i2c]->callback) { |
| i2c_transfer[i2c]->callback( |
| i2c_transfer[i2c]->callback_data, 0, |
| QM_SS_I2C_IDLE, i2c_write_pos[i2c]); |
| } |
| } |
| |
| write_buffer_remaining = |
| i2c_fill_tx_fifo(i2c, i2c_transfer[i2c], 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(QM_SS_I2C_READ_RXFLR(controller) <= missing_bytes); |
| |
| /* count_tx is the remaining size in the fifo */ |
| count_tx = |
| QM_SS_I2C_FIFO_SIZE - QM_SS_I2C_READ_TXFLR(controller); |
| |
| 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 (i2c_transfer[i2c]->stop && |
| (i2c_read_cmd_send[i2c] == 0)) { |
| |
| QM_SS_I2C_WRITE_DATA_CMD( |
| controller, (QM_SS_I2C_DATA_CMD_CMD | |
| QM_SS_I2C_DATA_CMD_PUSH | |
| QM_SS_I2C_DATA_CMD_STOP)); |
| |
| } else { |
| |
| QM_SS_I2C_WRITE_DATA_CMD( |
| controller, (QM_SS_I2C_DATA_CMD_CMD | |
| QM_SS_I2C_DATA_CMD_PUSH)); |
| } |
| } |
| |
| /* generate a tx_empty interrupt when tx fifo is fully |
| * empty */ |
| if ((write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| QM_SS_I2C_CLEAR_TX_TL(controller); |
| } |
| } |
| } |
| |
| static void handle_i2c_stop_det_interrupt(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & QM_SS_I2C_INTR_STAT_STOP)) { |
| |
| /* Clear interrupt */ |
| QM_SS_I2C_CLEAR_STOP_DET_INTR(controller); |
| |
| if (i2c_transfer[i2c]->callback) { |
| i2c_transfer[i2c]->callback( |
| i2c_transfer[i2c]->callback_data, 0, QM_SS_I2C_IDLE, |
| 0); |
| } |
| } |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_0_error_isr) |
| { |
| handle_i2c_error_interrupt(QM_SS_I2C_0); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_0_rx_avail_isr) |
| { |
| handle_i2c_rx_avail_interrupt(QM_SS_I2C_0); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_0_tx_req_isr) |
| { |
| handle_i2c_tx_req_interrupt(QM_SS_I2C_0); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_0_stop_det_isr) |
| { |
| handle_i2c_stop_det_interrupt(QM_SS_I2C_0); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_1_error_isr) |
| { |
| handle_i2c_error_interrupt(QM_SS_I2C_1); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_1_rx_avail_isr) |
| { |
| handle_i2c_rx_avail_interrupt(QM_SS_I2C_1); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_1_tx_req_isr) |
| { |
| handle_i2c_tx_req_interrupt(QM_SS_I2C_1); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_1_stop_det_isr) |
| { |
| handle_i2c_stop_det_interrupt(QM_SS_I2C_1); |
| } |
| |
| static uint32_t get_lo_cnt(uint32_t lo_time_ns) |
| { |
| return (((clk_sys_get_ticks_per_us() * lo_time_ns) / 1000) - 1); |
| } |
| |
| static uint32_t get_hi_cnt(qm_ss_i2c_t i2c, uint32_t hi_time_ns) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| |
| return (((clk_sys_get_ticks_per_us() * hi_time_ns) / 1000) - 7 - |
| (QM_SS_I2C_READ_SPKLEN(controller))); |
| } |
| |
| int qm_ss_i2c_set_config(const qm_ss_i2c_t i2c, |
| const qm_ss_i2c_config_t *const cfg) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(cfg != NULL, -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c], lcnt = 0, hcnt = 0, min_lcnt = 0, |
| lcnt_diff = 0; |
| |
| QM_SS_I2C_WRITE_CLKEN(controller); |
| |
| /* mask all interrupts */ |
| QM_SS_I2C_MASK_ALL_INTERRUPTS(controller); |
| |
| /* disable controller */ |
| if (controller_disable(i2c)) { |
| return -EBUSY; |
| } |
| |
| /* Set mode */ |
| QM_SS_I2C_WRITE_RESTART_EN(controller); |
| QM_SS_I2C_WRITE_ADDRESS_MODE(controller, cfg->address_mode); |
| |
| /* |
| * 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. |
| */ |
| |
| QM_SS_I2C_CLEAR_SPKLEN(controller); |
| QM_SS_I2C_CLEAR_SPEED(controller); |
| |
| switch (cfg->speed) { |
| case QM_SS_I2C_SPEED_STD: |
| |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_SS); |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_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_SS_I2C_SPEED_FAST: |
| |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_FS); |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_SPK_LEN_FS); |
| |
| 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; |
| |
| #if HAS_SS_I2C_FAST_PLUS_SPEED |
| case QM_SS_I2C_SPEED_FAST_PLUS: |
| |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_FSP); |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_SPK_LEN_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; |
| #endif /* HAS_SS_I2C_FAST_PLUS_SPEED */ |
| } |
| |
| if (hcnt > QM_SS_I2C_IC_HCNT_MAX || hcnt < QM_SS_I2C_IC_HCNT_MIN) { |
| return -EINVAL; |
| } |
| |
| if (lcnt > QM_SS_I2C_IC_LCNT_MAX || lcnt < QM_SS_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_SS_I2C_SPEED_STD == cfg->speed) { |
| QM_SS_I2C_CLEAR_SS_SCL_HCNT(controller); |
| QM_SS_I2C_CLEAR_SS_SCL_LCNT(controller); |
| |
| QM_SS_I2C_WRITE_SS_SCL_HCNT(controller, hcnt); |
| QM_SS_I2C_WRITE_SS_SCL_LCNT(controller, lcnt); |
| |
| } else { /* Fast and fast plus modes */ |
| QM_SS_I2C_CLEAR_FS_SCL_HCNT(controller); |
| QM_SS_I2C_CLEAR_FS_SCL_LCNT(controller); |
| |
| QM_SS_I2C_WRITE_FS_SCL_HCNT(controller, hcnt); |
| QM_SS_I2C_WRITE_FS_SCL_LCNT(controller, lcnt); |
| } |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_set_speed(const qm_ss_i2c_t i2c, const qm_ss_i2c_speed_t speed, |
| const uint16_t lo_cnt, const uint16_t hi_cnt) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(hi_cnt < QM_SS_I2C_IC_HCNT_MAX && |
| hi_cnt > QM_SS_I2C_IC_HCNT_MIN, |
| -EINVAL); |
| QM_CHECK(lo_cnt < QM_SS_I2C_IC_LCNT_MAX && |
| lo_cnt > QM_SS_I2C_IC_LCNT_MIN, |
| -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c]; |
| |
| QM_SS_I2C_CLEAR_SPKLEN(controller); |
| QM_SS_I2C_CLEAR_SPEED(controller); |
| |
| switch (speed) { |
| case QM_SS_I2C_SPEED_STD: |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_SPK_LEN_SS); |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_SS); |
| |
| QM_SS_I2C_CLEAR_SS_SCL_HCNT(controller); |
| QM_SS_I2C_CLEAR_SS_SCL_LCNT(controller); |
| |
| QM_SS_I2C_WRITE_SS_SCL_HCNT(controller, hi_cnt); |
| QM_SS_I2C_WRITE_SS_SCL_LCNT(controller, lo_cnt); |
| break; |
| |
| case QM_SS_I2C_SPEED_FAST: |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_SPK_LEN_FS); |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_FS); |
| |
| QM_SS_I2C_CLEAR_FS_SCL_HCNT(controller); |
| QM_SS_I2C_CLEAR_FS_SCL_LCNT(controller); |
| |
| QM_SS_I2C_WRITE_FS_SCL_HCNT(controller, hi_cnt); |
| QM_SS_I2C_WRITE_FS_SCL_LCNT(controller, lo_cnt); |
| break; |
| #if HAS_SS_I2C_FAST_PLUS_SPEED |
| case QM_SS_I2C_SPEED_FAST_PLUS: |
| QM_SS_I2C_WRITE_SPKLEN(controller, QM_SS_I2C_SPK_LEN_FSP); |
| QM_SS_I2C_WRITE_SPEED(controller, QM_SS_I2C_CON_SPEED_FSP); |
| |
| QM_SS_I2C_CLEAR_FS_SCL_HCNT(controller); |
| QM_SS_I2C_CLEAR_FS_SCL_LCNT(controller); |
| |
| QM_SS_I2C_WRITE_FS_SCL_HCNT(controller, hi_cnt); |
| QM_SS_I2C_WRITE_FS_SCL_LCNT(controller, lo_cnt); |
| break; |
| #endif /* HAS_SS_I2C_FAST_PLUS_SPEED */ |
| } |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_get_status(const qm_ss_i2c_t i2c, |
| qm_ss_i2c_status_t *const status) |
| { |
| QM_CHECK(status != NULL, -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c]; |
| |
| *status = QM_SS_I2C_IDLE; |
| |
| /* check if slave or master are active */ |
| if (QM_SS_I2C_READ_STATUS(controller) & QM_SS_I2C_STATUS_BUSY_MASK) { |
| *status |= QM_SS_I2C_BUSY; |
| } |
| |
| /* check for abort status */ |
| *status |= (QM_SS_I2C_READ_TX_ABRT_SOURCE(controller) & |
| QM_SS_I2C_TX_ABRT_SOURCE_ALL_MASK); |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_master_write(const qm_ss_i2c_t i2c, const uint16_t slave_addr, |
| const uint8_t *const data, uint32_t len, |
| const bool stop, qm_ss_i2c_status_t *const status) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| uint8_t *d = (uint8_t *)data; |
| uint32_t controller = i2c_base[i2c], data_cmd = 0; |
| int ret = 0; |
| |
| /* write slave address to TAR */ |
| QM_SS_I2C_CLEAR_TAR(controller); |
| QM_SS_I2C_WRITE_TAR(controller, slave_addr); |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| |
| /* wait if FIFO is full */ |
| while (!(QM_SS_I2C_READ_STATUS(controller) & |
| QM_SS_I2C_STATUS_TFNF)) |
| ; |
| |
| /* write command -IC_DATA_CMD[8] = 0 */ |
| /* fill IC_DATA_CMD[7:0] with the data */ |
| data_cmd = *d; |
| data_cmd |= QM_SS_I2C_DATA_CMD_PUSH; |
| |
| /* send stop after last byte */ |
| if (len == 0 && stop) { |
| data_cmd |= QM_SS_I2C_DATA_CMD_STOP; |
| } |
| |
| QM_SS_I2C_WRITE_DATA_CMD(controller, data_cmd); |
| d++; |
| } |
| |
| /* this is a blocking call, wait until FIFO is empty or tx abrt |
| * error */ |
| while (!(QM_SS_I2C_READ_STATUS(controller) & QM_SS_I2C_STATUS_TFE)) |
| ; |
| |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT)) { |
| ret = -EIO; |
| } |
| |
| /* disable controller */ |
| if (true == stop) { |
| if (controller_disable(i2c)) { |
| ret = -EBUSY; |
| } |
| } |
| |
| if (status != NULL) { |
| qm_ss_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. |
| */ |
| QM_SS_I2C_CLEAR_TX_ABRT_INTR(controller); |
| |
| return ret; |
| } |
| |
| int qm_ss_i2c_master_read(const qm_ss_i2c_t i2c, const uint16_t slave_addr, |
| uint8_t *const data, uint32_t len, const bool stop, |
| qm_ss_i2c_status_t *const status) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c], |
| data_cmd = QM_SS_I2C_DATA_CMD_CMD | QM_SS_I2C_DATA_CMD_PUSH; |
| uint8_t *d = (uint8_t *)data; |
| int ret = 0; |
| |
| /* write slave address to TAR */ |
| QM_SS_I2C_CLEAR_TAR(controller); |
| QM_SS_I2C_WRITE_TAR(controller, slave_addr); |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| if (len == 0 && stop) { |
| data_cmd |= QM_SS_I2C_DATA_CMD_STOP; |
| } |
| |
| QM_SS_I2C_WRITE_DATA_CMD(controller, data_cmd); |
| |
| /* wait if rx fifo is empty, break if tx empty and |
| * error*/ |
| while (!(QM_SS_I2C_READ_STATUS(controller) & |
| QM_SS_I2C_STATUS_RFNE)) { |
| if (QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT) { |
| break; |
| } |
| } |
| |
| if ((QM_SS_I2C_READ_INTR_STAT(controller) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT)) { |
| ret = -EIO; |
| break; |
| } |
| |
| QM_SS_I2C_READ_RX_FIFO(controller); |
| |
| /* wait until rx fifo is empty, indicating pop is complete*/ |
| while ( |
| (QM_SS_I2C_READ_STATUS(controller) & QM_SS_I2C_STATUS_RFNE)) |
| ; |
| |
| /* IC_DATA_CMD[7:0] contains received data */ |
| *d = QM_SS_I2C_READ_DATA_CMD(controller); |
| d++; |
| } |
| |
| /* disable controller */ |
| if (true == stop) { |
| if (controller_disable(i2c)) { |
| ret = -EBUSY; |
| } |
| } |
| |
| if (status != NULL) { |
| qm_ss_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. |
| */ |
| QM_SS_I2C_CLEAR_TX_ABRT_INTR(controller); |
| |
| return ret; |
| } |
| |
| int qm_ss_i2c_master_irq_transfer(const qm_ss_i2c_t i2c, |
| const qm_ss_i2c_transfer_t *const xfer, |
| const uint16_t slave_addr) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(NULL != xfer, -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c]; |
| |
| /* write slave address to TAR */ |
| QM_SS_I2C_CLEAR_TAR(controller); |
| QM_SS_I2C_WRITE_TAR(controller, 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 */ |
| 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. |
| */ |
| QM_SS_I2C_CLEAR_RX_TL(controller); |
| QM_SS_I2C_CLEAR_TX_TL(controller); |
| |
| QM_SS_I2C_WRITE_RX_TL(controller, (xfer->rx_len - 1)); |
| QM_SS_I2C_WRITE_TX_TL(controller, TX_TL); |
| } else { |
| QM_SS_I2C_CLEAR_RX_TL(controller); |
| QM_SS_I2C_CLEAR_TX_TL(controller); |
| |
| QM_SS_I2C_WRITE_RX_TL(controller, RX_TL); |
| QM_SS_I2C_WRITE_TX_TL(controller, TX_TL); |
| } |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| /* Start filling tx fifo. */ |
| i2c_fill_tx_fifo(i2c, xfer, controller); |
| |
| /* unmask interrupts */ |
| QM_SS_I2C_UNMASK_INTERRUPTS(controller); |
| |
| return 0; |
| } |
| |
| static void controller_enable(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| if (!(QM_SS_I2C_READ_ENABLE_STATUS(controller) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN)) { |
| /* enable controller */ |
| QM_SS_I2C_ENABLE(controller); |
| /* wait until controller is enabled */ |
| while (!(QM_SS_I2C_READ_ENABLE_STATUS(controller) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN)) |
| ; |
| } |
| |
| /* Clear all interruption flags */ |
| QM_SS_I2C_CLEAR_ALL_INTR(controller); |
| } |
| |
| static int controller_disable(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| int poll_count = I2C_POLL_COUNT; |
| |
| /* disable controller */ |
| QM_SS_I2C_DISABLE(controller); |
| |
| /* wait until controller is disabled */ |
| while ((QM_SS_I2C_READ_ENABLE_STATUS(controller) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN) && |
| poll_count--) { |
| clk_sys_udelay(I2C_POLL_MICROSECOND); |
| } |
| |
| /* returns 0 if ok, meaning controller is disabled */ |
| return (QM_SS_I2C_READ_ENABLE_STATUS(controller) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN); |
| } |
| |
| int qm_ss_i2c_irq_transfer_terminate(const qm_ss_i2c_t i2c) |
| { |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| |
| uint32_t controller = i2c_base[i2c]; |
| |
| /* 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_SS_I2C_ABORT(controller); |
| |
| return 0; |
| } |
| |
| #if (ENABLE_RESTORE_CONTEXT) |
| int qm_ss_i2c_save_context(const qm_ss_i2c_t i2c, |
| qm_ss_i2c_context_t *const ctx) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(ctx != NULL, -EINVAL); |
| |
| ctx->i2c_fs_scl_cnt = |
| __builtin_arc_lr(controller + QM_SS_I2C_FS_SCL_CNT); |
| ctx->i2c_ss_scl_cnt = |
| __builtin_arc_lr(controller + QM_SS_I2C_SS_SCL_CNT); |
| ctx->i2c_con = __builtin_arc_lr(controller + QM_SS_I2C_CON); |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_restore_context(const qm_ss_i2c_t i2c, |
| const qm_ss_i2c_context_t *const ctx) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(ctx != NULL, -EINVAL); |
| |
| __builtin_arc_sr(ctx->i2c_fs_scl_cnt, |
| controller + QM_SS_I2C_FS_SCL_CNT); |
| __builtin_arc_sr(ctx->i2c_ss_scl_cnt, |
| controller + QM_SS_I2C_SS_SCL_CNT); |
| __builtin_arc_sr(ctx->i2c_con, controller + QM_SS_I2C_CON); |
| |
| return 0; |
| } |
| #else |
| int qm_ss_i2c_save_context(const qm_ss_i2c_t i2c, |
| qm_ss_i2c_context_t *const ctx) |
| { |
| (void)i2c; |
| (void)ctx; |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_restore_context(const qm_ss_i2c_t i2c, |
| const qm_ss_i2c_context_t *const ctx) |
| { |
| (void)i2c; |
| (void)ctx; |
| |
| return 0; |
| } |
| #endif /* ENABLE_RESTORE_CONTEXT */ |