Commit 61bb2714 authored by Even Xu's avatar Even Xu Committed by Jiri Kosina
Browse files

HID: intel-thc-hid: intel-quicki2c: Add THC QuickI2C driver skeleton



Create intel-quicki2c folder and add Kconfig and Makefile for THC
QuickI2C driver. Add basic device structure, definitions and probe/remove
functions for QuickI2C driver.

Co-developed-by: default avatarXinpeng Sun <xinpeng.sun@intel.com>
Signed-off-by: default avatarXinpeng Sun <xinpeng.sun@intel.com>
Signed-off-by: default avatarEven Xu <even.xu@intel.com>
Tested-by: default avatarRui Zhang <rui1.zhang@intel.com>
Tested-by: default avatarMark Pearson <mpearson-lenovo@squebb.ca>
Reviewed-by: default avatarSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Reviewed-by: default avatarMark Pearson <mpearson-lenovo@squebb.ca>
Tested-by: default avatarAaron Ma <aaron.ma@canonical.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.com>
parent 6912aaf3
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -28,4 +28,15 @@ config INTEL_QUICKSPI

	  Say Y/M here if you want to support Intel QuickSPI. If unsure, say N.

config INTEL_QUICKI2C
	tristate "Intel QuickI2C driver based on Intel Touch Host Controller"
	depends on INTEL_THC_HID
	help
	  Intel QuickI2C, uses Touch Host Controller (THC) hardware, implements
	  HIDI2C (HID over I2C) protocol. It configures THC to work in I2C
	  mode, and controls THC hardware sequencer to accelerate HIDI2C
	  transaction flow.

	  Say Y/M here if you want to support Intel QuickI2C. If unsure, say N.

endmenu
+3 −0
Original line number Diff line number Diff line
@@ -14,4 +14,7 @@ intel-quickspi-objs += intel-quickspi/pci-quickspi.o
intel-quickspi-objs += intel-quickspi/quickspi-hid.o
intel-quickspi-objs += intel-quickspi/quickspi-protocol.o

obj-$(CONFIG_INTEL_QUICKI2C) += intel-quicki2c.o
intel-quicki2c-objs += intel-quicki2c/pci-quicki2c.o

ccflags-y += -I $(src)/intel-thc
+270 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Intel Corporation */

#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 "intel-thc-dev.h"

#include "quicki2c-dev.h"

/**
 * quicki2c_irq_quick_handler - The ISR of the quicki2c driver
 *
 * @irq: The irq number
 * @dev_id: pointer to the 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;
}

/**
 * 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 int_mask;

	if (qcdev->state == QUICKI2C_DISABLED)
		return IRQ_HANDLED;

	int_mask = thc_interrupt_handler(qcdev->thc_hw);

	thc_interrupt_enable(qcdev->thc_hw, true);

	return IRQ_HANDLED;
}

/**
 * quicki2c_dev_init - Initialize quicki2c device
 *
 * @pdev: pointer to the thc pci device
 * @mem_addr: The pointer of MMIO memory address
 *
 * 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 failed.
 */
static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr)
{
	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;

	/* thc hw 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 = 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);
	}

	thc_interrupt_config(qcdev->thc_hw);

	thc_interrupt_enable(qcdev->thc_hw, true);

	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_enable(qcdev->thc_hw, false);
}

/*
 * quicki2c_probe: Quicki2c driver probe function
 *
 * @pdev: point to pci device
 * @id: point to pci_device_id structure
 *
 * Return 0 if success or error code on failed.
 */
static int quicki2c_probe(struct pci_dev *pdev,
			  const struct pci_device_id *id)
{
	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);

	ret = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME);
	if (ret) {
		dev_err_once(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret);
		goto disable_pci_device;
	}

	mem_addr = pcim_iomap_table(pdev)[0];

	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 unmap_io_region;
		}
	}

	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 unmap_io_region;
	}

	pdev->irq = pci_irq_vector(pdev, 0);

	qcdev = quicki2c_dev_init(pdev, mem_addr);
	if (IS_ERR(qcdev)) {
		dev_err_once(&pdev->dev, "QuickI2C device init failed\n");
		ret = PTR_ERR(qcdev);
		goto unmap_io_region;
	}

	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;
	}

	return 0;

dev_deinit:
	quicki2c_dev_deinit(qcdev);
unmap_io_region:
	pcim_iounmap_regions(pdev, BIT(0));
disable_pci_device:
	pci_clear_master(pdev);

	return ret;
}

/**
 * quicki2c_remove - Device Removal Routine
 *
 * @pdev: 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_dev_deinit(qcdev);

	pcim_iounmap_regions(pdev, BIT(0));
	pci_clear_master(pdev);
}

/**
 * quicki2c_shutdown - Device Shutdown Routine
 *
 * @pdev: 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;

	quicki2c_dev_deinit(qcdev);
}

static const struct pci_device_id quicki2c_pci_tbl[] = {
	{PCI_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1), },
	{PCI_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2), },
	{PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1), },
	{PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2), },
	{PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1), },
	{PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2), },
	{}
};
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.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");
+48 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Intel Corporation */

#ifndef _QUICKI2C_DEV_H_
#define _QUICKI2C_DEV_H_

#define THC_LNL_DEVICE_ID_I2C_PORT1	0xA848
#define THC_LNL_DEVICE_ID_I2C_PORT2	0xA84A
#define THC_PTL_H_DEVICE_ID_I2C_PORT1	0xE348
#define THC_PTL_H_DEVICE_ID_I2C_PORT2	0xE34A
#define THC_PTL_U_DEVICE_ID_I2C_PORT1	0xE448
#define THC_PTL_U_DEVICE_ID_I2C_PORT2	0xE44A

/* Packet size value, the unit is 16 bytes */
#define MAX_PACKET_SIZE_VALUE_LNL			256

enum quicki2c_dev_state {
	QUICKI2C_NONE,
	QUICKI2C_RESETING,
	QUICKI2C_RESETED,
	QUICKI2C_INITED,
	QUICKI2C_ENABLED,
	QUICKI2C_DISABLED,
};

struct device;
struct pci_dev;
struct thc_device;

/**
 * struct quicki2c_device -  THC QuickI2C device struct
 * @dev: point to kernel device
 * @pdev: point to PCI device
 * @thc_hw: point to THC device
 * @driver_data: point to quicki2c specific driver data
 * @state: THC I2C device state
 * @mem_addr: MMIO memory address
 */
struct quicki2c_device {
	struct device *dev;
	struct pci_dev *pdev;
	struct thc_device *thc_hw;
	enum quicki2c_dev_state state;

	void __iomem *mem_addr;
};

#endif /* _QUICKI2C_DEV_H_ */