mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

After driver module is removed and during re-install stage, if there
is continueous user touching on the screen, it is a risk impacting
THC hardware initialization which causes driver installation failure.
This patch enhances this flow by quiescing the external touch
interrupt after driver is removed which keeps THC hardware
ignore external interrupt during this remove and re-install stage.
Signed-off-by: Even Xu <even.xu@intel.com>
Tested-by: Rui Zhang <rui1.zhang@intel.com>
Fixes: 66b59bfce6
("HID: intel-thc-hid: intel-quicki2c: Complete THC QuickI2C driver")
Signed-off-by: Jiri Kosina <jkosina@suse.com>
1022 lines
26 KiB
C
1022 lines
26 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/* Copyright (c) 2024 Intel Corporation */
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irqreturn.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <linux/gpio/consumer.h>
|
|
|
|
#include "intel-thc-dev.h"
|
|
#include "intel-thc-hw.h"
|
|
#include "intel-thc-wot.h"
|
|
|
|
#include "quicki2c-dev.h"
|
|
#include "quicki2c-hid.h"
|
|
#include "quicki2c-protocol.h"
|
|
|
|
static struct quicki2c_ddata ptl_ddata = {
|
|
.max_detect_size = MAX_RX_DETECT_SIZE_PTL,
|
|
};
|
|
|
|
/* THC QuickI2C ACPI method to get device properties */
|
|
/* HIDI2C device method */
|
|
static guid_t i2c_hid_guid =
|
|
GUID_INIT(0x3cdff6f7, 0x4267, 0x4555, 0xad, 0x05, 0xb3, 0x0a, 0x3d, 0x89, 0x38, 0xde);
|
|
|
|
/* platform method */
|
|
static guid_t thc_platform_guid =
|
|
GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38);
|
|
|
|
/* QuickI2C Wake-on-Touch GPIO resource */
|
|
static const struct acpi_gpio_params wake_gpio = { 0, 0, true };
|
|
|
|
static const struct acpi_gpio_mapping quicki2c_gpios[] = {
|
|
{ "wake-on-touch", &wake_gpio, 1 },
|
|
{ }
|
|
};
|
|
|
|
/**
|
|
* quicki2c_acpi_get_dsm_property - Query device ACPI DSM parameter
|
|
* @adev: Point to ACPI device
|
|
* @guid: ACPI method's guid
|
|
* @rev: ACPI method's revision
|
|
* @func: ACPI method's function number
|
|
* @type: ACPI parameter's data type
|
|
* @prop_buf: Point to return buffer
|
|
*
|
|
* This is a helper function for device to query its ACPI DSM parameters.
|
|
*
|
|
* Return: 0 if success or ENODEV on failure.
|
|
*/
|
|
static int quicki2c_acpi_get_dsm_property(struct acpi_device *adev, const guid_t *guid,
|
|
u64 rev, u64 func, acpi_object_type type, void *prop_buf)
|
|
{
|
|
acpi_handle handle = acpi_device_handle(adev);
|
|
union acpi_object *obj;
|
|
|
|
obj = acpi_evaluate_dsm_typed(handle, guid, rev, func, NULL, type);
|
|
if (!obj) {
|
|
acpi_handle_err(handle,
|
|
"Error _DSM call failed, rev: %d, func: %d, type: %d\n",
|
|
(int)rev, (int)func, (int)type);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (type == ACPI_TYPE_INTEGER)
|
|
*(u32 *)prop_buf = (u32)obj->integer.value;
|
|
else if (type == ACPI_TYPE_BUFFER)
|
|
memcpy(prop_buf, obj->buffer.pointer, obj->buffer.length);
|
|
|
|
ACPI_FREE(obj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_acpi_get_dsd_property - Query device ACPI DSD parameter
|
|
* @adev: Point to ACPI device
|
|
* @dsd_method_name: ACPI method's property name
|
|
* @type: ACPI parameter's data type
|
|
* @prop_buf: Point to return buffer
|
|
*
|
|
* This is a helper function for device to query its ACPI DSD parameters.
|
|
*
|
|
* Return: 0 if success or ENODEV on failed.
|
|
*/
|
|
static int quicki2c_acpi_get_dsd_property(struct acpi_device *adev, acpi_string dsd_method_name,
|
|
acpi_object_type type, void *prop_buf)
|
|
{
|
|
acpi_handle handle = acpi_device_handle(adev);
|
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
union acpi_object *ret_obj;
|
|
acpi_status status;
|
|
|
|
status = acpi_evaluate_object(handle, dsd_method_name, NULL, &buffer);
|
|
if (ACPI_FAILURE(status)) {
|
|
acpi_handle_err(handle,
|
|
"Can't evaluate %s method: %d\n", dsd_method_name, status);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret_obj = buffer.pointer;
|
|
|
|
memcpy(prop_buf, ret_obj->buffer.pointer, ret_obj->buffer.length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_get_acpi_resources - Query all QuickI2C devices' ACPI parameters
|
|
* @qcdev: Point to quicki2c_device structure
|
|
*
|
|
* This function gets all QuickI2C devices' ACPI resource.
|
|
*
|
|
* Return: 0 if success or error code on failure.
|
|
*/
|
|
static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
|
|
{
|
|
struct acpi_device *adev = ACPI_COMPANION(qcdev->dev);
|
|
struct quicki2c_subip_acpi_parameter i2c_param;
|
|
struct quicki2c_subip_acpi_config i2c_config;
|
|
u32 hid_desc_addr;
|
|
int ret = -EINVAL;
|
|
|
|
if (!adev) {
|
|
dev_err(qcdev->dev, "Invalid acpi device pointer\n");
|
|
return ret;
|
|
}
|
|
|
|
qcdev->acpi_dev = adev;
|
|
|
|
ret = quicki2c_acpi_get_dsm_property(adev, &i2c_hid_guid,
|
|
QUICKI2C_ACPI_REVISION_NUM,
|
|
QUICKI2C_ACPI_FUNC_NUM_HID_DESC_ADDR,
|
|
ACPI_TYPE_INTEGER,
|
|
&hid_desc_addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
qcdev->hid_desc_addr = (u16)hid_desc_addr;
|
|
|
|
ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid,
|
|
QUICKI2C_ACPI_REVISION_NUM,
|
|
QUICKI2C_ACPI_FUNC_NUM_ACTIVE_LTR_VAL,
|
|
ACPI_TYPE_INTEGER,
|
|
&qcdev->active_ltr_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid,
|
|
QUICKI2C_ACPI_REVISION_NUM,
|
|
QUICKI2C_ACPI_FUNC_NUM_LP_LTR_VAL,
|
|
ACPI_TYPE_INTEGER,
|
|
&qcdev->low_power_ltr_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ICRS,
|
|
ACPI_TYPE_BUFFER, &i2c_param);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT)
|
|
return -EOPNOTSUPP;
|
|
|
|
qcdev->i2c_slave_addr = i2c_param.device_address;
|
|
|
|
ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB,
|
|
ACPI_TYPE_BUFFER, &i2c_config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (i2c_param.connection_speed > 0 &&
|
|
i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) {
|
|
qcdev->i2c_speed_mode = THC_I2C_STANDARD;
|
|
qcdev->i2c_clock_hcnt = i2c_config.SMHX;
|
|
qcdev->i2c_clock_lcnt = i2c_config.SMLX;
|
|
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED &&
|
|
i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) {
|
|
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
|
|
qcdev->i2c_clock_hcnt = i2c_config.FMHX;
|
|
qcdev->i2c_clock_lcnt = i2c_config.FMLX;
|
|
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED &&
|
|
i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) {
|
|
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
|
|
qcdev->i2c_clock_hcnt = i2c_config.FPHX;
|
|
qcdev->i2c_clock_lcnt = i2c_config.FPLX;
|
|
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED &&
|
|
i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) {
|
|
qcdev->i2c_speed_mode = THC_I2C_HIGH_SPEED;
|
|
qcdev->i2c_clock_hcnt = i2c_config.HMHX;
|
|
qcdev->i2c_clock_lcnt = i2c_config.HMLX;
|
|
} else {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_irq_quick_handler - The ISR of the QuickI2C driver
|
|
* @irq: The irq number
|
|
* @dev_id: Pointer to the quicki2c_device structure
|
|
*
|
|
* Return: IRQ_WAKE_THREAD if further process needed.
|
|
*/
|
|
static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id)
|
|
{
|
|
struct quicki2c_device *qcdev = dev_id;
|
|
|
|
if (qcdev->state == QUICKI2C_DISABLED)
|
|
return IRQ_HANDLED;
|
|
|
|
/* Disable THC interrupt before current interrupt be handled */
|
|
thc_interrupt_enable(qcdev->thc_hw, false);
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
/**
|
|
* try_recover - Try to recovery THC and Device
|
|
* @qcdev: Pointer to quicki2c_device structure
|
|
*
|
|
* This function is an error handler, called when fatal error happens.
|
|
* It try to reset touch device and re-configure THC to recovery
|
|
* communication between touch device and THC.
|
|
*
|
|
* Return: 0 if successful or error code on failure
|
|
*/
|
|
static int try_recover(struct quicki2c_device *qcdev)
|
|
{
|
|
int ret;
|
|
|
|
thc_dma_unconfigure(qcdev->thc_hw);
|
|
|
|
ret = thc_dma_configure(qcdev->thc_hw);
|
|
if (ret) {
|
|
dev_err(qcdev->dev, "Reconfig DMA failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_input_report(struct quicki2c_device *qcdev)
|
|
{
|
|
struct hidi2c_report_packet *pkt = (struct hidi2c_report_packet *)qcdev->input_buf;
|
|
int rx_dma_finished = 0;
|
|
size_t report_len;
|
|
int ret;
|
|
|
|
while (!rx_dma_finished) {
|
|
ret = thc_rxdma_read(qcdev->thc_hw, THC_RXDMA2,
|
|
(u8 *)pkt, &report_len,
|
|
&rx_dma_finished);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!pkt->len) {
|
|
if (qcdev->state == QUICKI2C_RESETING) {
|
|
qcdev->reset_ack = true;
|
|
wake_up(&qcdev->reset_ack_wq);
|
|
|
|
qcdev->state = QUICKI2C_RESETED;
|
|
} else {
|
|
dev_warn(qcdev->dev, "unexpected DIR happen\n");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Discard samples before driver probe complete */
|
|
if (qcdev->state != QUICKI2C_ENABLED)
|
|
continue;
|
|
|
|
quicki2c_hid_send_report(qcdev, pkt->data,
|
|
HIDI2C_DATA_LEN(le16_to_cpu(pkt->len)));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_irq_thread_handler - IRQ thread handler of QuickI2C driver
|
|
* @irq: The IRQ number
|
|
* @dev_id: Pointer to the quicki2c_device structure
|
|
*
|
|
* Return: IRQ_HANDLED to finish this handler.
|
|
*/
|
|
static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id)
|
|
{
|
|
struct quicki2c_device *qcdev = dev_id;
|
|
int err_recover = 0;
|
|
int int_mask;
|
|
int ret;
|
|
|
|
if (qcdev->state == QUICKI2C_DISABLED)
|
|
return IRQ_HANDLED;
|
|
|
|
ret = pm_runtime_resume_and_get(qcdev->dev);
|
|
if (ret)
|
|
return IRQ_HANDLED;
|
|
|
|
int_mask = thc_interrupt_handler(qcdev->thc_hw);
|
|
|
|
if (int_mask & BIT(THC_FATAL_ERR_INT) || int_mask & BIT(THC_TXN_ERR_INT) ||
|
|
int_mask & BIT(THC_UNKNOWN_INT)) {
|
|
err_recover = 1;
|
|
goto exit;
|
|
}
|
|
|
|
if (int_mask & BIT(THC_RXDMA2_INT)) {
|
|
err_recover = handle_input_report(qcdev);
|
|
if (err_recover)
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
thc_interrupt_enable(qcdev->thc_hw, true);
|
|
|
|
if (err_recover)
|
|
if (try_recover(qcdev))
|
|
qcdev->state = QUICKI2C_DISABLED;
|
|
|
|
pm_runtime_mark_last_busy(qcdev->dev);
|
|
pm_runtime_put_autosuspend(qcdev->dev);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dev_init - Initialize QuickI2C device
|
|
* @pdev: Pointer to the THC PCI device
|
|
* @mem_addr: The Pointer of MMIO memory address
|
|
* @ddata: Point to quicki2c_ddata structure
|
|
*
|
|
* Alloc quicki2c_device structure and initialized THC device,
|
|
* then configure THC to HIDI2C mode.
|
|
*
|
|
* If success, enable THC hardware interrupt.
|
|
*
|
|
* Return: Pointer to the quicki2c_device structure if success
|
|
* or NULL on failure.
|
|
*/
|
|
static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr,
|
|
const struct quicki2c_ddata *ddata)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = devm_kzalloc(dev, sizeof(struct quicki2c_device), GFP_KERNEL);
|
|
if (!qcdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
qcdev->pdev = pdev;
|
|
qcdev->dev = dev;
|
|
qcdev->mem_addr = mem_addr;
|
|
qcdev->state = QUICKI2C_DISABLED;
|
|
qcdev->ddata = ddata;
|
|
|
|
init_waitqueue_head(&qcdev->reset_ack_wq);
|
|
|
|
/* THC hardware init */
|
|
qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr);
|
|
if (IS_ERR(qcdev->thc_hw)) {
|
|
ret = PTR_ERR(qcdev->thc_hw);
|
|
dev_err_once(dev, "Failed to initialize THC device context, ret = %d.\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = quicki2c_get_acpi_resources(qcdev);
|
|
if (ret) {
|
|
dev_err_once(dev, "Get ACPI resources failed, ret = %d\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
|
|
if (ret) {
|
|
dev_err_once(dev, "Failed to select THC port, ret = %d.\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
|
|
qcdev->i2c_speed_mode,
|
|
qcdev->i2c_clock_hcnt,
|
|
qcdev->i2c_clock_lcnt);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
thc_int_trigger_type_select(qcdev->thc_hw, false);
|
|
|
|
thc_interrupt_config(qcdev->thc_hw);
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, true);
|
|
|
|
thc_wot_config(qcdev->thc_hw, &quicki2c_gpios[0]);
|
|
|
|
qcdev->state = QUICKI2C_INITED;
|
|
|
|
return qcdev;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dev_deinit - De-initialize QuickI2C device
|
|
* @qcdev: Pointer to the quicki2c_device structure
|
|
*
|
|
* Disable THC interrupt and deinitilize THC.
|
|
*/
|
|
static void quicki2c_dev_deinit(struct quicki2c_device *qcdev)
|
|
{
|
|
thc_interrupt_quiesce(qcdev->thc_hw, true);
|
|
thc_interrupt_enable(qcdev->thc_hw, false);
|
|
thc_ltr_unconfig(qcdev->thc_hw);
|
|
thc_wot_unconfig(qcdev->thc_hw);
|
|
|
|
qcdev->state = QUICKI2C_DISABLED;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dma_adv_enable - Configure and enable DMA advanced features
|
|
* @qcdev: Pointer to the quicki2c_device structure
|
|
*
|
|
* If platform supports THC DMA advanced features, such as max input size
|
|
* control or interrupt delay, configures and enables them.
|
|
*/
|
|
static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
|
|
{
|
|
/*
|
|
* If platform supports max input size control feature and touch device
|
|
* max input length <= THC detect capability, enable the feature with device
|
|
* max input length.
|
|
*/
|
|
if (qcdev->ddata->max_detect_size >=
|
|
le16_to_cpu(qcdev->dev_desc.max_input_len)) {
|
|
thc_i2c_set_rx_max_size(qcdev->thc_hw,
|
|
le16_to_cpu(qcdev->dev_desc.max_input_len));
|
|
thc_i2c_rx_max_size_enable(qcdev->thc_hw, true);
|
|
}
|
|
|
|
/* If platform supports interrupt delay feature, enable it with given delay */
|
|
if (qcdev->ddata->interrupt_delay) {
|
|
thc_i2c_set_rx_int_delay(qcdev->thc_hw,
|
|
qcdev->ddata->interrupt_delay);
|
|
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dma_adv_disable - Disable DMA advanced features
|
|
* @qcdev: Pointer to the quicki2c device structure
|
|
*
|
|
* Disable all DMA advanced features if platform supports.
|
|
*/
|
|
static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev)
|
|
{
|
|
if (qcdev->ddata->max_detect_size)
|
|
thc_i2c_rx_max_size_enable(qcdev->thc_hw, false);
|
|
|
|
if (qcdev->ddata->interrupt_delay)
|
|
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false);
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dma_init - Configure THC DMA for QuickI2C device
|
|
* @qcdev: Pointer to the quicki2c_device structure
|
|
*
|
|
* This function uses TIC's parameters(such as max input length, max output
|
|
* length) to allocate THC DMA buffers and configure THC DMA engines.
|
|
*
|
|
* Return: 0 if success or error code on failure.
|
|
*/
|
|
static int quicki2c_dma_init(struct quicki2c_device *qcdev)
|
|
{
|
|
size_t swdma_max_len;
|
|
int ret;
|
|
|
|
swdma_max_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len),
|
|
le16_to_cpu(qcdev->dev_desc.report_desc_len));
|
|
|
|
ret = thc_dma_set_max_packet_sizes(qcdev->thc_hw, 0,
|
|
le16_to_cpu(qcdev->dev_desc.max_input_len),
|
|
le16_to_cpu(qcdev->dev_desc.max_output_len),
|
|
swdma_max_len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_dma_allocate(qcdev->thc_hw);
|
|
if (ret) {
|
|
dev_err(qcdev->dev, "Allocate THC DMA buffer failed, ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Enable RxDMA */
|
|
ret = thc_dma_configure(qcdev->thc_hw);
|
|
if (ret) {
|
|
dev_err(qcdev->dev, "Configure THC DMA failed, ret = %d\n", ret);
|
|
thc_dma_unconfigure(qcdev->thc_hw);
|
|
thc_dma_release(qcdev->thc_hw);
|
|
return ret;
|
|
}
|
|
|
|
if (qcdev->ddata)
|
|
quicki2c_dma_adv_enable(qcdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_dma_deinit - Release THC DMA for QuickI2C device
|
|
* @qcdev: Pointer to the quicki2c_device structure
|
|
*
|
|
* Stop THC DMA engines and release all DMA buffers.
|
|
*
|
|
*/
|
|
static void quicki2c_dma_deinit(struct quicki2c_device *qcdev)
|
|
{
|
|
thc_dma_unconfigure(qcdev->thc_hw);
|
|
thc_dma_release(qcdev->thc_hw);
|
|
|
|
if (qcdev->ddata)
|
|
quicki2c_dma_adv_disable(qcdev);
|
|
}
|
|
|
|
/**
|
|
* quicki2c_alloc_report_buf - Alloc report buffers
|
|
* @qcdev: Pointer to the quicki2c_device structure
|
|
*
|
|
* Allocate report descriptor buffer, it will be used for restore TIC HID
|
|
* report descriptor.
|
|
*
|
|
* Allocate input report buffer, it will be used for receive HID input report
|
|
* data from TIC.
|
|
*
|
|
* Allocate output report buffer, it will be used for store HID output report,
|
|
* such as set feature.
|
|
*
|
|
* Return: 0 if success or error code on failure.
|
|
*/
|
|
static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev)
|
|
{
|
|
size_t max_report_len;
|
|
|
|
qcdev->report_descriptor = devm_kzalloc(qcdev->dev,
|
|
le16_to_cpu(qcdev->dev_desc.report_desc_len),
|
|
GFP_KERNEL);
|
|
if (!qcdev->report_descriptor)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Some HIDI2C devices don't declare input/output max length correctly,
|
|
* give default 4K buffer to avoid DMA buffer overrun.
|
|
*/
|
|
max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), SZ_4K);
|
|
|
|
qcdev->input_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL);
|
|
if (!qcdev->input_buf)
|
|
return -ENOMEM;
|
|
|
|
if (!le16_to_cpu(qcdev->dev_desc.max_output_len))
|
|
qcdev->dev_desc.max_output_len = cpu_to_le16(SZ_4K);
|
|
|
|
max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_output_len),
|
|
max_report_len);
|
|
|
|
qcdev->report_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL);
|
|
if (!qcdev->report_buf)
|
|
return -ENOMEM;
|
|
|
|
qcdev->report_len = max_report_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* quicki2c_probe: QuickI2C driver probe function
|
|
* @pdev: Point to PCI device
|
|
* @id: Point to pci_device_id structure
|
|
*
|
|
* This function initializes THC and HIDI2C device, the flow is:
|
|
* - Do THC pci device initialization
|
|
* - Query HIDI2C ACPI parameters
|
|
* - Configure THC to HIDI2C mode
|
|
* - Go through HIDI2C enumeration flow
|
|
* |- Read device descriptor
|
|
* |- Reset HIDI2C device
|
|
* - Enable THC interrupt and DMA
|
|
* - Read report descriptor
|
|
* - Register HID device
|
|
* - Enable runtime power management
|
|
*
|
|
* Return 0 if success or error code on failure.
|
|
*/
|
|
static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
{
|
|
const struct quicki2c_ddata *ddata = (const struct quicki2c_ddata *)id->driver_data;
|
|
struct quicki2c_device *qcdev;
|
|
void __iomem *mem_addr;
|
|
int ret;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret) {
|
|
dev_err_once(&pdev->dev, "Failed to enable PCI device, ret = %d.\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
|
|
mem_addr = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
|
|
ret = PTR_ERR_OR_ZERO(mem_addr);
|
|
if (ret) {
|
|
dev_err_once(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret);
|
|
goto disable_pci_device;
|
|
}
|
|
|
|
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
|
|
if (ret) {
|
|
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
|
|
if (ret) {
|
|
dev_err_once(&pdev->dev, "No usable DMA configuration %d\n", ret);
|
|
goto disable_pci_device;
|
|
}
|
|
}
|
|
|
|
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
|
|
if (ret < 0) {
|
|
dev_err_once(&pdev->dev,
|
|
"Failed to allocate IRQ vectors. ret = %d\n", ret);
|
|
goto disable_pci_device;
|
|
}
|
|
|
|
pdev->irq = pci_irq_vector(pdev, 0);
|
|
|
|
qcdev = quicki2c_dev_init(pdev, mem_addr, ddata);
|
|
if (IS_ERR(qcdev)) {
|
|
dev_err_once(&pdev->dev, "QuickI2C device init failed\n");
|
|
ret = PTR_ERR(qcdev);
|
|
goto disable_pci_device;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, qcdev);
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, pdev->irq,
|
|
quicki2c_irq_quick_handler,
|
|
quicki2c_irq_thread_handler,
|
|
IRQF_ONESHOT, KBUILD_MODNAME,
|
|
qcdev);
|
|
if (ret) {
|
|
dev_err_once(&pdev->dev,
|
|
"Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = quicki2c_get_device_descriptor(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Get device descriptor failed, ret = %d\n", ret);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = quicki2c_alloc_report_buf(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Alloc report buffers failed, ret= %d\n", ret);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = quicki2c_dma_init(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Setup THC DMA failed, ret= %d\n", ret);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
|
|
if (ret)
|
|
goto dev_deinit;
|
|
|
|
ret = quicki2c_set_power(qcdev, HIDI2C_ON);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Set Power On command failed, ret= %d\n", ret);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = quicki2c_reset(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Reset HIDI2C device failed, ret= %d\n", ret);
|
|
goto dev_deinit;
|
|
}
|
|
|
|
ret = quicki2c_get_report_descriptor(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Get report descriptor failed, ret = %d\n", ret);
|
|
goto dma_deinit;
|
|
}
|
|
|
|
ret = quicki2c_hid_probe(qcdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to register HID device, ret = %d\n", ret);
|
|
goto dma_deinit;
|
|
}
|
|
|
|
qcdev->state = QUICKI2C_ENABLED;
|
|
|
|
/* Enable runtime power management */
|
|
pm_runtime_use_autosuspend(qcdev->dev);
|
|
pm_runtime_set_autosuspend_delay(qcdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS);
|
|
pm_runtime_mark_last_busy(qcdev->dev);
|
|
pm_runtime_put_noidle(qcdev->dev);
|
|
pm_runtime_put_autosuspend(qcdev->dev);
|
|
|
|
dev_dbg(&pdev->dev, "QuickI2C probe success\n");
|
|
|
|
return 0;
|
|
|
|
dma_deinit:
|
|
quicki2c_dma_deinit(qcdev);
|
|
dev_deinit:
|
|
quicki2c_dev_deinit(qcdev);
|
|
disable_pci_device:
|
|
pci_clear_master(pdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* quicki2c_remove - Device Removal Routine
|
|
* @pdev: Point to PCI device structure
|
|
*
|
|
* This is called by the PCI subsystem to alert the driver that it should
|
|
* release a PCI device.
|
|
*/
|
|
static void quicki2c_remove(struct pci_dev *pdev)
|
|
{
|
|
struct quicki2c_device *qcdev;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return;
|
|
|
|
quicki2c_hid_remove(qcdev);
|
|
quicki2c_dma_deinit(qcdev);
|
|
|
|
pm_runtime_get_noresume(qcdev->dev);
|
|
|
|
quicki2c_dev_deinit(qcdev);
|
|
|
|
pci_clear_master(pdev);
|
|
}
|
|
|
|
/**
|
|
* quicki2c_shutdown - Device Shutdown Routine
|
|
* @pdev: Point to PCI device structure
|
|
*
|
|
* This is called from the reboot notifier, it's a simplified version of remove
|
|
* so we go down faster.
|
|
*/
|
|
static void quicki2c_shutdown(struct pci_dev *pdev)
|
|
{
|
|
struct quicki2c_device *qcdev;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return;
|
|
|
|
/* Must stop DMA before reboot to avoid DMA entering into unknown state */
|
|
quicki2c_dma_deinit(qcdev);
|
|
|
|
quicki2c_dev_deinit(qcdev);
|
|
}
|
|
|
|
static int quicki2c_suspend(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* As I2C is THC subsystem, no register auto save/restore support,
|
|
* need driver to do that explicitly for every D3 case.
|
|
*/
|
|
ret = thc_i2c_subip_regs_save(qcdev->thc_hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, false);
|
|
|
|
thc_dma_unconfigure(qcdev->thc_hw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_resume(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_regs_restore(qcdev->thc_hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_config(qcdev->thc_hw);
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, true);
|
|
|
|
ret = thc_dma_configure(qcdev->thc_hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_freeze(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, false);
|
|
|
|
thc_dma_unconfigure(qcdev->thc_hw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_thaw(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
ret = thc_dma_configure(qcdev->thc_hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, true);
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_poweroff(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, false);
|
|
|
|
thc_ltr_unconfig(qcdev->thc_hw);
|
|
|
|
quicki2c_dma_deinit(qcdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_restore(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
int ret;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
/* Reconfig THC HW when back from hibernate */
|
|
ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
|
|
qcdev->i2c_speed_mode,
|
|
qcdev->i2c_clock_hcnt,
|
|
qcdev->i2c_clock_lcnt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_interrupt_config(qcdev->thc_hw);
|
|
|
|
thc_interrupt_enable(qcdev->thc_hw, true);
|
|
|
|
ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = thc_dma_configure(qcdev->thc_hw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thc_ltr_config(qcdev->thc_hw,
|
|
qcdev->active_ltr_val,
|
|
qcdev->low_power_ltr_val);
|
|
|
|
thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_runtime_suspend(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_LP);
|
|
|
|
pci_save_state(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int quicki2c_runtime_resume(struct device *device)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(device);
|
|
struct quicki2c_device *qcdev;
|
|
|
|
qcdev = pci_get_drvdata(pdev);
|
|
if (!qcdev)
|
|
return -ENODEV;
|
|
|
|
thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops quicki2c_pm_ops = {
|
|
.suspend = quicki2c_suspend,
|
|
.resume = quicki2c_resume,
|
|
.freeze = quicki2c_freeze,
|
|
.thaw = quicki2c_thaw,
|
|
.poweroff = quicki2c_poweroff,
|
|
.restore = quicki2c_restore,
|
|
.runtime_suspend = quicki2c_runtime_suspend,
|
|
.runtime_resume = quicki2c_runtime_resume,
|
|
.runtime_idle = NULL,
|
|
};
|
|
|
|
static const struct pci_device_id quicki2c_pci_tbl[] = {
|
|
{ PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1, NULL) },
|
|
{ PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2, NULL) },
|
|
{ PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1, &ptl_ddata) },
|
|
{ PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2, &ptl_ddata) },
|
|
{ PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1, &ptl_ddata) },
|
|
{ PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2, &ptl_ddata) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, quicki2c_pci_tbl);
|
|
|
|
static struct pci_driver quicki2c_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = quicki2c_pci_tbl,
|
|
.probe = quicki2c_probe,
|
|
.remove = quicki2c_remove,
|
|
.shutdown = quicki2c_shutdown,
|
|
.driver.pm = &quicki2c_pm_ops,
|
|
.driver.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
};
|
|
|
|
module_pci_driver(quicki2c_driver);
|
|
|
|
MODULE_AUTHOR("Xinpeng Sun <xinpeng.sun@intel.com>");
|
|
MODULE_AUTHOR("Even Xu <even.xu@intel.com>");
|
|
|
|
MODULE_DESCRIPTION("Intel(R) QuickI2C Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_IMPORT_NS("INTEL_THC");
|