Dan Carpenter 0b0d62fa73 HID: intel-thc-hid: intel-thc: Fix error code in thc_i2c_subip_init()
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>
2025-01-13 10:47:12 +01:00

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, &ltr_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");