blob: b3e7ad81d2cc4368280ef683dfaa1542256397f7 [file] [log] [blame]
/*
* 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_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];
/**
* 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);
static void qm_i2c_isr_handler(const qm_i2c_t i2c)
{
const volatile qm_i2c_transfer_t *const transfer = i2c_transfer[i2c];
uint32_t ic_data_cmd = 0, count_tx = (QM_I2C_FIFO_SIZE - TX_TL);
qm_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;
qm_i2c_reg_t *const controller = QM_I2C[i2c];
/* Check for errors */
QM_ASSERT(!(controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_OVER));
QM_ASSERT(!(controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_UNDER));
QM_ASSERT(!(controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_RX_OVER));
if (controller->ic_intr_stat & QM_I2C_IC_INTR_STAT_TX_ABRT) {
QM_ASSERT(!(controller->ic_tx_abrt_source &
QM_I2C_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT));
status = (controller->ic_tx_abrt_source &
QM_I2C_IC_TX_ABRT_SOURCE_ALL_MASK);
/* clear intr */
controller->ic_clr_tx_abrt;
/* mask interrupts */
controller->ic_intr_mask = QM_I2C_IC_INTR_MASK_ALL;
rc = (status & QM_I2C_TX_ABRT_USER_ABRT) ? -ECANCELED : -EIO;
if ((i2c_dma_context[i2c].ongoing_dma_rx_operation == true) ||
(i2c_dma_context[i2c].ongoing_dma_tx_operation == true)) {
/* If in DMA mode, 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);
} else {
controller_disable(i2c);
if (transfer->callback) {
/* NOTE: currently 0 is returned for length but
* we may revisit that soon
*/
transfer->callback(transfer->callback_data, rc,
status, 0);
}
}
}
/* 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]);
}
}
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
*/
}
/* 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;
}
}
}
QM_ISR_DECLARE(qm_i2c_0_isr)
{
qm_i2c_isr_handler(QM_I2C_0);
QM_ISR_EOI(QM_IRQ_I2C_0_VECTOR);
}
#if (QUARK_SE)
QM_ISR_DECLARE(qm_i2c_1_isr)
{
qm_i2c_isr_handler(QM_I2C_1);
QM_ISR_EOI(QM_IRQ_I2C_1_VECTOR);
}
#endif
static uint32_t get_lo_cnt(uint32_t lo_time_ns)
{
return ((((clk_sys_get_ticks_per_us() >>
((QM_SCSS_CCU->ccu_periph_clk_div_ctl0 &
CLK_PERIPH_DIV_DEF_MASK) >>
QM_CCU_PERIPH_PCLK_DIV_OFFSET)) *
lo_time_ns) /
1000) -
1);
}
static uint32_t get_hi_cnt(qm_i2c_t i2c, uint32_t hi_time_ns)
{
/*
* See HAS for Known Limitation : Generated SCL HIGH
* period is less than the expected SCL clock HIGH period in
* the Master receiver mode.
* Summary: workaround is +1 to hcnt.
*/
return (((((clk_sys_get_ticks_per_us() >>
((QM_SCSS_CCU->ccu_periph_clk_div_ctl0 &
CLK_PERIPH_DIV_DEF_MASK) >>
QM_CCU_PERIPH_PCLK_DIV_OFFSET)) *
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];
/* 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;
/* 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 = 0;
/* 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 ret = 0;
QM_CHECK(i2c < QM_I2C_NUM, -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 = 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) {
ret = -EIO;
}
/* disable controller */
if (true == stop) {
if (controller_disable(i2c)) {
ret = -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 ret;
}
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 ret = 0;
QM_CHECK(i2c < QM_I2C_NUM, -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 = 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) {
ret = -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)) {
ret = -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 ret;
}
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_i2c_reg_t *const controller = QM_I2C[i2c];
/* write slave address to TAR */
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);
/* 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;
}
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 what 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;
}
/* 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;
/* NOTE: This can be optimized for performance */
dma_channel_config.source_burst_length = QM_DMA_BURST_TRANS_LENGTH_1;
dma_channel_config.destination_burst_length =
QM_DMA_BURST_TRANS_LENGTH_1;
dma_channel_config.client_callback = i2c_dma_callbacks[direction];
/* 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);
/* 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 = slave_addr;
i2c_read_cmd_send[i2c] = xfer->rx_len;
i2c_transfer[i2c] = xfer;
/* Set DMA TX and RX waterlevels to 0, to make sure no data is lost */
/* NOTE: This can be optimized for performance */
QM_I2C[i2c]->ic_dma_rdlr = 0;
QM_I2C[i2c]->ic_dma_tdlr = 0;
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;
}