mirror of
https://github.com/torvalds/linux.git
synced 2025-04-09 14:45:27 +00:00

Return -ENOMEM if the allocation fails. Don't return success. Fixes: 4228966def88 ("HID: intel-thc-hid: intel-thc: Add THC I2C config interfaces") Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org> Link: https://patch.msgid.link/eb4ea363-c3b7-4988-9ff5-5ed74bf47620@stanley.mountain Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
1579 lines
48 KiB
C
1579 lines
48 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/* Copyright (c) 2024 Intel Corporation */
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "intel-thc-dev.h"
|
|
#include "intel-thc-hw.h"
|
|
|
|
static int thc_regmap_read(void *context, unsigned int reg,
|
|
unsigned int *val)
|
|
{
|
|
struct thc_device *thc_ctx = context;
|
|
void __iomem *base = thc_ctx->mmio_addr;
|
|
|
|
*val = ioread32(base + reg);
|
|
return 0;
|
|
}
|
|
|
|
static int thc_regmap_write(void *context, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
struct thc_device *thc_ctx = context;
|
|
void __iomem *base = thc_ctx->mmio_addr;
|
|
|
|
iowrite32(val, base + reg);
|
|
return 0;
|
|
}
|
|
|
|
static const struct regmap_range thc_rw_ranges[] = {
|
|
regmap_reg_range(0x10, 0x14),
|
|
regmap_reg_range(0x1000, 0x1320),
|
|
};
|
|
|
|
static const struct regmap_access_table thc_rw_table = {
|
|
.yes_ranges = thc_rw_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(thc_rw_ranges),
|
|
};
|
|
|
|
static const struct regmap_config thc_regmap_cfg = {
|
|
.name = "thc_regmap_common",
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = 0x1320,
|
|
.reg_read = thc_regmap_read,
|
|
.reg_write = thc_regmap_write,
|
|
.cache_type = REGCACHE_NONE,
|
|
.fast_io = true,
|
|
.rd_table = &thc_rw_table,
|
|
.wr_table = &thc_rw_table,
|
|
.volatile_table = &thc_rw_table,
|
|
};
|
|
|
|
/**
|
|
* thc_clear_state - Clear THC hardware state
|
|
*
|
|
* @dev: The pointer of THC device structure
|
|
*/
|
|
static void thc_clear_state(const struct thc_device *dev)
|
|
{
|
|
u32 val;
|
|
|
|
/* Clear interrupt cause register */
|
|
val = THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY |
|
|
THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR |
|
|
THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR |
|
|
THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, val, val);
|
|
|
|
/* Clear interrupt error state */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_STALL,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_STALL);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_STALL,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_STALL);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS,
|
|
THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS,
|
|
THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS);
|
|
|
|
val = THC_M_PRT_INT_EN_TXN_ERR_INT_EN |
|
|
THC_M_PRT_INT_EN_FATAL_ERR_INT_EN |
|
|
THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET, val, val);
|
|
|
|
val = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
|
|
THC_M_PRT_SW_SEQ_STS_TSSDONE;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, val, val);
|
|
|
|
/* Clear RxDMA state */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS,
|
|
THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS);
|
|
|
|
/* Clear TxDMA state */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
|
|
THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL,
|
|
THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL);
|
|
|
|
val = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS |
|
|
THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS |
|
|
THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, val, val);
|
|
|
|
/* Reset all DMAs count */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_DB_CNT_1_OFFSET,
|
|
THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST,
|
|
THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CNT_OFFSET,
|
|
THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST,
|
|
THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST);
|
|
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);
|
|
|
|
/* Reset THC hardware sequence state */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_1_OFFSET,
|
|
THC_M_PRT_FRAME_DROP_CNT_1_RFDC,
|
|
THC_M_PRT_FRAME_DROP_CNT_1_RFDC);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_2_OFFSET,
|
|
THC_M_PRT_FRAME_DROP_CNT_2_RFDC,
|
|
THC_M_PRT_FRAME_DROP_CNT_2_RFDC);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_1_OFFSET,
|
|
THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST,
|
|
THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_2_OFFSET,
|
|
THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST,
|
|
THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_1_OFFSET,
|
|
THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST,
|
|
THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_2_OFFSET,
|
|
THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST,
|
|
THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET,
|
|
THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST,
|
|
THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET,
|
|
THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST,
|
|
THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_TX_FRM_CNT_OFFSET,
|
|
THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST,
|
|
THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_TXDMA_PKT_CNT_OFFSET,
|
|
THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST,
|
|
THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_1_OFFSET,
|
|
THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST,
|
|
THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_2_OFFSET,
|
|
THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST,
|
|
THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_1_OFFSET,
|
|
THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC,
|
|
THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_2_OFFSET,
|
|
THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC,
|
|
THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC);
|
|
}
|
|
|
|
/**
|
|
* thc_dev_init - Allocate and initialize the THC device structure
|
|
*
|
|
* @device: The pointer of device structure
|
|
* @mem_addr: The pointer of MMIO memory address
|
|
*
|
|
* Return: The thc_device pointer on success, NULL on failed.
|
|
*/
|
|
struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr)
|
|
{
|
|
struct thc_device *thc_dev;
|
|
int ret;
|
|
|
|
thc_dev = devm_kzalloc(device, sizeof(*thc_dev), GFP_KERNEL);
|
|
if (!thc_dev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
thc_dev->dev = device;
|
|
thc_dev->mmio_addr = mem_addr;
|
|
thc_dev->thc_regmap = devm_regmap_init(device, NULL, thc_dev, &thc_regmap_cfg);
|
|
if (IS_ERR(thc_dev->thc_regmap)) {
|
|
ret = PTR_ERR(thc_dev->thc_regmap);
|
|
dev_err_once(device, "Failed to init thc_regmap: %d\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
thc_clear_state(thc_dev);
|
|
|
|
mutex_init(&thc_dev->thc_bus_lock);
|
|
init_waitqueue_head(&thc_dev->write_complete_wait);
|
|
init_waitqueue_head(&thc_dev->swdma_complete_wait);
|
|
|
|
thc_dev->dma_ctx = thc_dma_init(thc_dev);
|
|
if (!thc_dev->dma_ctx) {
|
|
dev_err_once(device, "DMA context init failed\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
return thc_dev;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_dev_init, "INTEL_THC");
|
|
|
|
static int prepare_pio(const struct thc_device *dev, const u8 pio_op,
|
|
const u32 address, const u32 size)
|
|
{
|
|
u32 sts, ctrl, addr, mask;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts);
|
|
|
|
/* Check if THC previous PIO still in progress */
|
|
if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP) {
|
|
dev_err_once(dev->dev, "THC PIO is still busy!\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Clear error bit and complete bit in state register */
|
|
sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
|
|
THC_M_PRT_SW_SEQ_STS_TSSDONE;
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts);
|
|
|
|
/* Set PIO data size, opcode and interrupt capability */
|
|
ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, size) |
|
|
FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD, pio_op);
|
|
if (dev->pio_int_supported)
|
|
ctrl |= THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE;
|
|
|
|
mask = THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC |
|
|
THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD |
|
|
THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_CNTRL_OFFSET, mask, ctrl);
|
|
|
|
/* Set PIO target address */
|
|
addr = FIELD_PREP(THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR, address);
|
|
mask = THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, mask, addr);
|
|
return 0;
|
|
}
|
|
|
|
static void pio_start(const struct thc_device *dev,
|
|
u32 size_in_bytes, const u32 *buffer)
|
|
{
|
|
if (size_in_bytes && buffer)
|
|
regmap_bulk_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET,
|
|
buffer, size_in_bytes / sizeof(u32));
|
|
|
|
/* Enable Start bit */
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_CNTRL_OFFSET,
|
|
THC_M_PRT_SW_SEQ_CNTRL_TSSGO,
|
|
THC_M_PRT_SW_SEQ_CNTRL_TSSGO);
|
|
}
|
|
|
|
static int pio_complete(const struct thc_device *dev,
|
|
u32 *buffer, u32 *size)
|
|
{
|
|
u32 sts, ctrl;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts);
|
|
if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_ERR) {
|
|
dev_err_once(dev->dev, "PIO operation error\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (buffer && size) {
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &ctrl);
|
|
*size = FIELD_GET(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, ctrl);
|
|
|
|
regmap_bulk_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET,
|
|
buffer, *size / sizeof(u32));
|
|
}
|
|
|
|
sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | THC_M_PRT_SW_SEQ_STS_TSSDONE;
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts);
|
|
return 0;
|
|
}
|
|
|
|
static int pio_wait(const struct thc_device *dev)
|
|
{
|
|
u32 sts = 0;
|
|
int ret;
|
|
|
|
ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts,
|
|
!(sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP ||
|
|
!(sts & THC_M_PRT_SW_SEQ_STS_TSSDONE)),
|
|
THC_REGMAP_POLLING_INTERVAL_US, THC_PIO_DONE_TIMEOUT_US);
|
|
if (ret)
|
|
dev_err_once(dev->dev, "Timeout while polling PIO operation done\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* thc_tic_pio_read - Read data from touch device by PIO
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @address: Slave address for the PIO operation
|
|
* @size: Expected read data size
|
|
* @actual_size: The pointer of the actual data size read from touch device
|
|
* @buffer: The pointer of data buffer to store the data read from touch device
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_tic_pio_read(struct thc_device *dev, const u32 address,
|
|
const u32 size, u32 *actual_size, u32 *buffer)
|
|
{
|
|
u8 opcode;
|
|
int ret;
|
|
|
|
if (size <= 0 || !actual_size || !buffer) {
|
|
dev_err(dev->dev, "Invalid input parameters, size %u, actual_size %p, buffer %p\n",
|
|
size, actual_size, buffer);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
opcode = (dev->port_type == THC_PORT_TYPE_SPI) ?
|
|
THC_PIO_OP_SPI_TIC_READ : THC_PIO_OP_I2C_TIC_READ;
|
|
|
|
ret = prepare_pio(dev, opcode, address, size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
pio_start(dev, 0, NULL);
|
|
|
|
ret = pio_wait(dev);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
ret = pio_complete(dev, buffer, actual_size);
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_tic_pio_read, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_tic_pio_write - Write data to touch device by PIO
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @address: Slave address for the PIO operation
|
|
* @size: PIO write data size
|
|
* @buffer: The pointer of the write data buffer
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_tic_pio_write(struct thc_device *dev, const u32 address,
|
|
const u32 size, const u32 *buffer)
|
|
{
|
|
u8 opcode;
|
|
int ret;
|
|
|
|
if (size <= 0 || !buffer) {
|
|
dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n",
|
|
size, buffer);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
opcode = (dev->port_type == THC_PORT_TYPE_SPI) ?
|
|
THC_PIO_OP_SPI_TIC_WRITE : THC_PIO_OP_I2C_TIC_WRITE;
|
|
|
|
ret = prepare_pio(dev, opcode, address, size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
pio_start(dev, size, buffer);
|
|
|
|
ret = pio_wait(dev);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
ret = pio_complete(dev, NULL, NULL);
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_tic_pio_write_and_read - Write data followed by read data by PIO
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @address: Slave address for the PIO operation
|
|
* @write_size: PIO write data size
|
|
* @write_buffer: The pointer of the write data buffer
|
|
* @read_size: Expected PIO read data size
|
|
* @actual_size: The pointer of the actual read data size
|
|
* @read_buffer: The pointer of PIO read data buffer
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_tic_pio_write_and_read(struct thc_device *dev, const u32 address,
|
|
const u32 write_size, const u32 *write_buffer,
|
|
const u32 read_size, u32 *actual_size, u32 *read_buffer)
|
|
{
|
|
u32 i2c_ctrl, mask;
|
|
int ret;
|
|
|
|
if (dev->port_type == THC_PORT_TYPE_SPI) {
|
|
dev_err(dev->dev, "SPI port type doesn't support pio write and read!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
/* Config i2c PIO write and read sequence */
|
|
i2c_ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC, write_size);
|
|
mask = THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC;
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET,
|
|
mask, i2c_ctrl);
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET,
|
|
THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN,
|
|
THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN);
|
|
|
|
ret = prepare_pio(dev, THC_PIO_OP_I2C_TIC_WRITE_AND_READ, address, read_size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
pio_start(dev, write_size, write_buffer);
|
|
|
|
ret = pio_wait(dev);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
ret = pio_complete(dev, read_buffer, actual_size);
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write_and_read, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_interrupt_config - Configure THC interrupts
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*/
|
|
void thc_interrupt_config(struct thc_device *dev)
|
|
{
|
|
u32 mbits, mask, r_dma_ctrl_1;
|
|
|
|
/* Clear Error reporting interrupt status bits */
|
|
mbits = THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS |
|
|
THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_INT_STATUS_OFFSET,
|
|
mbits, mbits);
|
|
|
|
/* Enable Error Reporting Interrupts */
|
|
mbits = THC_M_PRT_INT_EN_TXN_ERR_INT_EN |
|
|
THC_M_PRT_INT_EN_FATAL_ERR_INT_EN |
|
|
THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_INT_EN_OFFSET,
|
|
mbits, mbits);
|
|
|
|
/* Clear PIO Interrupt status bits */
|
|
mbits = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
|
|
THC_M_PRT_SW_SEQ_STS_TSSDONE;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_STS_OFFSET,
|
|
mbits, mbits);
|
|
|
|
/* Read Interrupts */
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
&r_dma_ctrl_1);
|
|
/* Disable RxDMA1 */
|
|
r_dma_ctrl_1 &= ~THC_M_PRT_READ_DMA_CNTRL_IE_EOF;
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
r_dma_ctrl_1);
|
|
|
|
/* Ack EOF Interrupt RxDMA1 */
|
|
mbits = THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS;
|
|
/* Ack NonDMA Interrupt */
|
|
mbits |= THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
|
|
mbits, mbits);
|
|
|
|
/* Ack EOF Interrupt RxDMA2 */
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
|
|
THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
|
|
|
|
/* Write Interrupts */
|
|
/* Disable TxDMA */
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
|
|
THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL,
|
|
0);
|
|
|
|
/* Clear TxDMA interrupt status bits */
|
|
mbits = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS;
|
|
mbits |= THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS;
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_WRITE_INT_STS_OFFSET,
|
|
mbits, mbits);
|
|
|
|
/* Enable Non-DMA device inband interrupt */
|
|
r_dma_ctrl_1 |= THC_M_PRT_READ_DMA_CNTRL_IE_NDDI;
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
|
|
r_dma_ctrl_1);
|
|
|
|
if (dev->port_type == THC_PORT_TYPE_SPI) {
|
|
/* Edge triggered interrupt */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
|
|
THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN,
|
|
THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN);
|
|
} else {
|
|
/* Level triggered interrupt */
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
|
|
THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN, 0);
|
|
|
|
mbits = THC_M_PRT_INT_EN_THC_I2C_IC_MST_ON_HOLD_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_SCL_STUCK_AT_LOW_DET_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_TX_ABRT_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_TX_OVER_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_RX_FULL_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_RX_OVER_INT_EN |
|
|
THC_M_PRT_INT_EN_THC_I2C_IC_RX_UNDER_INT_EN;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET,
|
|
mbits, mbits);
|
|
}
|
|
|
|
thc_set_pio_interrupt_support(dev, false);
|
|
|
|
/* HIDSPI specific settings */
|
|
if (dev->port_type == THC_PORT_TYPE_SPI) {
|
|
mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET,
|
|
THC_BIT_OFFSET_INTERRUPT_TYPE) |
|
|
FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN,
|
|
THC_BIT_LENGTH_INTERRUPT_TYPE) |
|
|
FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET,
|
|
THC_BIT_OFFSET_LAST_FRAGMENT_FLAG) |
|
|
FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL,
|
|
THC_BITMASK_INVALID_TYPE_DATA);
|
|
mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET |
|
|
THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN |
|
|
THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET |
|
|
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 = FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET,
|
|
THC_BIT_OFFSET_MICROFRAME_SIZE) |
|
|
FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN,
|
|
THC_BIT_LENGTH_MICROFRAME_SIZE) |
|
|
FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT,
|
|
THC_UNIT_MICROFRAME_SIZE) |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL;
|
|
mask = THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE |
|
|
THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CFG_2_OFFSET,
|
|
mask, mbits);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_interrupt_config, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_int_trigger_type_select - Select THC interrupt trigger type
|
|
*
|
|
* @dev: the pointer of THC private device context
|
|
* @edge_trigger: determine the interrupt is edge triggered or level triggered
|
|
*/
|
|
void thc_int_trigger_type_select(struct thc_device *dev, bool edge_trigger)
|
|
{
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
|
|
THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN,
|
|
edge_trigger ? THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN : 0);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_int_trigger_type_select, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_interrupt_enable - Enable or disable THC interrupt
|
|
*
|
|
* @dev: the pointer of THC private device context
|
|
* @int_enable: the flag to control THC interrupt enable or disable
|
|
*/
|
|
void thc_interrupt_enable(struct thc_device *dev, bool int_enable)
|
|
{
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET,
|
|
THC_M_PRT_INT_EN_GBL_INT_EN,
|
|
int_enable ? THC_M_PRT_INT_EN_GBL_INT_EN : 0);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_interrupt_enable, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_interrupt_quiesce - Quiesce or unquiesce external touch device interrupt
|
|
*
|
|
* @dev: the pointer of THC private device context
|
|
* @int_quiesce: the flag to determine quiesce or unquiesce device interrupt
|
|
*
|
|
* Return: 0 on success, other error codes on failed
|
|
*/
|
|
int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce)
|
|
{
|
|
u32 ctrl;
|
|
int ret;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl);
|
|
if (!(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && !int_quiesce) {
|
|
dev_warn(dev->dev, "THC interrupt already unquiesce\n");
|
|
return 0;
|
|
}
|
|
|
|
if ((ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && int_quiesce) {
|
|
dev_warn(dev->dev, "THC interrupt already quiesce\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Quiesce device interrupt - Set quiesce bit and waiting for THC HW to ACK */
|
|
if (int_quiesce)
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET,
|
|
THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN,
|
|
THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN);
|
|
|
|
ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, ctrl,
|
|
ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS,
|
|
THC_REGMAP_POLLING_INTERVAL_US, THC_QUIESCE_EN_TIMEOUT_US);
|
|
if (ret) {
|
|
dev_err_once(dev->dev,
|
|
"Timeout while waiting THC idle, target quiesce state = %s\n",
|
|
int_quiesce ? "true" : "false");
|
|
return ret;
|
|
}
|
|
|
|
/* Unquiesce device interrupt - Clear the quiesce bit */
|
|
if (!int_quiesce)
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET,
|
|
THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN, 0);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_interrupt_quiesce, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_set_pio_interrupt_support - Determine PIO interrupt is supported or not
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @supported: The flag to determine enabling PIO interrupt or not
|
|
*/
|
|
void thc_set_pio_interrupt_support(struct thc_device *dev, bool supported)
|
|
{
|
|
dev->pio_int_supported = supported;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_set_pio_interrupt_support, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_ltr_config - Configure THC Latency Tolerance Reporting(LTR) settings
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @active_ltr_us: active LTR value, unit is us
|
|
* @lp_ltr_us: low power LTR value, unit is us
|
|
*/
|
|
void thc_ltr_config(struct thc_device *dev, u32 active_ltr_us, u32 lp_ltr_us)
|
|
{
|
|
u32 active_ltr_scale, lp_ltr_scale, ltr_ctrl, ltr_mask, orig, tmp;
|
|
|
|
if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 &&
|
|
active_ltr_us < THC_LTR_MAX_VAL_SCALE_3) {
|
|
active_ltr_scale = THC_LTR_SCALE_3;
|
|
active_ltr_us = active_ltr_us >> 5;
|
|
} else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 &&
|
|
active_ltr_us < THC_LTR_MAX_VAL_SCALE_4) {
|
|
active_ltr_scale = THC_LTR_SCALE_4;
|
|
active_ltr_us = active_ltr_us >> 10;
|
|
} else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 &&
|
|
active_ltr_us < THC_LTR_MAX_VAL_SCALE_5) {
|
|
active_ltr_scale = THC_LTR_SCALE_5;
|
|
active_ltr_us = active_ltr_us >> 15;
|
|
} else {
|
|
active_ltr_scale = THC_LTR_SCALE_2;
|
|
}
|
|
|
|
if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 &&
|
|
lp_ltr_us < THC_LTR_MAX_VAL_SCALE_3) {
|
|
lp_ltr_scale = THC_LTR_SCALE_3;
|
|
lp_ltr_us = lp_ltr_us >> 5;
|
|
} else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 &&
|
|
lp_ltr_us < THC_LTR_MAX_VAL_SCALE_4) {
|
|
lp_ltr_scale = THC_LTR_SCALE_4;
|
|
lp_ltr_us = lp_ltr_us >> 10;
|
|
} else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 &&
|
|
lp_ltr_us < THC_LTR_MAX_VAL_SCALE_5) {
|
|
lp_ltr_scale = THC_LTR_SCALE_5;
|
|
lp_ltr_us = lp_ltr_us >> 15;
|
|
} else {
|
|
lp_ltr_scale = THC_LTR_SCALE_2;
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, &orig);
|
|
ltr_ctrl = FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_VAL, active_ltr_us) |
|
|
FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE, active_ltr_scale) |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
|
|
FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_VAL, lp_ltr_us) |
|
|
FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_SCALE, lp_ltr_scale) |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_REQ;
|
|
|
|
ltr_mask = THC_M_CMN_LTR_CTRL_ACT_LTR_VAL |
|
|
THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_VAL |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_SCALE |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_REQ |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_EN;
|
|
|
|
tmp = orig & ~ltr_mask;
|
|
tmp |= ltr_ctrl & ltr_mask;
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, tmp);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_ltr_config, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_change_ltr_mode - Change THC LTR mode
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @ltr_mode: LTR mode(active or low power)
|
|
*/
|
|
void thc_change_ltr_mode(struct thc_device *dev, u32 ltr_mode)
|
|
{
|
|
if (ltr_mode == THC_LTR_MODE_ACTIVE) {
|
|
regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_EN, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN,
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN);
|
|
return;
|
|
}
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN, 0);
|
|
regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_EN,
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_EN);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_change_ltr_mode, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_ltr_unconfig - Unconfigure THC Latency Tolerance Reporting(LTR) settings
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*/
|
|
void thc_ltr_unconfig(struct thc_device *dev)
|
|
{
|
|
u32 ltr_ctrl, bits_clear;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, <r_ctrl);
|
|
bits_clear = THC_M_CMN_LTR_CTRL_LP_LTR_EN |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
|
|
THC_M_CMN_LTR_CTRL_LP_LTR_REQ |
|
|
THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ;
|
|
|
|
ltr_ctrl &= ~bits_clear;
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, ltr_ctrl);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_ltr_unconfig, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_int_cause_read - Read interrupt cause register value
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: The interrupt cause register value
|
|
*/
|
|
u32 thc_int_cause_read(struct thc_device *dev)
|
|
{
|
|
u32 int_cause;
|
|
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_DEV_INT_CAUSE_REG_VAL_OFFSET, &int_cause);
|
|
|
|
return int_cause;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_int_cause_read, "INTEL_THC");
|
|
|
|
static void thc_print_txn_error_cause(const struct thc_device *dev)
|
|
{
|
|
bool known_error = false;
|
|
u32 cause = 0;
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &cause);
|
|
|
|
if (cause & THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR) {
|
|
dev_err(dev->dev, "TXN Error: Invalid PRD Entry\n");
|
|
known_error = true;
|
|
}
|
|
if (cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR) {
|
|
dev_err(dev->dev, "TXN Error: THC Buffer Overrun\n");
|
|
known_error = true;
|
|
}
|
|
if (cause & THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR) {
|
|
dev_err(dev->dev, "TXN Error: Frame Babble\n");
|
|
known_error = true;
|
|
}
|
|
if (cause & THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY) {
|
|
dev_err(dev->dev, "TXN Error: Invalid Device Register Setting\n");
|
|
known_error = true;
|
|
}
|
|
|
|
/* Clear interrupt status bits */
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, cause);
|
|
|
|
if (!known_error)
|
|
dev_err(dev->dev, "TXN Error does not match any known value: 0x%X\n",
|
|
cause);
|
|
}
|
|
|
|
/**
|
|
* thc_interrupt_handler - Handle THC interrupts
|
|
*
|
|
* THC interrupts include several types: external touch device (TIC) non-DMA
|
|
* interrupts, PIO completion interrupts, DMA interrtups, I2C subIP raw
|
|
* interrupts and error interrupts.
|
|
*
|
|
* This is a help function for interrupt processing, it detects interrupt
|
|
* type, clear the interrupt status bit and return the interrupt type to caller
|
|
* for future processing.
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: The combined flag for interrupt type
|
|
*/
|
|
int thc_interrupt_handler(struct thc_device *dev)
|
|
{
|
|
u32 read_sts_1, read_sts_2, read_sts_sw, write_sts;
|
|
u32 int_sts, err_cause, seq_cntrl, seq_sts;
|
|
int interrupt_type = 0;
|
|
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, &read_sts_1);
|
|
|
|
if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS) {
|
|
dev_dbg(dev->dev, "THC non-DMA device interrupt\n");
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
|
|
NONDMA_INT_STS_BIT);
|
|
|
|
interrupt_type |= BIT(THC_NONDMA_INT);
|
|
|
|
return interrupt_type;
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, &int_sts);
|
|
|
|
if (int_sts & THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS) {
|
|
dev_err(dev->dev, "THC transaction error, int_sts: 0x%08X\n", int_sts);
|
|
thc_print_txn_error_cause(dev);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
TXN_ERR_INT_STS_BIT);
|
|
|
|
interrupt_type |= BIT(THC_TXN_ERR_INT);
|
|
|
|
return interrupt_type;
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &err_cause);
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, &read_sts_2);
|
|
|
|
if (err_cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR ||
|
|
read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS ||
|
|
read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS) {
|
|
dev_err(dev->dev, "Buffer overrun or RxDMA engine stalled!\n");
|
|
thc_print_txn_error_cause(dev);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_STALL_STS);
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
|
|
THC_M_PRT_READ_DMA_INT_STS_STALL_STS);
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET,
|
|
THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR);
|
|
|
|
interrupt_type |= BIT(THC_TXN_ERR_INT);
|
|
|
|
return interrupt_type;
|
|
}
|
|
|
|
if (int_sts & THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS) {
|
|
dev_err_once(dev->dev, "THC FATAL error, int_sts: 0x%08X\n", int_sts);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
TXN_FATAL_INT_STS_BIT);
|
|
|
|
interrupt_type |= BIT(THC_FATAL_ERR_INT);
|
|
|
|
return interrupt_type;
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &seq_cntrl);
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_SW_SEQ_STS_OFFSET, &seq_sts);
|
|
|
|
if (seq_cntrl & THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE &&
|
|
seq_sts & THC_M_PRT_SW_SEQ_STS_TSSDONE) {
|
|
dev_dbg(dev->dev, "THC_SS_CD_IE and TSSDONE are set\n");
|
|
interrupt_type |= BIT(THC_PIO_DONE_INT);
|
|
}
|
|
|
|
if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) {
|
|
dev_dbg(dev->dev, "Got RxDMA1 Read Interrupt\n");
|
|
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, read_sts_1);
|
|
|
|
interrupt_type |= BIT(THC_RXDMA1_INT);
|
|
}
|
|
|
|
if (read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) {
|
|
dev_dbg(dev->dev, "Got RxDMA2 Read Interrupt\n");
|
|
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, read_sts_2);
|
|
|
|
interrupt_type |= BIT(THC_RXDMA2_INT);
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, &read_sts_sw);
|
|
|
|
if (read_sts_sw & THC_M_PRT_READ_DMA_INT_STS_DMACPL_STS) {
|
|
dev_dbg(dev->dev, "Got SwDMA Read Interrupt\n");
|
|
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, read_sts_sw);
|
|
|
|
dev->swdma_done = true;
|
|
wake_up_interruptible(&dev->swdma_complete_wait);
|
|
|
|
interrupt_type |= BIT(THC_SWDMA_INT);
|
|
}
|
|
|
|
regmap_read(dev->thc_regmap,
|
|
THC_M_PRT_WRITE_INT_STS_OFFSET, &write_sts);
|
|
|
|
if (write_sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS) {
|
|
dev_dbg(dev->dev, "Got TxDMA Write complete Interrupt\n");
|
|
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_WRITE_INT_STS_OFFSET, write_sts);
|
|
|
|
dev->write_done = true;
|
|
wake_up_interruptible(&dev->write_complete_wait);
|
|
|
|
interrupt_type |= BIT(THC_TXDMA_INT);
|
|
}
|
|
|
|
if (int_sts & THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS) {
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
|
|
THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS);
|
|
interrupt_type |= BIT(THC_I2CSUBIP_INT);
|
|
}
|
|
|
|
if (!interrupt_type)
|
|
interrupt_type |= BIT(THC_UNKNOWN_INT);
|
|
|
|
return interrupt_type;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_interrupt_handler, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_port_select - Set THC port type
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @port_type: THC port type to use for current device
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_port_select(struct thc_device *dev, enum thc_port_type port_type)
|
|
{
|
|
u32 ctrl, mask;
|
|
|
|
if (port_type == THC_PORT_TYPE_SPI) {
|
|
dev_dbg(dev->dev, "Set THC port type to SPI\n");
|
|
dev->port_type = THC_PORT_TYPE_SPI;
|
|
|
|
/* Enable delay of CS assertion and set to default value */
|
|
ctrl = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN |
|
|
FIELD_PREP(THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL,
|
|
THC_CSA_CK_DELAY_VAL_DEFAULT);
|
|
mask = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN |
|
|
THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL;
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET,
|
|
mask, ctrl);
|
|
} else if (port_type == THC_PORT_TYPE_I2C) {
|
|
dev_dbg(dev->dev, "Set THC port type to I2C\n");
|
|
dev->port_type = THC_PORT_TYPE_I2C;
|
|
|
|
/* Set THC transition arbitration policy to frame boundary for I2C */
|
|
ctrl = FIELD_PREP(THC_M_PRT_CONTROL_THC_ARB_POLICY,
|
|
THC_ARB_POLICY_FRAME_BOUNDARY);
|
|
mask = THC_M_PRT_CONTROL_THC_ARB_POLICY;
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl);
|
|
} else {
|
|
dev_err(dev->dev, "unsupported THC port type: %d\n", port_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctrl = FIELD_PREP(THC_M_PRT_CONTROL_PORT_TYPE, port_type);
|
|
mask = THC_M_PRT_CONTROL_PORT_TYPE;
|
|
|
|
regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_port_select, "INTEL_THC");
|
|
|
|
#define THC_SPI_FREQUENCY_7M 7812500
|
|
#define THC_SPI_FREQUENCY_15M 15625000
|
|
#define THC_SPI_FREQUENCY_17M 17857100
|
|
#define THC_SPI_FREQUENCY_20M 20833000
|
|
#define THC_SPI_FREQUENCY_25M 25000000
|
|
#define THC_SPI_FREQUENCY_31M 31250000
|
|
#define THC_SPI_FREQUENCY_41M 41666700
|
|
|
|
#define THC_SPI_LOW_FREQUENCY THC_SPI_FREQUENCY_17M
|
|
|
|
static u8 thc_get_spi_freq_div_val(struct thc_device *dev, u32 spi_freq_val)
|
|
{
|
|
int frequency[] = {
|
|
THC_SPI_FREQUENCY_7M,
|
|
THC_SPI_FREQUENCY_15M,
|
|
THC_SPI_FREQUENCY_17M,
|
|
THC_SPI_FREQUENCY_20M,
|
|
THC_SPI_FREQUENCY_25M,
|
|
THC_SPI_FREQUENCY_31M,
|
|
THC_SPI_FREQUENCY_41M,
|
|
};
|
|
u8 frequency_div[] = {
|
|
THC_SPI_FRQ_DIV_2,
|
|
THC_SPI_FRQ_DIV_1,
|
|
THC_SPI_FRQ_DIV_7,
|
|
THC_SPI_FRQ_DIV_6,
|
|
THC_SPI_FRQ_DIV_5,
|
|
THC_SPI_FRQ_DIV_4,
|
|
THC_SPI_FRQ_DIV_3,
|
|
};
|
|
int size = ARRAY_SIZE(frequency);
|
|
u32 closest_freq;
|
|
u8 freq_div;
|
|
int i;
|
|
|
|
for (i = size - 1; i >= 0; i--)
|
|
if ((int)spi_freq_val - frequency[i] >= 0)
|
|
break;
|
|
|
|
if (i < 0) {
|
|
dev_err_once(dev->dev, "Not supported SPI frequency %d\n", spi_freq_val);
|
|
return THC_SPI_FRQ_RESERVED;
|
|
}
|
|
|
|
closest_freq = frequency[i];
|
|
freq_div = frequency_div[i];
|
|
|
|
dev_dbg(dev->dev,
|
|
"Setting SPI frequency: spi_freq_val = %u, Closest freq = %u\n",
|
|
spi_freq_val, closest_freq);
|
|
|
|
return freq_div;
|
|
}
|
|
|
|
/**
|
|
* thc_spi_read_config - Configure SPI bus read attributes
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @spi_freq_val: SPI read frequecy value
|
|
* @io_mode: SPI read IO mode
|
|
* @opcode: Read opcode
|
|
* @spi_rd_mps: SPI read max packet size
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val,
|
|
u32 io_mode, u32 opcode, u32 spi_rd_mps)
|
|
{
|
|
bool is_low_freq = false;
|
|
u32 cfg, mask;
|
|
u8 freq_div;
|
|
|
|
freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val);
|
|
if (freq_div == THC_SPI_FRQ_RESERVED)
|
|
return -EINVAL;
|
|
|
|
if (spi_freq_val < THC_SPI_LOW_FREQUENCY)
|
|
is_low_freq = true;
|
|
|
|
cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCRF, freq_div) |
|
|
FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TRMODE, io_mode) |
|
|
(is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) |
|
|
FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_RD_MPS, spi_rd_mps);
|
|
mask = THC_M_PRT_SPI_CFG_SPI_TCRF |
|
|
THC_M_PRT_SPI_CFG_SPI_TRMODE |
|
|
THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN |
|
|
THC_M_PRT_SPI_CFG_SPI_RD_MPS;
|
|
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SPI_CFG_OFFSET, mask, cfg);
|
|
|
|
if (io_mode == THC_QUAD_IO)
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode);
|
|
else if (io_mode == THC_DUAL_IO)
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode);
|
|
else
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, opcode);
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SPI_DMARD_OPCODE_OFFSET, opcode);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_spi_read_config, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_spi_write_config - Configure SPI bus write attributes
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @spi_freq_val: SPI write frequecy value
|
|
* @io_mode: SPI write IO mode
|
|
* @opcode: Write opcode
|
|
* @spi_wr_mps: SPI write max packet size
|
|
* @perf_limit: Performance limitation in unit of 10us
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val,
|
|
u32 io_mode, u32 opcode, u32 spi_wr_mps,
|
|
u32 perf_limit)
|
|
{
|
|
bool is_low_freq = false;
|
|
u32 cfg, mask;
|
|
u8 freq_div;
|
|
|
|
freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val);
|
|
if (freq_div == THC_SPI_FRQ_RESERVED)
|
|
return -EINVAL;
|
|
|
|
if (spi_freq_val < THC_SPI_LOW_FREQUENCY)
|
|
is_low_freq = true;
|
|
|
|
cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCWF, freq_div) |
|
|
FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TWMODE, io_mode) |
|
|
(is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) |
|
|
FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_WR_MPS, spi_wr_mps);
|
|
mask = THC_M_PRT_SPI_CFG_SPI_TCWF |
|
|
THC_M_PRT_SPI_CFG_SPI_TWMODE |
|
|
THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN |
|
|
THC_M_PRT_SPI_CFG_SPI_WR_MPS;
|
|
|
|
regmap_write_bits(dev->thc_regmap,
|
|
THC_M_PRT_SPI_CFG_OFFSET, mask, cfg);
|
|
|
|
if (io_mode == THC_QUAD_IO)
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode);
|
|
else if (io_mode == THC_DUAL_IO)
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode);
|
|
else
|
|
opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode);
|
|
|
|
regmap_write(dev->thc_regmap, THC_M_PRT_SPI_WR_OPCODE_OFFSET, opcode);
|
|
|
|
dev->perf_limit = perf_limit;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_spi_write_config, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_spi_input_output_address_config - Configure SPI input and output addresses
|
|
*
|
|
* @dev: the pointer of THC private device context
|
|
* @input_hdr_addr: input report header address
|
|
* @input_bdy_addr: input report body address
|
|
* @output_addr: output report address
|
|
*/
|
|
void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr,
|
|
u32 input_bdy_addr, u32 output_addr)
|
|
{
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_DEV_INT_CAUSE_ADDR_OFFSET, input_hdr_addr);
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_RD_BULK_ADDR_1_OFFSET, input_bdy_addr);
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_RD_BULK_ADDR_2_OFFSET, input_bdy_addr);
|
|
regmap_write(dev->thc_regmap,
|
|
THC_M_PRT_WR_BULK_ADDR_OFFSET, output_addr);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_spi_input_output_address_config, "INTEL_THC");
|
|
|
|
static int thc_i2c_subip_pio_read(struct thc_device *dev, const u32 address,
|
|
u32 *size, u32 *buffer)
|
|
{
|
|
int ret;
|
|
|
|
if (!size || *size == 0 || !buffer) {
|
|
dev_err(dev->dev, "Invalid input parameters, size %p, buffer %p\n",
|
|
size, buffer);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_READ, address, *size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
pio_start(dev, 0, NULL);
|
|
|
|
ret = pio_wait(dev);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
ret = pio_complete(dev, buffer, size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
|
|
if (ret)
|
|
dev_err_once(dev->dev, "Read THC I2C SubIP register failed %d, offset %u\n",
|
|
ret, address);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address,
|
|
const u32 size, const u32 *buffer)
|
|
{
|
|
int ret;
|
|
|
|
if (size == 0 || !buffer) {
|
|
dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n",
|
|
size, buffer);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
|
return -EINTR;
|
|
|
|
ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_WRITE, address, size);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
pio_start(dev, size, buffer);
|
|
|
|
ret = pio_wait(dev);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
ret = pio_complete(dev, NULL, NULL);
|
|
if (ret < 0)
|
|
goto end;
|
|
|
|
end:
|
|
mutex_unlock(&dev->thc_bus_lock);
|
|
|
|
if (ret)
|
|
dev_err_once(dev->dev, "Write THC I2C SubIP register failed %d, offset %u\n",
|
|
ret, address);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define I2C_SUBIP_CON_DEFAULT 0x663
|
|
#define I2C_SUBIP_INT_MASK_DEFAULT 0x7FFF
|
|
#define I2C_SUBIP_RX_TL_DEFAULT 62
|
|
#define I2C_SUBIP_TX_TL_DEFAULT 0
|
|
#define I2C_SUBIP_DMA_TDLR_DEFAULT 7
|
|
#define I2C_SUBIP_DMA_RDLR_DEFAULT 7
|
|
|
|
static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
|
|
const u32 hcnt, const u32 lcnt)
|
|
{
|
|
u32 hcnt_offset, lcnt_offset;
|
|
u32 val;
|
|
int ret;
|
|
|
|
switch (speed) {
|
|
case THC_I2C_STANDARD:
|
|
hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET;
|
|
lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET;
|
|
break;
|
|
|
|
case THC_I2C_FAST_AND_PLUS:
|
|
hcnt_offset = THC_I2C_IC_FS_SCL_HCNT_OFFSET;
|
|
lcnt_offset = THC_I2C_IC_FS_SCL_LCNT_OFFSET;
|
|
break;
|
|
|
|
case THC_I2C_HIGH_SPEED:
|
|
hcnt_offset = THC_I2C_IC_HS_SCL_HCNT_OFFSET;
|
|
lcnt_offset = THC_I2C_IC_HS_SCL_LCNT_OFFSET;
|
|
break;
|
|
|
|
default:
|
|
dev_err_once(dev->dev, "Unsupported i2c speed %d\n", speed);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED;
|
|
val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed);
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 i2c_subip_regs[] = {
|
|
THC_I2C_IC_CON_OFFSET,
|
|
THC_I2C_IC_TAR_OFFSET,
|
|
THC_I2C_IC_INTR_MASK_OFFSET,
|
|
THC_I2C_IC_RX_TL_OFFSET,
|
|
THC_I2C_IC_TX_TL_OFFSET,
|
|
THC_I2C_IC_DMA_CR_OFFSET,
|
|
THC_I2C_IC_DMA_TDLR_OFFSET,
|
|
THC_I2C_IC_DMA_RDLR_OFFSET,
|
|
THC_I2C_IC_SS_SCL_HCNT_OFFSET,
|
|
THC_I2C_IC_SS_SCL_LCNT_OFFSET,
|
|
THC_I2C_IC_FS_SCL_HCNT_OFFSET,
|
|
THC_I2C_IC_FS_SCL_LCNT_OFFSET,
|
|
THC_I2C_IC_HS_SCL_HCNT_OFFSET,
|
|
THC_I2C_IC_HS_SCL_LCNT_OFFSET,
|
|
THC_I2C_IC_ENABLE_OFFSET,
|
|
};
|
|
|
|
/**
|
|
* thc_i2c_subip_init - Initialize and configure THC I2C subsystem
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
* @target_address: Slave address of touch device (TIC)
|
|
* @speed: I2C bus frequency speed mode
|
|
* @hcnt: I2C clock SCL high count
|
|
* @lcnt: I2C clock SCL low count
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
|
|
const u32 speed, const u32 hcnt, const u32 lcnt)
|
|
{
|
|
u32 read_size = sizeof(u32);
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val &= ~THC_I2C_IC_ENABLE_ENABLE;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val &= ~THC_I2C_IC_TAR_IC_TAR;
|
|
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address);
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_INT_MASK_DEFAULT;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_INTR_MASK_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_RX_TL_DEFAULT;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_RX_TL_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_TX_TL_DEFAULT;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TX_TL_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = THC_I2C_IC_DMA_CR_RDMAE | THC_I2C_IC_DMA_CR_TDMAE;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_CR_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_DMA_TDLR_DEFAULT;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_TDLR_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = I2C_SUBIP_DMA_RDLR_DEFAULT;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_RDLR_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val |= THC_I2C_IC_ENABLE_ENABLE;
|
|
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev->i2c_subip_regs = devm_kzalloc(dev->dev, sizeof(i2c_subip_regs), GFP_KERNEL);
|
|
if (!dev->i2c_subip_regs)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_init, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_i2c_subip_regs_save - Save THC I2C sub-subsystem register values to THC device context
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_i2c_subip_regs_save(struct thc_device *dev)
|
|
{
|
|
int ret;
|
|
u32 read_size = sizeof(u32);
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) {
|
|
ret = thc_i2c_subip_pio_read(dev, i2c_subip_regs[i],
|
|
&read_size, (u32 *)&dev->i2c_subip_regs + i);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_save, "INTEL_THC");
|
|
|
|
/**
|
|
* thc_i2c_subip_regs_restore - Restore THC I2C subsystem registers from THC device context
|
|
*
|
|
* @dev: The pointer of THC private device context
|
|
*
|
|
* Return: 0 on success, other error codes on failed.
|
|
*/
|
|
int thc_i2c_subip_regs_restore(struct thc_device *dev)
|
|
{
|
|
int ret;
|
|
u32 write_size = sizeof(u32);
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) {
|
|
ret = thc_i2c_subip_pio_write(dev, i2c_subip_regs[i],
|
|
write_size, (u32 *)&dev->i2c_subip_regs + i);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, "INTEL_THC");
|
|
|
|
MODULE_AUTHOR("Xinpeng Sun <xinpeng.sun@intel.com>");
|
|
MODULE_AUTHOR("Even Xu <even.xu@intel.com>");
|
|
|
|
MODULE_DESCRIPTION("Intel(R) Intel THC Hardware Driver");
|
|
MODULE_LICENSE("GPL");
|