| /* |
| * Copyright (c) 2016, 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 SPK_LEN_SS (1) |
| #define SPK_LEN_FS (2) |
| #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) |
| |
| /* |
| * NOTE: There are a number of differences between this Sensor Subsystem I2C |
| * driver and the Lakemont version. The IP is not the same, the |
| * functionality is a subset of the features contained on the Lakemont |
| * version: |
| * 1. Fast Mode Plus is not supported |
| * 2. Slave mode is not supported |
| * |
| * The registers are different and the register set is compressed. |
| * Some noteworthy differences are: |
| * 1. Clock enable is contained in the QM_SS_I2C_CON register |
| * 2. SPKLEN is contained in the QM_SS_I2C_CON register |
| * 3. The high and low count values are contained within a single |
| * register |
| * 4. There is no raw interrupt status register, QM_SS_I2C_INT_STAT |
| * takes its place and is non-maskable |
| * 5. There is a reduced number of TX abrt source status bits |
| * 6. The QM_SS_I2C_DATA_CMD register is different and requires the |
| * strobe bit to be written to indicate a QM_SS_I2C_DATA_CMD |
| * register update. There is a push and pop mechanism for using |
| * the FIFO. |
| */ |
| |
| 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 void qm_ss_i2c_isr_handler(const qm_ss_i2c_t i2c) |
| { |
| const volatile qm_ss_i2c_transfer_t *const transfer = i2c_transfer[i2c]; |
| uint32_t controller = i2c_base[i2c], data_cmd = 0, |
| count_tx = (QM_SS_I2C_FIFO_SIZE - TX_TL); |
| qm_ss_i2c_status_t status = 0; |
| int rc = 0; |
| 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; |
| |
| /* Check for errors */ |
| QM_ASSERT(!(__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_TX_OVER)); |
| QM_ASSERT(!(__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_RX_UNDER)); |
| QM_ASSERT(!(__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_RX_OVER)); |
| |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT)) { |
| QM_ASSERT( |
| !(__builtin_arc_lr(controller + QM_SS_I2C_TX_ABRT_SOURCE) & |
| QM_SS_I2C_TX_ABRT_SBYTE_NORSTRT)); |
| |
| status = |
| (__builtin_arc_lr(controller + QM_SS_I2C_TX_ABRT_SOURCE) & |
| QM_SS_I2C_TX_ABRT_SOURCE_ALL_MASK); |
| |
| /* clear intr */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_CLR_TX_ABRT, |
| controller + QM_SS_I2C_INTR_CLR); |
| |
| /* mask interrupts */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_MASK_ALL, |
| controller + QM_SS_I2C_INTR_MASK); |
| |
| rc = (status & QM_SS_I2C_TX_ABRT_USER_ABRT) ? -ECANCELED : -EIO; |
| |
| controller_disable(i2c); |
| if (i2c_transfer[i2c]->callback) { |
| i2c_transfer[i2c]->callback( |
| i2c_transfer[i2c]->callback_data, rc, status, 0); |
| } |
| } |
| |
| /* RX read from buffer */ |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_RX_FULL)) { |
| |
| while (read_buffer_remaining && |
| (__builtin_arc_lr(controller + QM_SS_I2C_RXFLR))) { |
| __builtin_arc_sr(QM_SS_I2C_DATA_CMD_POP, |
| controller + QM_SS_I2C_DATA_CMD); |
| /* IC_DATA_CMD[7:0] contains received data */ |
| i2c_transfer[i2c]->rx[i2c_read_pos[i2c]] = |
| __builtin_arc_lr(controller + QM_SS_I2C_DATA_CMD); |
| read_buffer_remaining--; |
| i2c_read_pos[i2c]++; |
| |
| if (read_buffer_remaining == 0) { |
| /* mask rx full interrupt if transfer |
| * complete |
| */ |
| QM_SS_REG_AUX_NAND( |
| (controller + QM_SS_I2C_INTR_MASK), |
| 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_REG_AUX_NAND((controller + QM_SS_I2C_TL), |
| QM_SS_I2C_TL_RX_TL_MASK); |
| QM_SS_REG_AUX_OR((controller + QM_SS_I2C_TL), |
| (read_buffer_remaining - 1)); |
| } |
| |
| /* RX_FULL INTR is autocleared when the buffer |
| * levels goes below the threshold |
| */ |
| } |
| |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_TX_EMPTY)) { |
| |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_STATUS) & |
| QM_SS_I2C_STATUS_TFE) && |
| (i2c_transfer[i2c]->tx != NULL) && |
| (write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| |
| QM_SS_REG_AUX_NAND((controller + QM_SS_I2C_INTR_MASK), |
| 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]); |
| } |
| } |
| |
| 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 */ |
| __builtin_arc_sr(data_cmd, |
| controller + QM_SS_I2C_DATA_CMD); |
| i2c_write_pos[i2c]++; |
| |
| /* TX_EMPTY INTR is autocleared when the buffer |
| * levels goes above the threshold |
| */ |
| } |
| |
| /* 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(__builtin_arc_lr(controller + QM_SS_I2C_RXFLR) <= |
| missing_bytes); |
| |
| /* count_tx is the remaining size in the fifo */ |
| count_tx = QM_SS_I2C_FIFO_SIZE - |
| __builtin_arc_lr(controller + QM_SS_I2C_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 (i2c_transfer[i2c]->stop && |
| (i2c_read_cmd_send[i2c] == 0)) { |
| |
| __builtin_arc_sr((QM_SS_I2C_DATA_CMD_CMD | |
| QM_SS_I2C_DATA_CMD_PUSH | |
| QM_SS_I2C_DATA_CMD_STOP), |
| controller + |
| QM_SS_I2C_DATA_CMD); |
| } else { |
| |
| __builtin_arc_sr((QM_SS_I2C_DATA_CMD_CMD | |
| QM_SS_I2C_DATA_CMD_PUSH), |
| controller + |
| QM_SS_I2C_DATA_CMD); |
| } |
| } |
| |
| /* generate a tx_empty interrupt when tx fifo is fully |
| * empty */ |
| if ((write_buffer_remaining == 0) && |
| (read_buffer_remaining == 0)) { |
| QM_SS_REG_AUX_NAND((controller + QM_SS_I2C_TL), |
| QM_SS_I2C_TL_TX_TL_MASK); |
| } |
| } |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_0_isr) |
| { |
| qm_ss_i2c_isr_handler(QM_SS_I2C_0); |
| } |
| |
| QM_ISR_DECLARE(qm_ss_i2c_1_isr) |
| { |
| qm_ss_i2c_isr_handler(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 - |
| ((__builtin_arc_lr(controller + QM_SS_I2C_CON) & |
| QM_SS_I2C_CON_SPKLEN_MASK) >> |
| QM_SS_I2C_CON_SPKLEN_OFFSET)); |
| } |
| |
| int qm_ss_i2c_set_config(const qm_ss_i2c_t i2c, |
| const qm_ss_i2c_config_t *const cfg) |
| { |
| uint32_t controller = i2c_base[i2c], lcnt = 0, hcnt = 0, full_cnt = 0, |
| min_lcnt = 0, lcnt_diff = 0, |
| con = (__builtin_arc_lr(controller + QM_SS_I2C_CON) & |
| QM_SS_I2C_CON_CLK_ENA); |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(cfg != NULL, -EINVAL); |
| |
| /* mask all interrupts */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_MASK_ALL, |
| controller + QM_SS_I2C_INTR_MASK); |
| |
| /* disable controller */ |
| if (controller_disable(i2c)) { |
| return -EBUSY; |
| } |
| |
| /* set mode */ |
| con |= QM_SS_I2C_CON_RESTART_EN | |
| /* set 7/10 bit address mode */ |
| (cfg->address_mode << QM_SS_I2C_CON_IC_10BITADDR_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_SS_I2C_SPEED_STD: |
| |
| con |= QM_SS_I2C_CON_SPEED_SS | |
| SPK_LEN_SS << QM_SS_I2C_CON_SPKLEN_OFFSET; |
| |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| 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: |
| con |= QM_SS_I2C_CON_SPEED_FS | |
| SPK_LEN_FS << QM_SS_I2C_CON_SPKLEN_OFFSET; |
| |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| 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 (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); |
| } |
| |
| full_cnt = (lcnt & 0xFFFF) | |
| (hcnt & 0xFFFF) << QM_SS_I2C_SS_FS_SCL_CNT_HCNT_OFFSET; |
| |
| if (QM_SS_I2C_SPEED_STD == cfg->speed) { |
| __builtin_arc_sr(full_cnt, controller + QM_SS_I2C_SS_SCL_CNT); |
| |
| } else { |
| __builtin_arc_sr(full_cnt, controller + QM_SS_I2C_FS_SCL_CNT); |
| } |
| |
| 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) |
| { |
| uint32_t full_cnt = 0, controller = i2c_base[i2c], |
| con = __builtin_arc_lr(controller + QM_SS_I2C_CON); |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(hi_cnt < QM_SS_I2C_IC_HCNT_MAX && |
| lo_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); |
| |
| con &= ~(QM_SS_I2C_CON_SPEED_MASK | QM_SS_I2C_CON_SPKLEN_MASK); |
| |
| full_cnt = (lo_cnt & QM_SS_I2C_SS_FS_SCL_CNT_16BIT_MASK) | |
| (hi_cnt & QM_SS_I2C_SS_FS_SCL_CNT_16BIT_MASK) |
| << QM_SS_I2C_SS_FS_SCL_CNT_HCNT_OFFSET; |
| |
| switch (speed) { |
| case QM_SS_I2C_SPEED_STD: |
| con |= (QM_SS_I2C_CON_SPEED_SS | |
| (SPK_LEN_SS << QM_SS_I2C_CON_SPKLEN_OFFSET)); |
| __builtin_arc_sr(full_cnt, controller + QM_SS_I2C_SS_SCL_CNT); |
| break; |
| |
| case QM_SS_I2C_SPEED_FAST: |
| con |= (QM_SS_I2C_CON_SPEED_FS | |
| (SPK_LEN_FS << QM_SS_I2C_CON_SPKLEN_OFFSET)); |
| __builtin_arc_sr(full_cnt, controller + QM_SS_I2C_FS_SCL_CNT); |
| break; |
| } |
| |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| return 0; |
| } |
| |
| int qm_ss_i2c_get_status(const qm_ss_i2c_t i2c, |
| qm_ss_i2c_status_t *const status) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| QM_CHECK(status != NULL, -EINVAL); |
| |
| *status = 0; |
| |
| /* check if slave or master are active */ |
| if (__builtin_arc_lr(controller + QM_SS_I2C_STATUS) & |
| QM_SS_I2C_STATUS_BUSY_MASK) { |
| *status |= QM_SS_I2C_BUSY; |
| } |
| |
| /* check for abort status */ |
| *status |= (__builtin_arc_lr(controller + QM_SS_I2C_TX_ABRT_SOURCE) & |
| 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) |
| { |
| uint8_t *d = (uint8_t *)data; |
| uint32_t controller = i2c_base[i2c], |
| con = __builtin_arc_lr(controller + QM_SS_I2C_CON), |
| data_cmd = 0; |
| int ret = 0; |
| |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| /* write slave address to TAR */ |
| con &= ~QM_SS_I2C_CON_TAR_SAR_MASK; |
| con |= (slave_addr & QM_SS_I2C_CON_TAR_SAR_10_BIT_MASK) |
| << QM_SS_I2C_CON_TAR_SAR_OFFSET; |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| |
| /* wait if FIFO is full */ |
| while (!((__builtin_arc_lr(controller + QM_SS_I2C_STATUS)) & |
| 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; |
| } |
| |
| __builtin_arc_sr(data_cmd, controller + QM_SS_I2C_DATA_CMD); |
| d++; |
| } |
| |
| /* this is a blocking call, wait until FIFO is empty or tx abrt |
| * error */ |
| while (!(__builtin_arc_lr(controller + QM_SS_I2C_STATUS) & |
| QM_SS_I2C_STATUS_TFE)) |
| ; |
| |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| 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. |
| */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_CLR_TX_ABRT, |
| controller + QM_SS_I2C_INTR_CLR); |
| |
| 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) |
| { |
| uint32_t controller = i2c_base[i2c], |
| con = __builtin_arc_lr(controller + QM_SS_I2C_CON), |
| data_cmd = QM_SS_I2C_DATA_CMD_CMD | QM_SS_I2C_DATA_CMD_PUSH; |
| uint8_t *d = (uint8_t *)data; |
| int ret = 0; |
| |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(data != NULL, -EINVAL); |
| QM_CHECK(len > 0, -EINVAL); |
| |
| /* write slave address to TAR */ |
| con &= ~QM_SS_I2C_CON_TAR_SAR_MASK; |
| con |= (slave_addr & QM_SS_I2C_CON_TAR_SAR_10_BIT_MASK) |
| << QM_SS_I2C_CON_TAR_SAR_OFFSET; |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| while (len--) { |
| if (len == 0 && stop) { |
| data_cmd |= QM_SS_I2C_DATA_CMD_STOP; |
| } |
| |
| __builtin_arc_sr(data_cmd, controller + QM_SS_I2C_DATA_CMD); |
| |
| /* wait if rx fifo is empty, break if tx empty and |
| * error*/ |
| while (!(__builtin_arc_lr(controller + QM_SS_I2C_STATUS) & |
| QM_SS_I2C_STATUS_RFNE)) { |
| if (__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT) { |
| break; |
| } |
| } |
| |
| if ((__builtin_arc_lr(controller + QM_SS_I2C_INTR_STAT) & |
| QM_SS_I2C_INTR_STAT_TX_ABRT)) { |
| ret = -EIO; |
| break; |
| } |
| |
| __builtin_arc_sr(QM_SS_I2C_DATA_CMD_POP, |
| controller + QM_SS_I2C_DATA_CMD); |
| |
| /* wait until rx fifo is empty, indicating pop is complete*/ |
| while ((__builtin_arc_lr(controller + QM_SS_I2C_STATUS) & |
| QM_SS_I2C_STATUS_RFNE)) |
| ; |
| |
| /* IC_DATA_CMD[7:0] contains received data */ |
| *d = __builtin_arc_lr(controller + QM_SS_I2C_DATA_CMD); |
| 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. |
| */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_CLR_TX_ABRT, |
| controller + QM_SS_I2C_INTR_CLR); |
| |
| 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) |
| { |
| uint32_t controller = i2c_base[i2c], |
| con = __builtin_arc_lr(controller + QM_SS_I2C_CON); |
| QM_CHECK(i2c < QM_SS_I2C_NUM, -EINVAL); |
| QM_CHECK(NULL != xfer, -EINVAL); |
| |
| /* write slave address to TAR */ |
| con &= ~QM_SS_I2C_CON_TAR_SAR_MASK; |
| con |= (slave_addr & QM_SS_I2C_CON_TAR_SAR_10_BIT_MASK) |
| << QM_SS_I2C_CON_TAR_SAR_OFFSET; |
| __builtin_arc_sr(con, controller + QM_SS_I2C_CON); |
| |
| 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. |
| */ |
| __builtin_arc_sr( |
| ((TX_TL << QM_SS_I2C_TL_TX_TL_OFFSET) | (xfer->rx_len - 1)), |
| controller + QM_SS_I2C_TL); |
| } else { |
| __builtin_arc_sr(((TX_TL << QM_SS_I2C_TL_TX_TL_OFFSET) | RX_TL), |
| controller + QM_SS_I2C_TL); |
| } |
| |
| /* mask interrupts */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_MASK_ALL, |
| controller + QM_SS_I2C_INTR_MASK); |
| |
| /* enable controller */ |
| controller_enable(i2c); |
| |
| /* unmask interrupts */ |
| __builtin_arc_sr( |
| (QM_SS_I2C_INTR_MASK_TX_ABRT | QM_SS_I2C_INTR_MASK_TX_EMPTY | |
| QM_SS_I2C_INTR_MASK_TX_OVER | QM_SS_I2C_INTR_MASK_RX_FULL | |
| QM_SS_I2C_INTR_MASK_RX_OVER | QM_SS_I2C_INTR_MASK_RX_UNDER), |
| controller + QM_SS_I2C_INTR_MASK); |
| |
| return 0; |
| } |
| |
| static void controller_enable(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| if (!(__builtin_arc_lr(controller + QM_SS_I2C_ENABLE_STATUS) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN)) { |
| /* enable controller */ |
| QM_SS_REG_AUX_OR((controller + QM_SS_I2C_CON), |
| QM_SS_I2C_CON_ENABLE); |
| /* wait until controller is enabled */ |
| while ( |
| !(__builtin_arc_lr(controller + QM_SS_I2C_ENABLE_STATUS) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN)) |
| ; |
| } |
| |
| /* Clear all interruption flags */ |
| __builtin_arc_sr(QM_SS_I2C_INTR_CLR_ALL, |
| controller + QM_SS_I2C_INTR_CLR); |
| } |
| |
| 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_REG_AUX_NAND((controller + QM_SS_I2C_CON), QM_SS_I2C_CON_ENABLE); |
| |
| /* wait until controller is disabled */ |
| while ((__builtin_arc_lr(controller + QM_SS_I2C_ENABLE_STATUS) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN) && |
| poll_count--) { |
| clk_sys_udelay(I2C_POLL_MICROSECOND); |
| } |
| |
| /* returns 0 if ok, meaning controller is disabled */ |
| return (__builtin_arc_lr(controller + QM_SS_I2C_ENABLE_STATUS) & |
| QM_SS_I2C_ENABLE_STATUS_IC_EN); |
| } |
| |
| int qm_ss_i2c_irq_transfer_terminate(const qm_ss_i2c_t i2c) |
| { |
| uint32_t controller = i2c_base[i2c]; |
| QM_CHECK(i2c < QM_SS_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_SS_REG_AUX_OR((controller + QM_SS_I2C_CON), QM_SS_I2C_CON_ABORT); |
| |
| 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; |
| } |
| #endif /* ENABLE_RESTORE_CONTEXT */ |