| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* Copyright (c) 2024 Intel Corporation */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/math.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) |
| { |
| static const 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, |
| }; |
| static const 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, &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, &dev->i2c_subip_regs[i]); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, "INTEL_THC"); |
| |
| /** |
| * thc_i2c_set_rx_max_size - Set I2C Rx transfer max input size |
| * @dev: The pointer of THC private device context |
| * @max_rx_size: Max input report packet size for input report |
| * |
| * Set @max_rx_size for I2C RxDMA max input size control feature. |
| * |
| * Return: 0 on success, other error codes on failure. |
| */ |
| int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size) |
| { |
| u32 val; |
| int ret; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| if (!max_rx_size) |
| return -EOPNOTSUPP; |
| |
| ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); |
| if (ret) |
| return ret; |
| |
| val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE, max_rx_size); |
| |
| ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); |
| if (ret) |
| return ret; |
| |
| dev->i2c_max_rx_size = max_rx_size; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_max_size, "INTEL_THC"); |
| |
| /** |
| * thc_i2c_rx_max_size_enable - Enable I2C Rx max input size control |
| * @dev: The pointer of THC private device context |
| * @enable: Enable max input size control or not |
| * |
| * Enable or disable I2C RxDMA max input size control feature. |
| * Max input size control only can be enabled after max input size |
| * was set by thc_i2c_set_rx_max_size(). |
| * |
| * Return: 0 on success, other error codes on failure. |
| */ |
| int thc_i2c_rx_max_size_enable(struct thc_device *dev, bool enable) |
| { |
| u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE_EN; |
| u32 val = enable ? mask : 0; |
| int ret; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| if (!dev->i2c_max_rx_size) |
| return -EOPNOTSUPP; |
| |
| ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val); |
| if (ret) |
| return ret; |
| |
| dev->i2c_max_rx_size_en = enable; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_max_size_enable, "INTEL_THC"); |
| |
| /** |
| * thc_i2c_set_rx_int_delay - Set I2C Rx input interrupt delay value |
| * @dev: The pointer of THC private device context |
| * @delay_us: Interrupt delay value, unit is us |
| * |
| * Set @delay_us for I2C RxDMA input interrupt delay feature. |
| * |
| * Return: 0 on success, other error codes on failure. |
| */ |
| int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us) |
| { |
| u32 val; |
| int ret; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| if (!delay_us) |
| return -EOPNOTSUPP; |
| |
| ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); |
| if (ret) |
| return ret; |
| |
| /* THC hardware counts at 10us unit */ |
| val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL, DIV_ROUND_UP(delay_us, 10)); |
| |
| ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); |
| if (ret) |
| return ret; |
| |
| dev->i2c_int_delay_us = delay_us; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_int_delay, "INTEL_THC"); |
| |
| /** |
| * thc_i2c_rx_int_delay_enable - Enable I2C Rx interrupt delay |
| * @dev: The pointer of THC private device context |
| * @enable: Enable interrupt delay or not |
| * |
| * Enable or disable I2C RxDMA input interrupt delay feature. |
| * Input interrupt delay can only be enabled after interrupt delay value |
| * was set by thc_i2c_set_rx_int_delay(). |
| * |
| * Return: 0 on success, other error codes on failure. |
| */ |
| int thc_i2c_rx_int_delay_enable(struct thc_device *dev, bool enable) |
| { |
| u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL_EN; |
| u32 val = enable ? mask : 0; |
| int ret; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| if (!dev->i2c_int_delay_us) |
| return -EOPNOTSUPP; |
| |
| ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val); |
| if (ret) |
| return ret; |
| |
| dev->i2c_int_delay_en = enable; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_int_delay_enable, "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"); |