mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-30 03:57:32 +00:00

This patch adds support for a new feature, named "Interrupt Delay", allowing driver to set a specific delay time for next interrupt detection. It gives driver a capability to control THC waiting time for the next interrupt, to reduce the likelihood of spurious readings. APIs added: - thc_i2c_set_rx_int_delay(): Set I2C Rx input interrupt delay value - thc_i2c_rx_int_delay_enable(): Enable or disable I2C Rx interrupt delay As this interrupt delay feature is only applicable to RxDMA and must remain disabled during SWDMA operations, it also involves a change in SWDMA code to record the max input size control feature state before SWDMA and restore the state after SWDMA. Signed-off-by: Even Xu <even.xu@intel.com> Tested-by: Chong Han <chong.han@intel.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Jiri Kosina <jkosina@suse.com>
1010 lines
29 KiB
C
1010 lines
29 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/* Copyright (c) 2024 Intel Corporation */
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/overflow.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/scatterlist.h>
|
|
|
|
#include "intel-thc-dev.h"
|
|
#include "intel-thc-dma.h"
|
|
#include "intel-thc-hw.h"
|
|
|
|
static void dma_set_prd_base_addr(struct thc_device *dev, u64 physical_addr,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 addr_high, addr_low;
|
|
|
|
if (!dma_config->is_enabled)
|
|
return;
|
|
|
|
addr_high = upper_32_bits(physical_addr);
|
|
addr_low = lower_32_bits(physical_addr);
|
|
|
|
regmap_write(dev->thc_regmap, dma_config->prd_base_addr_high, addr_high);
|
|
regmap_write(dev->thc_regmap, dma_config->prd_base_addr_low, addr_low);
|
|
}
|
|
|
|
static void dma_set_start_bit(struct thc_device *dev,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 ctrl, mask, mbits, data, offset;
|
|
|
|
if (!dma_config->is_enabled)
|
|
return;
|
|
|
|
switch (dma_config->dma_channel) {
|
|
case THC_RXDMA1:
|
|
case THC_RXDMA2:
|
|
if (dma_config->dma_channel == THC_RXDMA2) {
|
|
mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL,
|
|
THC_BITMASK_INTERRUPT_TYPE_DATA);
|
|
mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_DEVINT_CFG_1_OFFSET, mask, mbits);
|
|
}
|
|
|
|
mbits = THC_M_PRT_READ_DMA_CNTRL_IE_EOF |
|
|
THC_M_PRT_READ_DMA_CNTRL_SOO |
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_STALL |
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_ERROR |
|
|
THC_M_PRT_READ_DMA_CNTRL_START;
|
|
|
|
mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
|
|
mask |= THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN;
|
|
ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
|
|
offset = dma_config->dma_channel == THC_RXDMA1 ?
|
|
THC_M_PRT_READ_DMA_CNTRL_1_OFFSET : THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
|
|
regmap_write_bits(dev->thc_regmap, offset, mask, ctrl);
|
|
break;
|
|
|
|
case THC_SWDMA:
|
|
mbits = THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL |
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_IOC |
|
|
THC_M_PRT_READ_DMA_CNTRL_SOO |
|
|
THC_M_PRT_READ_DMA_CNTRL_START;
|
|
|
|
mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
|
|
ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
|
|
mask, ctrl);
|
|
break;
|
|
|
|
case THC_TXDMA:
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET,
|
|
THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS,
|
|
THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS);
|
|
|
|
/* Select interrupt or polling method upon Write completion */
|
|
if (dev->dma_ctx->use_write_interrupts)
|
|
data = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL;
|
|
else
|
|
data = 0;
|
|
|
|
data |= THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
|
|
mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL |
|
|
THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
|
|
mask, data);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void dma_set_prd_control(struct thc_device *dev, u8 entry_count, u8 cb_depth,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 ctrl, mask;
|
|
|
|
if (!dma_config->is_enabled)
|
|
return;
|
|
|
|
if (dma_config->dma_channel == THC_TXDMA) {
|
|
mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
|
|
ctrl = FIELD_PREP(THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC, entry_count);
|
|
} else {
|
|
mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;
|
|
ctrl = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PTEC, entry_count) |
|
|
FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PCD, cb_depth);
|
|
}
|
|
|
|
regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, ctrl);
|
|
}
|
|
|
|
static void dma_clear_prd_control(struct thc_device *dev,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 mask;
|
|
|
|
if (!dma_config->is_enabled)
|
|
return;
|
|
|
|
if (dma_config->dma_channel == THC_TXDMA)
|
|
mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
|
|
else
|
|
mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;
|
|
|
|
regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, 0);
|
|
}
|
|
|
|
static u8 dma_get_read_pointer(struct thc_device *dev,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 ctrl, read_pointer;
|
|
|
|
regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
|
|
read_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCRP, ctrl);
|
|
|
|
dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCRP 0x%x\n",
|
|
ctrl, dma_config->dma_cntrl, read_pointer);
|
|
|
|
return read_pointer;
|
|
}
|
|
|
|
static u8 dma_get_write_pointer(struct thc_device *dev,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 ctrl, write_pointer;
|
|
|
|
regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
|
|
write_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCWP, ctrl);
|
|
|
|
dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCWP 0x%x\n",
|
|
ctrl, dma_config->dma_cntrl, write_pointer);
|
|
|
|
return write_pointer;
|
|
}
|
|
|
|
static void dma_set_write_pointer(struct thc_device *dev, u8 value,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
u32 ctrl, mask;
|
|
|
|
mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP;
|
|
ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, value);
|
|
regmap_write_bits(dev->thc_regmap, dma_config->dma_cntrl, mask, ctrl);
|
|
}
|
|
|
|
static size_t dma_get_max_packet_size(struct thc_device *dev,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
return dma_config->max_packet_size;
|
|
}
|
|
|
|
static void dma_set_max_packet_size(struct thc_device *dev, size_t size,
|
|
struct thc_dma_configuration *dma_config)
|
|
{
|
|
if (size) {
|
|
dma_config->max_packet_size = ALIGN(size, SZ_4K);
|
|
dma_config->is_enabled = true;
|
|
}
|
|
}
|
|
|
|
static void thc_copy_one_sgl_to_prd(struct thc_device *dev,
|
|
struct thc_dma_configuration *config,
|
|
unsigned int ind)
|
|
{
|
|
struct thc_prd_table *prd_tbl;
|
|
struct scatterlist *sg;
|
|
int j;
|
|
|
|
prd_tbl = &config->prd_tbls[ind];
|
|
|
|
for_each_sg(config->sgls[ind], sg, config->sgls_nent[ind], j) {
|
|
prd_tbl->entries[j].dest_addr =
|
|
sg_dma_address(sg) >> THC_ADDRESS_SHIFT;
|
|
prd_tbl->entries[j].len = sg_dma_len(sg);
|
|
prd_tbl->entries[j].hw_status = 0;
|
|
prd_tbl->entries[j].end_of_prd = 0;
|
|
}
|
|
|
|
/* Set the end_of_prd flag in the last filled entry */
|
|
if (j > 0)
|
|
prd_tbl->entries[j - 1].end_of_prd = 1;
|
|
}
|
|
|
|
static void thc_copy_sgls_to_prd(struct thc_device *dev,
|
|
struct thc_dma_configuration *config)
|
|
{
|
|
unsigned int i;
|
|
|
|
memset(config->prd_tbls, 0, array_size(PRD_TABLE_SIZE, config->prd_tbl_num));
|
|
|
|
for (i = 0; i < config->prd_tbl_num; i++)
|
|
thc_copy_one_sgl_to_prd(dev, config, i);
|
|
}
|
|
|
|
static int setup_dma_buffers(struct thc_device *dev,
|
|
struct thc_dma_configuration *config,
|
|
enum dma_data_direction dir)
|
|
{
|
|
size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
|
|
unsigned int i, nent = PRD_ENTRIES_NUM;
|
|
dma_addr_t dma_handle;
|
|
void *cpu_addr;
|
|
size_t buf_sz;
|
|
int count;
|
|
|
|
if (!config->is_enabled)
|
|
return 0;
|
|
|
|
memset(config->sgls, 0, sizeof(config->sgls));
|
|
memset(config->sgls_nent, 0, sizeof(config->sgls_nent));
|
|
|
|
cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size,
|
|
&dma_handle, GFP_KERNEL);
|
|
if (!cpu_addr)
|
|
return -ENOMEM;
|
|
|
|
config->prd_tbls = cpu_addr;
|
|
config->prd_tbls_dma_handle = dma_handle;
|
|
|
|
buf_sz = dma_get_max_packet_size(dev, config);
|
|
|
|
/* Allocate and map the scatter-gather lists, one for each PRD table */
|
|
for (i = 0; i < config->prd_tbl_num; i++) {
|
|
config->sgls[i] = sgl_alloc(buf_sz, GFP_KERNEL, &nent);
|
|
if (!config->sgls[i] || nent > PRD_ENTRIES_NUM) {
|
|
dev_err_once(dev->dev, "sgl_alloc (%uth) failed, nent %u\n",
|
|
i, nent);
|
|
return -ENOMEM;
|
|
}
|
|
count = dma_map_sg(dev->dev, config->sgls[i], nent, dir);
|
|
|
|
config->sgls_nent[i] = count;
|
|
}
|
|
|
|
thc_copy_sgls_to_prd(dev, config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void thc_reset_dma_settings(struct thc_device *dev)
|
|
{
|
|
/* Stop all DMA channels and reset DMA read pointers */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
|
|
THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START, 0);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR,
|
|
THC_M_PRT_READ_DMA_CNTRL_TPCPR);
|
|
}
|
|
|
|
static void release_dma_buffers(struct thc_device *dev,
|
|
struct thc_dma_configuration *config)
|
|
{
|
|
size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
|
|
unsigned int i;
|
|
|
|
if (!config->is_enabled)
|
|
return;
|
|
|
|
for (i = 0; i < config->prd_tbl_num; i++) {
|
|
if (!config->sgls[i] || !config->sgls_nent[i])
|
|
continue;
|
|
|
|
dma_unmap_sg(dev->dev, config->sgls[i],
|
|
config->sgls_nent[i],
|
|
config->dir);
|
|
|
|
sgl_free(config->sgls[i]);
|
|
config->sgls[i] = NULL;
|
|
}
|
|
|
|
memset(config->prd_tbls, 0, prd_tbls_size);
|
|
|
|
if (config->prd_tbls) {
|
|
dma_free_coherent(dev->dev, prd_tbls_size, config->prd_tbls,
|
|
config->prd_tbls_dma_handle);
|
|
config->prd_tbls = NULL;
|
|
config->prd_tbls_dma_handle = 0;
|
|
}
|
|
}
|
|
|
|
struct thc_dma_context *thc_dma_init(struct thc_device *dev)
|
|
{
|
|
struct thc_dma_context *dma_ctx;
|
|
|
|
dma_ctx = devm_kzalloc(dev->dev, sizeof(*dma_ctx), GFP_KERNEL);
|
|
if (!dma_ctx)
|
|
return NULL;
|
|
|
|
dev->dma_ctx = dma_ctx;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].dma_channel = THC_RXDMA1;
|
|
dma_ctx->dma_config[THC_RXDMA2].dma_channel = THC_RXDMA2;
|
|
dma_ctx->dma_config[THC_TXDMA].dma_channel = THC_TXDMA;
|
|
dma_ctx->dma_config[THC_SWDMA].dma_channel = THC_SWDMA;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].dir = DMA_FROM_DEVICE;
|
|
dma_ctx->dma_config[THC_RXDMA2].dir = DMA_FROM_DEVICE;
|
|
dma_ctx->dma_config[THC_TXDMA].dir = DMA_TO_DEVICE;
|
|
dma_ctx->dma_config[THC_SWDMA].dir = DMA_FROM_DEVICE;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].prd_tbl_num = PRD_TABLES_NUM;
|
|
dma_ctx->dma_config[THC_RXDMA2].prd_tbl_num = PRD_TABLES_NUM;
|
|
dma_ctx->dma_config[THC_TXDMA].prd_tbl_num = 1;
|
|
dma_ctx->dma_config[THC_SWDMA].prd_tbl_num = 1;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_1_OFFSET;
|
|
dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_2_OFFSET;
|
|
dma_ctx->dma_config[THC_TXDMA].prd_base_addr_high = THC_M_PRT_WPRD_BA_HI_OFFSET;
|
|
dma_ctx->dma_config[THC_SWDMA].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_SW_OFFSET;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_1_OFFSET;
|
|
dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_2_OFFSET;
|
|
dma_ctx->dma_config[THC_TXDMA].prd_base_addr_low = THC_M_PRT_WPRD_BA_LOW_OFFSET;
|
|
dma_ctx->dma_config[THC_SWDMA].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_SW_OFFSET;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].prd_cntrl = THC_M_PRT_RPRD_CNTRL_1_OFFSET;
|
|
dma_ctx->dma_config[THC_RXDMA2].prd_cntrl = THC_M_PRT_RPRD_CNTRL_2_OFFSET;
|
|
dma_ctx->dma_config[THC_TXDMA].prd_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
|
|
dma_ctx->dma_config[THC_SWDMA].prd_cntrl = THC_M_PRT_RPRD_CNTRL_SW_OFFSET;
|
|
|
|
dma_ctx->dma_config[THC_RXDMA1].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_1_OFFSET;
|
|
dma_ctx->dma_config[THC_RXDMA2].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
|
|
dma_ctx->dma_config[THC_TXDMA].dma_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
|
|
dma_ctx->dma_config[THC_SWDMA].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET;
|
|
|
|
/* Enable write DMA completion interrupt by default */
|
|
dma_ctx->use_write_interrupts = 1;
|
|
|
|
return dma_ctx;
|
|
}
|
|
|
|
/**
|
|
* thc_dma_set_max_packet_sizes - Set max packet sizes for all DMA engines
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @mps_read1: RxDMA1 max packet size
|
|
* @mps_read2: RxDMA2 max packet size
|
|
* @mps_write: TxDMA max packet size
|
|
* @mps_swdma: Software DMA max packet size
|
|
*
|
|
* If mps is not 0, it means the corresponding DMA channel is used, then set
|
|
* the flag to turn on this channel.
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_dma_set_max_packet_sizes(struct thc_device *dev, size_t mps_read1,
|
|
size_t mps_read2, size_t mps_write,
|
|
size_t mps_swdma)
|
|
{
|
|
if (!dev->dma_ctx) {
|
|
dev_err_once(dev->dev,
|
|
"Cannot set max packet sizes because DMA context is NULL!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dma_set_max_packet_size(dev, mps_read1, &dev->dma_ctx->dma_config[THC_RXDMA1]);
|
|
dma_set_max_packet_size(dev, mps_read2, &dev->dma_ctx->dma_config[THC_RXDMA2]);
|
|
dma_set_max_packet_size(dev, mps_write, &dev->dma_ctx->dma_config[THC_TXDMA]);
|
|
dma_set_max_packet_size(dev, mps_swdma, &dev->dma_ctx->dma_config[THC_SWDMA]);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_set_max_packet_sizes, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_dma_allocate - Allocate DMA buffers for all DMA engines
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_dma_allocate(struct thc_device *dev)
|
|
{
|
|
int ret, chan;
|
|
|
|
for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
|
|
ret = setup_dma_buffers(dev, &dev->dma_ctx->dma_config[chan],
|
|
dev->dma_ctx->dma_config[chan].dir);
|
|
if (ret < 0) {
|
|
dev_err_once(dev->dev, "DMA setup failed for DMA channel %d\n", chan);
|
|
goto release_bufs;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
release_bufs:
|
|
while (chan--)
|
|
release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_allocate, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_dma_release - Release DMA buffers for all DMA engines
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*/
|
|
void thc_dma_release(struct thc_device *dev)
|
|
{
|
|
int chan;
|
|
|
|
for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++)
|
|
release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_release, "INTEL_THC");
|
|
|
|
static int calc_prd_entries_num(struct thc_prd_table *prd_tbl,
|
|
size_t mes_len, u8 *nent)
|
|
{
|
|
*nent = DIV_ROUND_UP(mes_len, THC_MIN_BYTES_PER_SG_LIST_ENTRY);
|
|
if (*nent > PRD_ENTRIES_NUM)
|
|
return -EMSGSIZE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t calc_message_len(struct thc_prd_table *prd_tbl, u8 *nent)
|
|
{
|
|
size_t mes_len = 0;
|
|
unsigned int j;
|
|
|
|
for (j = 0; j < PRD_ENTRIES_NUM; j++) {
|
|
mes_len += prd_tbl->entries[j].len;
|
|
if (prd_tbl->entries[j].end_of_prd)
|
|
break;
|
|
}
|
|
|
|
*nent = j + 1;
|
|
|
|
return mes_len;
|
|
}
|
|
|
|
/**
|
|
* thc_dma_configure - Configure DMA settings for all DMA engines
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_dma_configure(struct thc_device *dev)
|
|
{
|
|
struct thc_dma_context *dma_ctx = dev->dma_ctx;
|
|
int chan;
|
|
|
|
thc_reset_dma_settings(dev);
|
|
|
|
if (!dma_ctx) {
|
|
dev_err_once(dev->dev, "Cannot do DMA configure because DMA context is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
|
|
dma_set_prd_base_addr(dev,
|
|
dma_ctx->dma_config[chan].prd_tbls_dma_handle,
|
|
&dma_ctx->dma_config[chan]);
|
|
|
|
dma_set_prd_control(dev, PRD_ENTRIES_NUM - 1,
|
|
dma_ctx->dma_config[chan].prd_tbl_num - 1,
|
|
&dma_ctx->dma_config[chan]);
|
|
}
|
|
|
|
/* Start read2 DMA engine */
|
|
dma_set_start_bit(dev, &dma_ctx->dma_config[THC_RXDMA2]);
|
|
|
|
dev_dbg(dev->dev, "DMA configured successfully!\n");
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_configure, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_dma_unconfigure - Unconfigure DMA settings for all DMA engines
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*/
|
|
void thc_dma_unconfigure(struct thc_device *dev)
|
|
{
|
|
int chan;
|
|
|
|
for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
|
|
dma_set_prd_base_addr(dev, 0, &dev->dma_ctx->dma_config[chan]);
|
|
dma_clear_prd_control(dev, &dev->dma_ctx->dma_config[chan]);
|
|
}
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_unconfigure, "INTEL_THC");
|
|
|
|
static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel channel)
|
|
{
|
|
u32 ctrl_reg, sts_reg, sts;
|
|
int ret;
|
|
|
|
ctrl_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_CNTRL_1_OFFSET :
|
|
((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_CNTRL_2_OFFSET :
|
|
THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET);
|
|
|
|
regmap_write_bits(dev->thc_regmap, ctrl_reg, THC_M_PRT_READ_DMA_CNTRL_START, 0);
|
|
|
|
sts_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_INT_STS_1_OFFSET :
|
|
((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_INT_STS_2_OFFSET :
|
|
THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET);
|
|
|
|
ret = regmap_read_poll_timeout(dev->thc_regmap, sts_reg, sts,
|
|
!(sts & THC_M_PRT_READ_DMA_INT_STS_ACTIVE),
|
|
THC_DEFAULT_RXDMA_POLLING_US_INTERVAL,
|
|
THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT);
|
|
|
|
if (ret) {
|
|
dev_err_once(dev->dev,
|
|
"Timeout while waiting for DMA %d stop\n", channel);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_dma_buffer(struct thc_device *dev,
|
|
struct thc_dma_configuration *read_config,
|
|
u8 prd_table_index, void *read_buff)
|
|
{
|
|
struct thc_prd_table *prd_tbl;
|
|
struct scatterlist *sg;
|
|
size_t mes_len, ret;
|
|
u8 nent;
|
|
|
|
if (prd_table_index >= read_config->prd_tbl_num) {
|
|
dev_err_once(dev->dev, "PRD table index %d too big\n", prd_table_index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prd_tbl = &read_config->prd_tbls[prd_table_index];
|
|
mes_len = calc_message_len(prd_tbl, &nent);
|
|
if (mes_len > read_config->max_packet_size) {
|
|
dev_err(dev->dev,
|
|
"Message length %zu is bigger than buffer length %lu\n",
|
|
mes_len, read_config->max_packet_size);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
sg = read_config->sgls[prd_table_index];
|
|
ret = sg_copy_to_buffer(sg, nent, read_buff, mes_len);
|
|
if (ret != mes_len) {
|
|
dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
|
|
ret, mes_len);
|
|
return -EIO;
|
|
}
|
|
|
|
return mes_len;
|
|
}
|
|
|
|
static void update_write_pointer(struct thc_device *dev,
|
|
struct thc_dma_configuration *read_config)
|
|
{
|
|
u8 write_ptr = dma_get_write_pointer(dev, read_config);
|
|
|
|
if (write_ptr + 1 == THC_WRAPAROUND_VALUE_ODD)
|
|
dma_set_write_pointer(dev, THC_POINTER_WRAPAROUND, read_config);
|
|
else if (write_ptr + 1 == THC_WRAPAROUND_VALUE_EVEN)
|
|
dma_set_write_pointer(dev, 0, read_config);
|
|
else
|
|
dma_set_write_pointer(dev, write_ptr + 1, read_config);
|
|
}
|
|
|
|
static int is_dma_buf_empty(struct thc_device *dev,
|
|
struct thc_dma_configuration *read_config,
|
|
u8 *read_ptr, u8 *write_ptr)
|
|
{
|
|
*read_ptr = dma_get_read_pointer(dev, read_config);
|
|
*write_ptr = dma_get_write_pointer(dev, read_config);
|
|
|
|
if ((*read_ptr & THC_POINTER_MASK) == (*write_ptr & THC_POINTER_MASK))
|
|
if (*read_ptr != *write_ptr)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int thc_dma_read(struct thc_device *dev,
|
|
struct thc_dma_configuration *read_config,
|
|
void *read_buff, size_t *read_len, int *read_finished)
|
|
{
|
|
u8 read_ptr, write_ptr, prd_table_index;
|
|
int status;
|
|
|
|
if (!is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr)) {
|
|
prd_table_index = write_ptr & THC_POINTER_MASK;
|
|
|
|
status = read_dma_buffer(dev, read_config, prd_table_index, read_buff);
|
|
if (status <= 0) {
|
|
dev_err_once(dev->dev, "read DMA buffer failed %d\n", status);
|
|
return -EIO;
|
|
}
|
|
|
|
*read_len = status;
|
|
|
|
/* Clear the relevant PRD table */
|
|
thc_copy_one_sgl_to_prd(dev, read_config, prd_table_index);
|
|
|
|
/* Increment the write pointer to let the HW know we have processed this PRD */
|
|
update_write_pointer(dev, read_config);
|
|
}
|
|
|
|
/*
|
|
* This function only reads one frame from PRD table for each call, so we need to
|
|
* check if all DMAed data is read out and return the flag to the caller. Caller
|
|
* should repeatedly call thc_dma_read() until all DMAed data is handled.
|
|
*/
|
|
if (read_finished)
|
|
*read_finished = is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr) ? 1 : 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* thc_rxdma_read - Read data from RXDMA buffer
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @dma_channel: The RXDMA engine of read data source
|
|
* @read_buff: The pointer of the read data buffer
|
|
* @read_len: The pointer of the read data length
|
|
* @read_finished: The pointer of the flag indicating if all pending data has been read out
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel,
|
|
void *read_buff, size_t *read_len, int *read_finished)
|
|
{
|
|
struct thc_dma_configuration *dma_config;
|
|
int ret;
|
|
|
|
dma_config = &dev->dma_ctx->dma_config[dma_channel];
|
|
|
|
if (!dma_config->is_enabled) {
|
|
dev_err_once(dev->dev, "The DMA channel %d is not enabled", dma_channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!read_buff || !read_len) {
|
|
dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
|
|
read_buff, read_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma_channel >= THC_TXDMA) {
|
|
dev_err(dev->dev, "Unsupported DMA channel for RxDMA read, %d\n", dma_channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = thc_dma_read(dev, dma_config, read_buff, read_len, read_finished);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_rxdma_read, "INTEL_THC");
|
|
|
|
static int thc_swdma_read_start(struct thc_device *dev, void *write_buff,
|
|
size_t write_len, u32 *prd_tbl_len)
|
|
{
|
|
u32 mask, val, data0 = 0, data1 = 0;
|
|
int ret;
|
|
|
|
ret = thc_interrupt_quiesce(dev, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (thc_wait_for_dma_pause(dev, THC_RXDMA1) || thc_wait_for_dma_pause(dev, THC_RXDMA2))
|
|
return -EIO;
|
|
|
|
thc_reset_dma_settings(dev);
|
|
|
|
/*
|
|
* Max input size control feature is only available for RxDMA, it must keep disabled
|
|
* during SWDMA operation, and restore to previous state after SWDMA is done.
|
|
* Max input size variables in THC device context track hardware state, and keep change
|
|
* when feature state was changed, so those variables cannot be used to record feature
|
|
* state after state was changed during SWDMA operation. Here have to use a temp variable
|
|
* in DMA context to record feature state before SWDMA operation.
|
|
*/
|
|
if (dev->i2c_max_rx_size_en) {
|
|
thc_i2c_rx_max_size_enable(dev, false);
|
|
dev->dma_ctx->rx_max_size_en = true;
|
|
}
|
|
|
|
/*
|
|
* Interrupt delay feature is in the same situation with max input size control feature,
|
|
* needs record feature state before SWDMA.
|
|
*/
|
|
if (dev->i2c_int_delay_en) {
|
|
thc_i2c_rx_int_delay_enable(dev, false);
|
|
dev->dma_ctx->rx_int_delay_en = true;
|
|
}
|
|
|
|
mask = THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC |
|
|
THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN;
|
|
val = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC, write_len) |
|
|
((!prd_tbl_len) ? THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN : 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_RPRD_CNTRL_SW_OFFSET,
|
|
mask, val);
|
|
|
|
if (prd_tbl_len) {
|
|
mask = THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN;
|
|
val = FIELD_PREP(THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN,
|
|
*prd_tbl_len);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET,
|
|
mask, val);
|
|
}
|
|
|
|
if (write_len <= sizeof(u32)) {
|
|
for (int i = 0; i < write_len; i++)
|
|
data0 |= *(((u8 *)write_buff) + i) << (i * 8);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);
|
|
} else if (write_len <= 2 * sizeof(u32)) {
|
|
data0 = *(u32 *)write_buff;
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);
|
|
|
|
for (int i = 0; i < write_len - sizeof(u32); i++)
|
|
data1 |= *(((u8 *)write_buff) + sizeof(u32) + i) << (i * 8);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, data1);
|
|
}
|
|
dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_SWDMA]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int thc_swdma_read_completion(struct thc_device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = thc_wait_for_dma_pause(dev, THC_SWDMA);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Restore max input size control feature to previous state after SWDMA if it was
|
|
* enabled before SWDMA, and reset temp rx_max_size_en variable for next time.
|
|
*/
|
|
if (dev->dma_ctx->rx_max_size_en) {
|
|
thc_i2c_rx_max_size_enable(dev, true);
|
|
dev->dma_ctx->rx_max_size_en = false;
|
|
}
|
|
|
|
/*
|
|
* Restore input interrupt delay feature to previous state after SWDMA if it was
|
|
* enabled before SWDMA, and reset temp rx_int_delay_en variable for next time.
|
|
*/
|
|
if (dev->dma_ctx->rx_int_delay_en) {
|
|
thc_i2c_rx_int_delay_enable(dev, true);
|
|
dev->dma_ctx->rx_int_delay_en = false;
|
|
}
|
|
|
|
thc_reset_dma_settings(dev);
|
|
|
|
dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_RXDMA2]);
|
|
|
|
ret = thc_interrupt_quiesce(dev, false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* thc_swdma_read - Use software DMA to read data from touch device
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @write_buff: The pointer of write buffer for SWDMA sequence
|
|
* @write_len: The write data length for SWDMA sequence
|
|
* @prd_tbl_len: The prd table length of SWDMA engine, can be set to NULL
|
|
* @read_buff: The pointer of the read data buffer
|
|
* @read_len: The pointer of the read data length
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len,
|
|
u32 *prd_tbl_len, void *read_buff, size_t *read_len)
|
|
{
|
|
int ret;
|
|
|
|
if (!(&dev->dma_ctx->dma_config[THC_SWDMA])->is_enabled) {
|
|
dev_err_once(dev->dev, "The SWDMA channel is not enabled");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!read_buff || !read_len) {
|
|
dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
|
|
read_buff, read_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
dev->swdma_done = false;
|
|
|
|
ret = thc_swdma_read_start(dev, write_buff, write_len, prd_tbl_len);
|
|
if (ret)
|
|
goto end;
|
|
|
|
ret = wait_event_interruptible_timeout(dev->swdma_complete_wait, dev->swdma_done, 1 * HZ);
|
|
if (ret <= 0 || !dev->swdma_done) {
|
|
dev_err_once(dev->dev, "timeout for waiting SWDMA completion\n");
|
|
ret = -ETIMEDOUT;
|
|
goto end;
|
|
}
|
|
|
|
ret = thc_dma_read(dev, &dev->dma_ctx->dma_config[THC_SWDMA], read_buff, read_len, NULL);
|
|
if (ret)
|
|
goto end;
|
|
|
|
ret = thc_swdma_read_completion(dev);
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_swdma_read, "INTEL_THC");
|
|
|
|
static int write_dma_buffer(struct thc_device *dev,
|
|
void *buffer, size_t buf_len)
|
|
{
|
|
struct thc_dma_configuration *write_config = &dev->dma_ctx->dma_config[THC_TXDMA];
|
|
struct thc_prd_table *prd_tbl;
|
|
struct scatterlist *sg;
|
|
unsigned long len_left;
|
|
size_t ret;
|
|
u8 nent;
|
|
int i;
|
|
|
|
/* There is only one PRD table for write */
|
|
prd_tbl = &write_config->prd_tbls[0];
|
|
|
|
if (calc_prd_entries_num(prd_tbl, buf_len, &nent) < 0) {
|
|
dev_err(dev->dev, "Tx message length too big (%zu)\n", buf_len);
|
|
return -EOVERFLOW;
|
|
}
|
|
|
|
sg = write_config->sgls[0];
|
|
ret = sg_copy_from_buffer(sg, nent, buffer, buf_len);
|
|
if (ret != buf_len) {
|
|
dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
|
|
ret, buf_len);
|
|
return -EIO;
|
|
}
|
|
|
|
prd_tbl = &write_config->prd_tbls[0];
|
|
len_left = buf_len;
|
|
|
|
for_each_sg(write_config->sgls[0], sg, write_config->sgls_nent[0], i) {
|
|
if (sg_dma_address(sg) == 0 || sg_dma_len(sg) == 0) {
|
|
dev_err_once(dev->dev, "SGList: zero address or length\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
prd_tbl->entries[i].dest_addr =
|
|
sg_dma_address(sg) >> THC_ADDRESS_SHIFT;
|
|
|
|
if (len_left < sg_dma_len(sg)) {
|
|
prd_tbl->entries[i].len = len_left;
|
|
prd_tbl->entries[i].end_of_prd = 1;
|
|
break;
|
|
}
|
|
|
|
prd_tbl->entries[i].len = sg_dma_len(sg);
|
|
prd_tbl->entries[i].end_of_prd = 0;
|
|
|
|
len_left -= sg_dma_len(sg);
|
|
}
|
|
|
|
dma_set_prd_control(dev, i, 0, write_config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void thc_ensure_performance_limitations(struct thc_device *dev)
|
|
{
|
|
unsigned long delay_usec = 0;
|
|
/*
|
|
* Minimum amount of delay the THC / QUICKSPI driver must wait
|
|
* between end of write operation and begin of read operation.
|
|
* This value shall be in 10us multiples.
|
|
*/
|
|
if (dev->perf_limit > 0) {
|
|
delay_usec = dev->perf_limit * 10;
|
|
udelay(delay_usec);
|
|
}
|
|
}
|
|
|
|
static void thc_dma_write_completion(struct thc_device *dev)
|
|
{
|
|
thc_ensure_performance_limitations(dev);
|
|
}
|
|
|
|
/**
|
|
* thc_dma_write - Use TXDMA to write data to touch device
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @buffer: The pointer of write data buffer
|
|
* @buf_len: The write data length
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len)
|
|
{
|
|
bool restore_interrupts = false;
|
|
u32 sts, ctrl;
|
|
int ret;
|
|
|
|
if (!(&dev->dma_ctx->dma_config[THC_TXDMA])->is_enabled) {
|
|
dev_err_once(dev->dev, "The TxDMA channel is not enabled\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!buffer || buf_len <= 0) {
|
|
dev_err(dev->dev, "Invalid input parameters, buffer %p\n, buf_len %zu\n",
|
|
buffer, buf_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, &sts);
|
|
if (sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE) {
|
|
dev_err_once(dev->dev, "THC TxDMA is till active and can't start again\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl);
|
|
|
|
ret = write_dma_buffer(dev, buffer, buf_len);
|
|
if (ret)
|
|
goto end;
|
|
|
|
if (dev->perf_limit && !(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS)) {
|
|
ret = thc_interrupt_quiesce(dev, true);
|
|
if (ret)
|
|
goto end;
|
|
|
|
restore_interrupts = true;
|
|
}
|
|
|
|
dev->write_done = false;
|
|
|
|
dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_TXDMA]);
|
|
|
|
ret = wait_event_interruptible_timeout(dev->write_complete_wait, dev->write_done, 1 * HZ);
|
|
if (ret <= 0 || !dev->write_done) {
|
|
dev_err_once(dev->dev, "timeout for waiting TxDMA completion\n");
|
|
ret = -ETIMEDOUT;
|
|
goto end;
|
|
}
|
|
|
|
thc_dma_write_completion(dev);
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
return 0;
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
|
|
if (restore_interrupts)
|
|
ret = thc_interrupt_quiesce(dev, false);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dma_write, "INTEL_THC");
|