Загрузка данных
static void lpi2c_imx_write(struct lpi2c_imx_struct *lpi2c_imx,
struct i2c_msg *msgs)
{
lpi2c_imx->tx_buf = msgs->buf;
lpi2c_imx_set_tx_watermark(lpi2c_imx);
lpi2c_imx_write_txfifo(lpi2c_imx);
}
static void lpi2c_imx_read(struct lpi2c_imx_struct *lpi2c_imx,
struct i2c_msg *msgs)
{
unsigned int temp;
lpi2c_imx->rx_buf = msgs->buf;
lpi2c_imx->block_data = msgs->flags & I2C_M_RECV_LEN;
lpi2c_imx_set_rx_watermark(lpi2c_imx);
temp = msgs->len > CHUNK_DATA ? CHUNK_DATA - 1 : msgs->len - 1;
temp |= (RECV_DATA << 8);
writel(temp, lpi2c_imx->base + LPI2C_MTDR);
lpi2c_imx_intctrl(lpi2c_imx, MIER_RDIE | MIER_NDIE);
}
static bool is_use_dma(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg)
{
if (!lpi2c_imx->can_use_dma)
return false;
/*
* When system is in suspend process. LPI2C should use PIO to transfer data to
* avoid issue caused by not ready DMA HW resource.
*/
if (pm_suspend_target_state != PM_SUSPEND_ON)
return false;
/*
* When the length of data is less than I2C_DMA_THRESHOLD,
* cpu mode is used directly to avoid low performance.
*/
if (msg->len < I2C_DMA_THRESHOLD)
return false;
return true;
}
static int lpi2c_imx_pio_xfer(struct lpi2c_imx_struct *lpi2c_imx,
struct i2c_msg *msg)
{
int ret;
reinit_completion(&lpi2c_imx->complete);
if (msg->flags & I2C_M_RD)
lpi2c_imx_read(lpi2c_imx, msg);
else
lpi2c_imx_write(lpi2c_imx, msg);
ret = lpi2c_imx_pio_msg_complete(lpi2c_imx);
if (ret)
return ret;
return 0;
}
static int lpi2c_imx_dma_timeout_calculate(struct lpi2c_imx_struct *lpi2c_imx)
{
unsigned long time = 0;
time = 8 * lpi2c_imx->dma->dma_len * 1000 / lpi2c_imx->bitrate;
/* Add extra second for scheduler related activities */
time += 1;
/* Double calculated time */
return msecs_to_jiffies(time * MSEC_PER_SEC);
}
static int lpi2c_imx_alloc_rx_cmd_buf(struct lpi2c_imx_struct *lpi2c_imx)
{
struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
u16 rx_remain = dma->dma_len;
int cmd_num;
u16 temp;
/*
* Calculate the number of rx command words via the DMA TX channel
* writing into command register based on the i2c msg len, and build
* the rx command words buffer.
*/
cmd_num = DIV_ROUND_UP(rx_remain, CHUNK_DATA);
dma->rx_cmd_buf = kcalloc(cmd_num, sizeof(u16), GFP_KERNEL);
dma->rx_cmd_buf_len = cmd_num * sizeof(u16);
if (!dma->rx_cmd_buf) {
dev_err(&lpi2c_imx->adapter.dev, "Alloc RX cmd buffer failed\n");
return -ENOMEM;
}
for (int i = 0; i < cmd_num ; i++) {
temp = rx_remain > CHUNK_DATA ? CHUNK_DATA - 1 : rx_remain - 1;
temp |= (RECV_DATA << 8);
rx_remain -= CHUNK_DATA;
dma->rx_cmd_buf[i] = temp;
}
return 0;
}
static int lpi2c_imx_dma_msg_complete(struct lpi2c_imx_struct *lpi2c_imx)
{
unsigned long time_left, time;
time = lpi2c_imx_dma_timeout_calculate(lpi2c_imx);
time_left = wait_for_completion_timeout(&lpi2c_imx->complete, time);
if (time_left == 0) {
dev_err(&lpi2c_imx->adapter.dev, "I/O Error in DMA Data Transfer\n");
return -ETIMEDOUT;
}
return 0;
}
static void lpi2c_dma_unmap(struct lpi2c_imx_dma *dma)
{
struct dma_chan *chan = dma->dma_direction == DMA_FROM_DEVICE
? dma->chan_rx : dma->chan_tx;
dma_unmap_single(chan->device->dev, dma->dma_addr,
dma->dma_len, dma->dma_direction);
dma->dma_direction = DMA_NONE;
}
static void lpi2c_cleanup_rx_cmd_dma(struct lpi2c_imx_dma *dma)
{
dmaengine_terminate_sync(dma->chan_tx);
dma_unmap_single(dma->chan_tx->device->dev, dma->dma_tx_addr,
dma->rx_cmd_buf_len, DMA_TO_DEVICE);
}
static void lpi2c_cleanup_dma(struct lpi2c_imx_dma *dma)
{
if (dma->dma_direction == DMA_FROM_DEVICE)
dmaengine_terminate_sync(dma->chan_rx);
else if (dma->dma_direction == DMA_TO_DEVICE)
dmaengine_terminate_sync(dma->chan_tx);
lpi2c_dma_unmap(dma);
}
static void lpi2c_dma_callback(void *data)
{
struct lpi2c_imx_struct *lpi2c_imx = (struct lpi2c_imx_struct *)data;
complete(&lpi2c_imx->complete);
}
static int lpi2c_dma_rx_cmd_submit(struct lpi2c_imx_struct *lpi2c_imx)
{
struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
struct dma_chan *txchan = dma->chan_tx;
struct dma_async_tx_descriptor *rx_cmd_desc;
dma_cookie_t cookie;
dma->dma_tx_addr = dma_map_single(txchan->device->dev,
dma->rx_cmd_buf,
dma->rx_cmd_buf_len, DMA_TO_DEVICE);
if (dma_mapping_error(txchan->device->dev, dma->dma_tx_addr)) {
dev_err(&lpi2c_imx->adapter.dev, "dma map failed, use pio\n");
return -EINVAL;
}
rx_cmd_desc = dmaengine_prep_slave_single(txchan, dma->dma_tx_addr,
dma->rx_cmd_buf_len, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!rx_cmd_desc) {
dma_unmap_single(txchan->device->dev, dma->dma_tx_addr,
dma->rx_cmd_buf_len, DMA_TO_DEVICE);
dev_err(&lpi2c_imx->adapter.dev, "dma prep slave sg failed, use pio\n");
return -EINVAL;
}
cookie = dmaengine_submit(rx_cmd_desc);
if (dma_submit_error(cookie)) {
dma_unmap_single(txchan->device->dev, dma->dma_tx_addr,
dma->rx_cmd_buf_len, DMA_TO_DEVICE);
dmaengine_desc_free(rx_cmd_desc);
dev_err(&lpi2c_imx->adapter.dev, "submitting dma failed, use pio\n");
return -EINVAL;
}
dma_async_issue_pending(txchan);
return 0;
}
static int lpi2c_dma_submit(struct lpi2c_imx_struct *lpi2c_imx)
{
struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
bool read = dma->dma_msg_flag & I2C_M_RD;
enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
struct dma_chan *chan = read ? dma->chan_rx : dma->chan_tx;
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
dma->dma_direction = dir;
dma->dma_addr = dma_map_single(chan->device->dev,
dma->dma_buf,
dma->dma_len, dir);
if (dma_mapping_error(chan->device->dev, dma->dma_addr)) {
dev_err(&lpi2c_imx->adapter.dev, "dma map failed, use pio\n");
return -EINVAL;
}
desc = dmaengine_prep_slave_single(chan, dma->dma_addr,
dma->dma_len, read ?
DMA_DEV_TO_MEM : DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
dev_err(&lpi2c_imx->adapter.dev, "dma prep slave sg failed, use pio\n");
lpi2c_dma_unmap(dma);
return -EINVAL;
}
reinit_completion(&lpi2c_imx->complete);
desc->callback = lpi2c_dma_callback;
desc->callback_param = (void *)lpi2c_imx;
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie)) {
dev_err(&lpi2c_imx->adapter.dev, "submitting dma failed, use pio\n");
lpi2c_dma_unmap(dma);
dmaengine_desc_free(desc);
return -EINVAL;
}
/* Can't switch to PIO mode when DMA have started transfer */
dma->using_pio_mode = false;
dma_async_issue_pending(chan);
return 0;
}