lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Wed, 6 Dec 2023 07:40:49 +0000
From:   "Winkler, Tomas" <tomas.winkler@...el.com>
To:     "Wu, Wentong" <wentong.wu@...el.com>,
        "gregkh@...uxfoundation.org" <gregkh@...uxfoundation.org>,
        "hdegoede@...hat.com" <hdegoede@...hat.com>
CC:     "andriy.shevchenko@...ux.intel.com" 
        <andriy.shevchenko@...ux.intel.com>,
        "sakari.ailus@...ux.intel.com" <sakari.ailus@...ux.intel.com>,
        "Usyskin, Alexander" <alexander.usyskin@...el.com>,
        "Wang, Zhifeng" <zhifeng.wang@...el.com>,
        "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: RE: [PATCH 1/2] mei: Add transport driver for IVSC device



> -----Original Message-----
> 
> The Intel visual sensing controller (IVSC) device is designed to control the
> camera sharing between host IPU for media usage and IVSC for context
> sensing (face detection).
> 
> IVSC is exposed to HOST as an SPI device and the message protocol over the
> SPI BUS for communicating with the IVSC device is implemented. This is the
> backend of mei framework for IVSC device, which usually handles the
> hardware data transfer. The mei_csi and mei_ace are the clients of IVSC mei
> framework.
> 
> The firmware downloading for the IVSC device is implemented as well.
> 
> Signed-off-by: Wentong Wu <wentong.wu@...el.com>
> Reviewed-by: Sakari Ailus <sakari.ailus@...ux.intel.com>
Acked-by: Tomas Winkler <tomas.winkler@...el.com>

> ---
>  drivers/misc/mei/Kconfig         |  11 +
>  drivers/misc/mei/Makefile        |   4 +
>  drivers/misc/mei/vsc-fw-loader.c | 822
> +++++++++++++++++++++++++++++++++++++++
>  drivers/misc/mei/vsc-tp.c        | 555 ++++++++++++++++++++++++++
>  drivers/misc/mei/vsc-tp.h        |  50 +++
>  5 files changed, 1442 insertions(+)
>  create mode 100644 drivers/misc/mei/vsc-fw-loader.c  create mode 100644
> drivers/misc/mei/vsc-tp.c  create mode 100644 drivers/misc/mei/vsc-tp.h
> 
> diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index
> 37db142..470957a 100644
> --- a/drivers/misc/mei/Kconfig
> +++ b/drivers/misc/mei/Kconfig
> @@ -60,6 +60,17 @@ config INTEL_MEI_GSC
>  	  tasks such as graphics card firmware update and security
>  	  tasks.
> 
> +config INTEL_MEI_VSC_HW
> +	tristate "Intel visual sensing controller device transport driver"
> +	depends on ACPI && SPI
> +	depends on GPIOLIB || COMPILE_TEST
> +	help
> +	  Intel SPI transport driver between host and Intel visual sensing
> +	  controller (IVSC) device.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called mei-vsc-hw.
> +
>  source "drivers/misc/mei/hdcp/Kconfig"
>  source "drivers/misc/mei/pxp/Kconfig"
>  source "drivers/misc/mei/gsc_proxy/Kconfig"
> diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index
> 14aee25..3d0da19 100644
> --- a/drivers/misc/mei/Makefile
> +++ b/drivers/misc/mei/Makefile
> @@ -31,3 +31,7 @@ CFLAGS_mei-trace.o = -I$(src)
>  obj-$(CONFIG_INTEL_MEI_HDCP) += hdcp/
>  obj-$(CONFIG_INTEL_MEI_PXP) += pxp/
>  obj-$(CONFIG_INTEL_MEI_GSC_PROXY) += gsc_proxy/
> +
> +obj-$(CONFIG_INTEL_MEI_VSC_HW) += mei-vsc-hw.o mei-vsc-hw-y := vsc-
> tp.o
> +mei-vsc-hw-y += vsc-fw-loader.o
> diff --git a/drivers/misc/mei/vsc-fw-loader.c b/drivers/misc/mei/vsc-fw-
> loader.c
> new file mode 100644
> index 0000000..3e151f0
> --- /dev/null
> +++ b/drivers/misc/mei/vsc-fw-loader.c
> @@ -0,0 +1,822 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2023, Intel Corporation.
> + * Intel Visual Sensing Controller Transport Layer Linux driver  */
> +
> +#include <linux/acpi.h>
> +#include <linux/align.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/cleanup.h>
> +#include <linux/firmware.h>
> +#include <linux/sizes.h>
> +#include <linux/slab.h>
> +#include <linux/string_helpers.h>
> +#include <linux/types.h>
> +
> +#include <asm-generic/unaligned.h>
> +
> +#include "vsc-tp.h"
> +
> +#define VSC_MAGIC_NUM			0x49505343 /* IPSC */
> +#define VSC_MAGIC_FW			0x49574653 /* IWFS */
> +#define VSC_MAGIC_FILE			0x46564353 /* FVCS */
> +
> +#define VSC_ADDR_BASE			0xE0030000
> +#define VSC_EFUSE_ADDR			(VSC_ADDR_BASE + 0x038)
> +#define VSC_STRAP_ADDR			(VSC_ADDR_BASE + 0x100)
> +
> +#define VSC_STRAP_KEY_SRC_MASK		BIT(0)
> +#define VSC_STRAP_KEY_SRC_PRODUCT	1
> +
> +#define VSC_MAINSTEPPING_VERSION_MASK	GENMASK(7, 4)
> +#define VSC_MAINSTEPPING_VERSION_A	0
> +
> +#define VSC_SUBSTEPPING_VERSION_MASK	GENMASK(3, 0)
> +#define VSC_SUBSTEPPING_VERSION_0	0
> +#define VSC_SUBSTEPPING_VERSION_1	2
> +
> +#define VSC_BOOT_IMG_OPTION_MASK	GENMASK(15, 0)
> +
> +#define VSC_SKU_CFG_LOCATION		0x5001A000
> +#define VSC_SKU_MAX_SIZE		4100u
> +
> +#define VSC_ACE_IMG_CNT			2
> +#define VSC_CSI_IMG_CNT			4
> +#define VSC_IMG_CNT_MAX			6
> +
> +#define VSC_ROM_PKG_SIZE		256u
> +#define VSC_FW_PKG_SIZE			512u
> +
> +#define VSC_CSI_IMAGE_NAME_FMT		"ivsc_fw_a1.bin"
> +#define VSC_CSI_IMAGE_NAME_FMT_PROD	"ivsc_fw_a1_%s.bin"
> +#define VSC_ACE_IMAGE_NAME_FMT
> 	"ivsc_pkg_%s_0_a1.bin"
> +#define VSC_ACE_IMAGE_NAME_FMT_PROD	"ivsc_pkg_%s_0_a1_%s.bin"
> +#define VSC_CFG_IMAGE_NAME_FMT
> 	"ivsc_skucfg_%s_0_1_a1.bin"
> +#define VSC_CFG_IMAGE_NAME_FMT_PROD
> 	"ivsc_skucfg_%s_0_1_a1_%s.bin"
> +
> +#define VSC_IMAGE_FOLDER_FMT		"vsc/soc_a1"
> +#define VSC_IMAGE_FOLDER_FMT_PROD	"vsc/soc_a1_%s"
> +
> +#define VSC_IMAGE_NAME_MAX_LEN		64
> +#define VSC_IMAGE_PATH_MAX_LEN		128
> +
> +#define VSC_SENSOR_NAME_MAX_LEN		16
> +#define VSC_IMAGE_FOLDER_NAME_MAX_LEN	32
> +#define VSC_IMAGE_NAME_SUFFIX_MAX_LEN	8
> +
> +/* command id */
> +enum {
> +	VSC_CMD_QUERY = 0,
> +	VSC_CMD_DL_SET = 1,
> +	VSC_CMD_DL_START = 2,
> +	VSC_CMD_DL_CONT = 3,
> +	VSC_CMD_DUMP_MEM = 4,
> +	VSC_CMD_GET_CONT = 8,
> +	VSC_CMD_CAM_BOOT = 10,
> +};
> +
> +/* command ack token */
> +enum {
> +	VSC_TOKEN_BOOTLOADER_REQ = 1,
> +	VSC_TOKEN_DUMP_RESP = 4,
> +	VSC_TOKEN_ERROR = 7,
> +};
> +
> +/* image type */
> +enum {
> +	VSC_IMG_BOOTLOADER_TYPE = 1,
> +	VSC_IMG_CSI_EM7D_TYPE,
> +	VSC_IMG_CSI_SEM_TYPE,
> +	VSC_IMG_CSI_RUNTIME_TYPE,
> +	VSC_IMG_ACE_VISION_TYPE,
> +	VSC_IMG_ACE_CFG_TYPE,
> +	VSC_IMG_SKU_CFG_TYPE,
> +};
> +
> +/* image fragments */
> +enum {
> +	VSC_IMG_BOOTLOADER_FRAG,
> +	VSC_IMG_CSI_SEM_FRAG,
> +	VSC_IMG_CSI_RUNTIME_FRAG,
> +	VSC_IMG_ACE_VISION_FRAG,
> +	VSC_IMG_ACE_CFG_FRAG,
> +	VSC_IMG_CSI_EM7D_FRAG,
> +	VSC_IMG_SKU_CFG_FRAG,
> +	VSC_IMG_FRAG_MAX
> +};
> +
> +struct vsc_rom_cmd {
> +	__le32 magic;
> +	__u8 cmd_id;
> +	union {
> +		/* download start */
> +		struct {
> +			__u8 img_type;
> +			__le16 option;
> +			__le32 img_len;
> +			__le32 img_loc;
> +			__le32 crc;
> +			DECLARE_FLEX_ARRAY(__u8, res);
> +		} __packed dl_start;
> +		/* download set */
> +		struct {
> +			__u8 option;
> +			__le16 img_cnt;
> +			DECLARE_FLEX_ARRAY(__le32, payload);
> +		} __packed dl_set;
> +		/* download continue */
> +		struct {
> +			__u8 end_flag;
> +			__le16 len;
> +			/* 8 is the offset of payload */
> +			__u8 payload[VSC_ROM_PKG_SIZE - 8];
> +		} __packed dl_cont;
> +		/* dump memory */
> +		struct {
> +			__u8 res;
> +			__le16 len;
> +			__le32 addr;
> +			DECLARE_FLEX_ARRAY(__u8, payload);
> +		} __packed dump_mem;
> +		/* 5 is the offset of padding */
> +		__u8 padding[VSC_ROM_PKG_SIZE - 5];
> +	} data;
> +};
> +
> +struct vsc_rom_cmd_ack {
> +	__le32 magic;
> +	__u8 token;
> +	__u8 type;
> +	__u8 res[2];
> +	__u8 payload[];
> +};
> +
> +struct vsc_fw_cmd {
> +	__le32 magic;
> +	__u8 cmd_id;
> +	union {
> +		struct {
> +			__le16 option;
> +			__u8 img_type;
> +			__le32 img_len;
> +			__le32 img_loc;
> +			__le32 crc;
> +			DECLARE_FLEX_ARRAY(__u8, res);
> +		} __packed dl_start;
> +		struct {
> +			__le16 option;
> +			__u8 img_cnt;
> +			DECLARE_FLEX_ARRAY(__le32, payload);
> +		} __packed dl_set;
> +		struct {
> +			__le32 addr;
> +			__u8 len;
> +			DECLARE_FLEX_ARRAY(__u8, payload);
> +		} __packed dump_mem;
> +		struct {
> +			__u8 resv[3];
> +			__le32 crc;
> +			DECLARE_FLEX_ARRAY(__u8, payload);
> +		} __packed boot;
> +		/* 5 is the offset of padding */
> +		__u8 padding[VSC_FW_PKG_SIZE - 5];
> +	} data;
> +};
> +
> +struct vsc_img {
> +	__le32 magic;
> +	__le32 option;
> +	__le32 image_count;
> +	__le32 image_location[VSC_IMG_CNT_MAX]; };
> +
> +struct vsc_fw_sign {
> +	__le32 magic;
> +	__le32 image_size;
> +	__u8 image[];
> +};
> +
> +struct vsc_image_code_data {
> +	/* fragment index */
> +	u8 frag_index;
> +	/* image type */
> +	u8 image_type;
> +};
> +
> +struct vsc_img_frag {
> +	u8 type;
> +	u32 location;
> +	const u8 *data;
> +	u32 size;
> +};
> +
> +/**
> + * struct vsc_fw_loader - represent vsc firmware loader
> + * @dev: device used to request fimware
> + * @tp: transport layer used with the firmware loader
> + * @csi: CSI image
> + * @ace: ACE image
> + * @cfg: config image
> + * @tx_buf: tx buffer
> + * @rx_buf: rx buffer
> + * @option: command option
> + * @count: total image count
> + * @key_src: key source
> + * @folder: image folder
> + * @sensor_name: camera sensor name
> + * @suffix: image name suffix
> + * @frags: image fragments
> + */
> +struct vsc_fw_loader {
> +	struct device *dev;
> +	struct vsc_tp *tp;
> +
> +	const struct firmware *csi;
> +	const struct firmware *ace;
> +	const struct firmware *cfg;
> +
> +	void *tx_buf;
> +	void *rx_buf;
> +
> +	u16 option;
> +	u16 count;
> +	u32 key_src;
> +
> +	char folder[VSC_IMAGE_FOLDER_NAME_MAX_LEN];
> +	char sensor_name[VSC_SENSOR_NAME_MAX_LEN];
> +	char suffix[VSC_IMAGE_NAME_SUFFIX_MAX_LEN];
> +
> +	struct vsc_img_frag frags[VSC_IMG_FRAG_MAX]; };
> +
> +static inline u32 vsc_sum_crc(void *data, size_t size) {
> +	u32 crc = 0;
> +	size_t i;
> +
> +	for (i = 0; i < size; i++)
> +		crc += *((u8 *)data + i);
> +
> +	return crc;
> +}
> +
> +/* get sensor name to construct image name */ static int
> +vsc_get_sensor_name(struct vsc_fw_loader *fw_loader,
> +			       struct device *dev)
> +{
> +	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER };
> +	union acpi_object obj = {
> +		.type = ACPI_TYPE_INTEGER,
> +		.integer.value = 1,
> +	};
> +	struct acpi_object_list arg_list = {
> +		.count = 1,
> +		.pointer = &obj,
> +	};
> +	union acpi_object *ret_obj;
> +	acpi_handle handle;
> +	acpi_status status;
> +	int ret = 0;
> +
> +	handle = ACPI_HANDLE(dev);
> +	if (!handle)
> +		return -EINVAL;
> +
> +	status = acpi_evaluate_object(handle, "SID", &arg_list, &buffer);
> +	if (ACPI_FAILURE(status)) {
> +		dev_err(dev, "can't evaluate SID method: %d\n", status);
> +		return -ENODEV;
> +	}
> +
> +	ret_obj = buffer.pointer;
> +	if (!ret_obj) {
> +		dev_err(dev, "can't locate ACPI buffer\n");
> +		return -ENODEV;
> +	}
> +
> +	if (ret_obj->type != ACPI_TYPE_STRING) {
> +		dev_err(dev, "found non-string entry\n");
> +		ret = -ENODEV;
> +		goto out_free_buff;
> +	}
> +
> +	/* string length excludes trailing NUL */
> +	if (ret_obj->string.length >= sizeof(fw_loader->sensor_name)) {
> +		dev_err(dev, "sensor name buffer too small\n");
> +		ret = -EINVAL;
> +		goto out_free_buff;
> +	}
> +
> +	memcpy(fw_loader->sensor_name, ret_obj->string.pointer,
> +	       ret_obj->string.length);
> +
> +	string_lower(fw_loader->sensor_name, fw_loader->sensor_name);
> +
> +out_free_buff:
> +	ACPI_FREE(buffer.pointer);
> +
> +	return ret;
> +}
> +
> +static int vsc_identify_silicon(struct vsc_fw_loader *fw_loader) {
> +	struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf;
> +	struct vsc_rom_cmd *cmd = fw_loader->tx_buf;
> +	u8 version, sub_version;
> +	int ret;
> +
> +	/* identify stepping information */
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_DUMP_MEM;
> +	cmd->data.dump_mem.addr = cpu_to_le32(VSC_EFUSE_ADDR);
> +	cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32));
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +	if (ack->token == VSC_TOKEN_ERROR)
> +		return -EINVAL;
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_GET_CONT;
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +	if (ack->token != VSC_TOKEN_DUMP_RESP)
> +		return -EINVAL;
> +
> +	version = FIELD_GET(VSC_MAINSTEPPING_VERSION_MASK, ack-
> >payload[0]);
> +	sub_version = FIELD_GET(VSC_SUBSTEPPING_VERSION_MASK,
> +ack->payload[0]);
> +
> +	if (version != VSC_MAINSTEPPING_VERSION_A)
> +		return -EINVAL;
> +
> +	if (sub_version != VSC_SUBSTEPPING_VERSION_0 &&
> +	    sub_version != VSC_SUBSTEPPING_VERSION_1)
> +		return -EINVAL;
> +
> +	dev_info(fw_loader->dev, "silicon stepping version is %u:%u\n",
> +		 version, sub_version);
> +
> +	/* identify strap information */
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_DUMP_MEM;
> +	cmd->data.dump_mem.addr = cpu_to_le32(VSC_STRAP_ADDR);
> +	cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32));
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +	if (ack->token == VSC_TOKEN_ERROR)
> +		return -EINVAL;
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_GET_CONT;
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +	if (ack->token != VSC_TOKEN_DUMP_RESP)
> +		return -EINVAL;
> +
> +	fw_loader->key_src = FIELD_GET(VSC_STRAP_KEY_SRC_MASK,
> +ack->payload[2]);
> +
> +	if (fw_loader->key_src == VSC_STRAP_KEY_SRC_PRODUCT)
> +		strscpy(fw_loader->suffix, "prod", sizeof(fw_loader->suffix));
> +
> +	return 0;
> +}
> +
> +static int vsc_identify_csi_image(struct vsc_fw_loader *fw_loader) {
> +	char path[VSC_IMAGE_PATH_MAX_LEN];
> +	char name[VSC_IMAGE_NAME_MAX_LEN];
> +	const struct firmware *image;
> +	struct vsc_fw_sign *sign;
> +	struct vsc_img *img;
> +	unsigned int i;
> +	int ret;
> +
> +	if (fw_loader->key_src == VSC_STRAP_KEY_SRC_PRODUCT)
> +		snprintf(name, sizeof(name),
> VSC_CSI_IMAGE_NAME_FMT_PROD,
> +			 fw_loader->suffix);
> +	else
> +		snprintf(name, sizeof(name), VSC_CSI_IMAGE_NAME_FMT);
> +
> +	snprintf(path, sizeof(path), "%s/%s", fw_loader->folder, name);
> +
> +	ret = request_firmware(&image, path, fw_loader->dev);
> +	if (ret)
> +		return ret;
> +
> +	img = (struct vsc_img *)image->data;
> +	if (!img) {
> +		ret = -ENOENT;
> +		goto err_release_image;
> +	}
> +
> +	if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +
> +	if (le32_to_cpu(img->image_count) != VSC_CSI_IMG_CNT) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +	fw_loader->count += le32_to_cpu(img->image_count) - 1;
> +
> +	fw_loader->option =
> +		FIELD_GET(VSC_BOOT_IMG_OPTION_MASK,
> le32_to_cpu(img->option));
> +
> +	sign = (struct vsc_fw_sign *)
> +		(img->image_location + le32_to_cpu(img->image_count));
> +
> +	for (i = 0; i < VSC_CSI_IMG_CNT; i++) {
> +		/* mapping from CSI image index to image code data */
> +		static const struct vsc_image_code_data csi_image_map[] = {
> +			{ VSC_IMG_BOOTLOADER_FRAG,
> VSC_IMG_BOOTLOADER_TYPE },
> +			{ VSC_IMG_CSI_SEM_FRAG, VSC_IMG_CSI_SEM_TYPE
> },
> +			{ VSC_IMG_CSI_RUNTIME_FRAG,
> VSC_IMG_CSI_RUNTIME_TYPE },
> +			{ VSC_IMG_CSI_EM7D_FRAG,
> VSC_IMG_CSI_EM7D_TYPE },
> +		};
> +		struct vsc_img_frag *frag;
> +
> +		if ((u8 *)sign + sizeof(*sign) > image->data + image->size) {
> +			ret = -EINVAL;
> +			goto err_release_image;
> +		}
> +
> +		if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) {
> +			ret = -EINVAL;
> +			goto err_release_image;
> +		}
> +
> +		if (!le32_to_cpu(img->image_location[i])) {
> +			ret = -EINVAL;
> +			goto err_release_image;
> +		}
> +
> +		frag = &fw_loader->frags[csi_image_map[i].frag_index];
> +
> +		frag->data = sign->image;
> +		frag->size = le32_to_cpu(sign->image_size);
> +		frag->location = le32_to_cpu(img->image_location[i]);
> +		frag->type = csi_image_map[i].image_type;
> +
> +		sign = (struct vsc_fw_sign *)
> +			(sign->image + le32_to_cpu(sign->image_size));
> +	}
> +
> +	fw_loader->csi = image;
> +
> +	return 0;
> +
> +err_release_image:
> +	release_firmware(image);
> +
> +	return ret;
> +}
> +
> +static int vsc_identify_ace_image(struct vsc_fw_loader *fw_loader) {
> +	char path[VSC_IMAGE_PATH_MAX_LEN];
> +	char name[VSC_IMAGE_NAME_MAX_LEN];
> +	const struct firmware *image;
> +	struct vsc_fw_sign *sign;
> +	struct vsc_img *img;
> +	unsigned int i;
> +	int ret;
> +
> +	if (fw_loader->key_src == VSC_STRAP_KEY_SRC_PRODUCT)
> +		snprintf(name, sizeof(name),
> VSC_ACE_IMAGE_NAME_FMT_PROD,
> +			 fw_loader->sensor_name, fw_loader->suffix);
> +	else
> +		snprintf(name, sizeof(name), VSC_ACE_IMAGE_NAME_FMT,
> +			 fw_loader->sensor_name);
> +
> +	snprintf(path, sizeof(path), "%s/%s", fw_loader->folder, name);
> +
> +	ret = request_firmware(&image, path, fw_loader->dev);
> +	if (ret)
> +		return ret;
> +
> +	img = (struct vsc_img *)image->data;
> +	if (!img) {
> +		ret = -ENOENT;
> +		goto err_release_image;
> +	}
> +
> +	if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +
> +	if (le32_to_cpu(img->image_count) != VSC_ACE_IMG_CNT) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +	fw_loader->count += le32_to_cpu(img->image_count);
> +
> +	sign = (struct vsc_fw_sign *)
> +		(img->image_location + le32_to_cpu(img->image_count));
> +
> +	for (i = 0; i < VSC_ACE_IMG_CNT; i++) {
> +		/* mapping from ACE image index to image code data */
> +		static const struct vsc_image_code_data ace_image_map[] = {
> +			{ VSC_IMG_ACE_VISION_FRAG,
> VSC_IMG_ACE_VISION_TYPE },
> +			{ VSC_IMG_ACE_CFG_FRAG,
> VSC_IMG_ACE_CFG_TYPE },
> +		};
> +		struct vsc_img_frag *frag, *last_frag;
> +		u8 frag_index;
> +
> +		if ((u8 *)sign + sizeof(*sign) > image->data + image->size) {
> +			ret = -EINVAL;
> +			goto err_release_image;
> +		}
> +
> +		if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) {
> +			ret = -EINVAL;
> +			goto err_release_image;
> +		}
> +
> +		frag_index = ace_image_map[i].frag_index;
> +		frag = &fw_loader->frags[frag_index];
> +
> +		frag->data = sign->image;
> +		frag->size = le32_to_cpu(sign->image_size);
> +		frag->location = le32_to_cpu(img->image_location[i]);
> +		frag->type = ace_image_map[i].image_type;
> +
> +		if (!frag->location) {
> +			last_frag = &fw_loader->frags[frag_index - 1];
> +			frag->location =
> +				ALIGN(last_frag->location + last_frag->size,
> SZ_4K);
> +		}
> +
> +		sign = (struct vsc_fw_sign *)
> +			(sign->image + le32_to_cpu(sign->image_size));
> +	}
> +
> +	fw_loader->ace = image;
> +
> +	return 0;
> +
> +err_release_image:
> +	release_firmware(image);
> +
> +	return ret;
> +}
> +
> +static int vsc_identify_cfg_image(struct vsc_fw_loader *fw_loader) {
> +	struct vsc_img_frag *frag = &fw_loader-
> >frags[VSC_IMG_SKU_CFG_FRAG];
> +	char path[VSC_IMAGE_PATH_MAX_LEN];
> +	char name[VSC_IMAGE_NAME_MAX_LEN];
> +	const struct firmware *image;
> +	u32 size;
> +	int ret;
> +
> +	if (fw_loader->key_src == VSC_STRAP_KEY_SRC_PRODUCT)
> +		snprintf(name, sizeof(name),
> VSC_CFG_IMAGE_NAME_FMT_PROD,
> +			 fw_loader->sensor_name, fw_loader->suffix);
> +	else
> +		snprintf(name, sizeof(name), VSC_CFG_IMAGE_NAME_FMT,
> +			 fw_loader->sensor_name);
> +
> +	snprintf(path, sizeof(path), "%s/%s", fw_loader->folder, name);
> +
> +	ret = request_firmware(&image, path, fw_loader->dev);
> +	if (ret)
> +		return ret;
> +
> +	/* identify image size */
> +	if (image->size <= sizeof(u32) || image->size > VSC_SKU_MAX_SIZE) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +
> +	size = le32_to_cpu(*((__le32 *)image->data)) + sizeof(u32);
> +	if (image->size != size) {
> +		ret = -EINVAL;
> +		goto err_release_image;
> +	}
> +
> +	frag->data = image->data;
> +	frag->size = image->size;
> +	frag->type = VSC_IMG_SKU_CFG_TYPE;
> +	frag->location = VSC_SKU_CFG_LOCATION;
> +
> +	fw_loader->cfg = image;
> +
> +	return 0;
> +
> +err_release_image:
> +	release_firmware(image);
> +
> +	return ret;
> +}
> +
> +static int vsc_download_bootloader(struct vsc_fw_loader *fw_loader) {
> +	struct vsc_img_frag *frag = &fw_loader-
> >frags[VSC_IMG_BOOTLOADER_FRAG];
> +	struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf;
> +	struct vsc_rom_cmd *cmd = fw_loader->tx_buf;
> +	u32 len, c_len;
> +	size_t remain;
> +	const u8 *p;
> +	int ret;
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_QUERY;
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +	if (ack->token != VSC_TOKEN_DUMP_RESP &&
> +	    ack->token != VSC_TOKEN_BOOTLOADER_REQ)
> +		return -EINVAL;
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_DL_START;
> +	cmd->data.dl_start.option = cpu_to_le16(fw_loader->option);
> +	cmd->data.dl_start.img_type = frag->type;
> +	cmd->data.dl_start.img_len = cpu_to_le32(frag->size);
> +	cmd->data.dl_start.img_loc = cpu_to_le32(frag->location);
> +
> +	c_len = offsetof(struct vsc_rom_cmd, data.dl_start.crc);
> +	cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len));
> +
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL,
> VSC_ROM_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +
> +	p = frag->data;
> +	remain = frag->size;
> +
> +	/* download image data */
> +	while (remain > 0) {
> +		len = min(remain, sizeof(cmd->data.dl_cont.payload));
> +
> +		cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +		cmd->cmd_id = VSC_CMD_DL_CONT;
> +		cmd->data.dl_cont.len = cpu_to_le16(len);
> +		cmd->data.dl_cont.end_flag = remain == len;
> +		memcpy(cmd->data.dl_cont.payload, p, len);
> +
> +		ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL,
> VSC_ROM_PKG_SIZE);
> +		if (ret)
> +			return ret;
> +
> +		p += len;
> +		remain -= len;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vsc_download_firmware(struct vsc_fw_loader *fw_loader) {
> +	struct vsc_fw_cmd *cmd = fw_loader->tx_buf;
> +	unsigned int i, index = 0;
> +	u32 c_len;
> +	int ret;
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_DL_SET;
> +	cmd->data.dl_set.img_cnt = cpu_to_le16(fw_loader->count);
> +	put_unaligned_le16(fw_loader->option, &cmd->data.dl_set.option);
> +
> +	for (i = VSC_IMG_CSI_SEM_FRAG; i <= VSC_IMG_CSI_EM7D_FRAG;
> i++) {
> +		struct vsc_img_frag *frag = &fw_loader->frags[i];
> +
> +		cmd->data.dl_set.payload[index++] = cpu_to_le32(frag-
> >location);
> +		cmd->data.dl_set.payload[index++] = cpu_to_le32(frag-
> >size);
> +	}
> +
> +	c_len = offsetof(struct vsc_fw_cmd, data.dl_set.payload[index]);
> +	cmd->data.dl_set.payload[index] = cpu_to_le32(vsc_sum_crc(cmd,
> +c_len));
> +
> +	ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL,
> VSC_FW_PKG_SIZE);
> +	if (ret)
> +		return ret;
> +
> +	for (i = VSC_IMG_CSI_SEM_FRAG; i < VSC_IMG_FRAG_MAX; i++) {
> +		struct vsc_img_frag *frag = &fw_loader->frags[i];
> +		const u8 *p;
> +		u32 remain;
> +
> +		cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +		cmd->cmd_id = VSC_CMD_DL_START;
> +		cmd->data.dl_start.img_type = frag->type;
> +		cmd->data.dl_start.img_len = cpu_to_le32(frag->size);
> +		cmd->data.dl_start.img_loc = cpu_to_le32(frag->location);
> +		put_unaligned_le16(fw_loader->option, &cmd-
> >data.dl_start.option);
> +
> +		c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc);
> +		cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd,
> c_len));
> +
> +		ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL,
> VSC_FW_PKG_SIZE);
> +		if (ret)
> +			return ret;
> +
> +		p = frag->data;
> +		remain = frag->size;
> +
> +		/* download image data */
> +		while (remain > 0) {
> +			u32 len = min(remain, VSC_FW_PKG_SIZE);
> +
> +			memcpy(fw_loader->tx_buf, p, len);
> +			memset(fw_loader->tx_buf + len, 0,
> VSC_FW_PKG_SIZE - len);
> +
> +			ret = vsc_tp_rom_xfer(fw_loader->tp, fw_loader-
> >tx_buf,
> +					      NULL, VSC_FW_PKG_SIZE);
> +			if (ret)
> +				break;
> +
> +			p += len;
> +			remain -= len;
> +		}
> +	}
> +
> +	cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
> +	cmd->cmd_id = VSC_CMD_CAM_BOOT;
> +
> +	c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc);
> +	cmd->data.boot.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len));
> +
> +	return vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL,
> VSC_FW_PKG_SIZE); }
> +
> +/**
> + * vsc_tp_init - init vsc_tp
> + * @tp: vsc_tp device handle
> + * @dev: device node for mei vsc device
> + * Return: 0 in case of success, negative value in case of error  */
> +int vsc_tp_init(struct vsc_tp *tp, struct device *dev) {
> +	struct vsc_fw_loader *fw_loader __free(kfree) = NULL;
> +	void *tx_buf __free(kfree) = NULL;
> +	void *rx_buf __free(kfree) = NULL;
> +	int ret;
> +
> +	fw_loader = kzalloc(sizeof(*fw_loader), GFP_KERNEL);
> +	if (!fw_loader)
> +		return -ENOMEM;
> +
> +	tx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL);
> +	if (!tx_buf)
> +		return -ENOMEM;
> +
> +	rx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL);
> +	if (!rx_buf)
> +		return -ENOMEM;
> +
> +	fw_loader->tx_buf = tx_buf;
> +	fw_loader->rx_buf = rx_buf;
> +
> +	fw_loader->tp = tp;
> +	fw_loader->dev = dev;
> +
> +	ret = vsc_get_sensor_name(fw_loader, dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = vsc_identify_silicon(fw_loader);
> +	if (ret)
> +		return ret;
> +
> +	if (fw_loader->key_src == VSC_STRAP_KEY_SRC_PRODUCT)
> +		snprintf(fw_loader->folder, sizeof(fw_loader->folder),
> +			 VSC_IMAGE_FOLDER_FMT_PROD, fw_loader-
> >suffix);
> +	else
> +		snprintf(fw_loader->folder, sizeof(fw_loader->folder),
> +			 VSC_IMAGE_FOLDER_FMT);
> +
> +	ret = vsc_identify_csi_image(fw_loader);
> +	if (ret)
> +		return ret;
> +
> +	ret = vsc_identify_ace_image(fw_loader);
> +	if (ret)
> +		goto err_release_csi;
> +
> +	ret = vsc_identify_cfg_image(fw_loader);
> +	if (ret)
> +		goto err_release_ace;
> +
> +	ret = vsc_download_bootloader(fw_loader);
> +	if (!ret)
> +		ret = vsc_download_firmware(fw_loader);
> +
> +	release_firmware(fw_loader->cfg);
> +
> +err_release_ace:
> +	release_firmware(fw_loader->ace);
> +
> +err_release_csi:
> +	release_firmware(fw_loader->csi);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_init, VSC_TP);
> diff --git a/drivers/misc/mei/vsc-tp.c b/drivers/misc/mei/vsc-tp.c new file
> mode 100644 index 0000000..6f4a4be
> --- /dev/null
> +++ b/drivers/misc/mei/vsc-tp.c
> @@ -0,0 +1,555 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2023, Intel Corporation.
> + * Intel Visual Sensing Controller Transport Layer Linux driver  */
> +
> +#include <linux/acpi.h>
> +#include <linux/cleanup.h>
> +#include <linux/crc32.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/irq.h>
> +#include <linux/irqreturn.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +#include "vsc-tp.h"
> +
> +#define VSC_TP_RESET_PIN_TOGGLE_INTERVAL_MS	20
> +#define VSC_TP_ROM_BOOTUP_DELAY_MS		10
> +#define VSC_TP_ROM_XFER_POLL_TIMEOUT_US		(500 *
> USEC_PER_MSEC)
> +#define VSC_TP_ROM_XFER_POLL_DELAY_US		(20 *
> USEC_PER_MSEC)
> +#define VSC_TP_WAIT_FW_ASSERTED_TIMEOUT		(2 * HZ)
> +#define VSC_TP_MAX_XFER_COUNT			5
> +
> +#define VSC_TP_PACKET_SYNC			0x31
> +#define VSC_TP_CRC_SIZE				sizeof(u32)
> +#define VSC_TP_MAX_MSG_SIZE			2048
> +/* SPI xfer timeout size */
> +#define VSC_TP_XFER_TIMEOUT_BYTES		700
> +#define VSC_TP_PACKET_PADDING_SIZE		1
> +#define VSC_TP_PACKET_SIZE(pkt) \
> +	(sizeof(struct vsc_tp_packet) + le16_to_cpu((pkt)->len) +
> +VSC_TP_CRC_SIZE) #define VSC_TP_MAX_PACKET_SIZE \
> +	(sizeof(struct vsc_tp_packet) + VSC_TP_MAX_MSG_SIZE +
> VSC_TP_CRC_SIZE)
> +#define VSC_TP_MAX_XFER_SIZE \
> +	(VSC_TP_MAX_PACKET_SIZE + VSC_TP_XFER_TIMEOUT_BYTES)
> #define
> +VSC_TP_NEXT_XFER_LEN(len, offset) \
> +	(len + sizeof(struct vsc_tp_packet) + VSC_TP_CRC_SIZE - offset +
> +VSC_TP_PACKET_PADDING_SIZE)
> +
> +struct vsc_tp_packet {
> +	__u8 sync;
> +	__u8 cmd;
> +	__le16 len;
> +	__le32 seq;
> +	__u8 buf[] __counted_by(len);
> +};
> +
> +struct vsc_tp {
> +	/* do the actual data transfer */
> +	struct spi_device *spi;
> +
> +	/* bind with mei framework */
> +	struct platform_device *pdev;
> +
> +	struct gpio_desc *wakeuphost;
> +	struct gpio_desc *resetfw;
> +	struct gpio_desc *wakeupfw;
> +
> +	/* command sequence number */
> +	u32 seq;
> +
> +	/* command buffer */
> +	void *tx_buf;
> +	void *rx_buf;
> +
> +	atomic_t assert_cnt;
> +	wait_queue_head_t xfer_wait;
> +
> +	vsc_tp_event_cb_t event_notify;
> +	void *event_notify_context;
> +
> +	/* used to protect command download */
> +	struct mutex mutex;
> +};
> +
> +/* GPIO resources */
> +static const struct acpi_gpio_params wakeuphost_gpio = { 0, 0, false };
> +static const struct acpi_gpio_params wakeuphostint_gpio = { 1, 0, false
> +}; static const struct acpi_gpio_params resetfw_gpio = { 2, 0, false };
> +static const struct acpi_gpio_params wakeupfw = { 3, 0, false };
> +
> +static const struct acpi_gpio_mapping vsc_tp_acpi_gpios[] = {
> +	{ "wakeuphost-gpios", &wakeuphost_gpio, 1 },
> +	{ "wakeuphostint-gpios", &wakeuphostint_gpio, 1 },
> +	{ "resetfw-gpios", &resetfw_gpio, 1 },
> +	{ "wakeupfw-gpios", &wakeupfw, 1 },
> +	{}
> +};
> +
> +/* wakeup firmware and wait for response */ static int
> +vsc_tp_wakeup_request(struct vsc_tp *tp) {
> +	int ret;
> +
> +	gpiod_set_value_cansleep(tp->wakeupfw, 0);
> +
> +	ret = wait_event_timeout(tp->xfer_wait,
> +				 atomic_read(&tp->assert_cnt) &&
> +				 gpiod_get_value_cansleep(tp->wakeuphost),
> +				 VSC_TP_WAIT_FW_ASSERTED_TIMEOUT);
> +	if (!ret)
> +		return -ETIMEDOUT;
> +
> +	return 0;
> +}
> +
> +static void vsc_tp_wakeup_release(struct vsc_tp *tp) {
> +	atomic_dec_if_positive(&tp->assert_cnt);
> +
> +	gpiod_set_value_cansleep(tp->wakeupfw, 1); }
> +
> +static int vsc_tp_dev_xfer(struct vsc_tp *tp, void *obuf, void *ibuf,
> +size_t len) {
> +	struct spi_message msg = { 0 };
> +	struct spi_transfer xfer = {
> +		.tx_buf = obuf,
> +		.rx_buf = ibuf,
> +		.len = len,
> +	};
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +
> +	return spi_sync_locked(tp->spi, &msg); }
> +
> +static int vsc_tp_xfer_helper(struct vsc_tp *tp, struct vsc_tp_packet *pkt,
> +			      void *ibuf, u16 ilen)
> +{
> +	int ret, offset = 0, cpy_len, src_len, dst_len = sizeof(struct
> vsc_tp_packet);
> +	int next_xfer_len = VSC_TP_PACKET_SIZE(pkt) +
> VSC_TP_XFER_TIMEOUT_BYTES;
> +	u8 *src, *crc_src, *rx_buf = tp->rx_buf;
> +	int count_down = VSC_TP_MAX_XFER_COUNT;
> +	u32 recv_crc = 0, crc = ~0;
> +	struct vsc_tp_packet ack;
> +	u8 *dst = (u8 *)&ack;
> +	bool synced = false;
> +
> +	do {
> +		ret = vsc_tp_dev_xfer(tp, pkt, rx_buf, next_xfer_len);
> +		if (ret)
> +			return ret;
> +		memset(pkt, 0, VSC_TP_MAX_XFER_SIZE);
> +
> +		if (synced) {
> +			src = rx_buf;
> +			src_len = next_xfer_len;
> +		} else {
> +			src = memchr(rx_buf, VSC_TP_PACKET_SYNC,
> next_xfer_len);
> +			if (!src)
> +				continue;
> +			synced = true;
> +			src_len = next_xfer_len - (src - rx_buf);
> +		}
> +
> +		/* traverse received data */
> +		while (src_len > 0) {
> +			cpy_len = min(src_len, dst_len);
> +			memcpy(dst, src, cpy_len);
> +			crc_src = src;
> +			src += cpy_len;
> +			src_len -= cpy_len;
> +			dst += cpy_len;
> +			dst_len -= cpy_len;
> +
> +			if (offset < sizeof(ack)) {
> +				offset += cpy_len;
> +				crc = crc32(crc, crc_src, cpy_len);
> +
> +				if (!src_len)
> +					continue;
> +
> +				if (le16_to_cpu(ack.len)) {
> +					dst = ibuf;
> +					dst_len = min(ilen,
> le16_to_cpu(ack.len));
> +				} else {
> +					dst = (u8 *)&recv_crc;
> +					dst_len = sizeof(recv_crc);
> +				}
> +			} else if (offset < sizeof(ack) + le16_to_cpu(ack.len)) {
> +				offset += cpy_len;
> +				crc = crc32(crc, crc_src, cpy_len);
> +
> +				if (src_len) {
> +					int remain = sizeof(ack) +
> le16_to_cpu(ack.len) - offset;
> +
> +					cpy_len = min(src_len, remain);
> +					offset += cpy_len;
> +					crc = crc32(crc, src, cpy_len);
> +					src += cpy_len;
> +					src_len -= cpy_len;
> +					if (src_len) {
> +						dst = (u8 *)&recv_crc;
> +						dst_len = sizeof(recv_crc);
> +						continue;
> +					}
> +				}
> +				next_xfer_len =
> VSC_TP_NEXT_XFER_LEN(le16_to_cpu(ack.len), offset);
> +			} else if (offset < sizeof(ack) + le16_to_cpu(ack.len) +
> VSC_TP_CRC_SIZE) {
> +				offset += cpy_len;
> +
> +				if (src_len) {
> +					/* terminate the traverse */
> +					next_xfer_len = 0;
> +					break;
> +				}
> +				next_xfer_len =
> VSC_TP_NEXT_XFER_LEN(le16_to_cpu(ack.len), offset);
> +			}
> +		}
> +	} while (next_xfer_len > 0 && --count_down);
> +
> +	if (next_xfer_len > 0)
> +		return -EAGAIN;
> +
> +	if (~recv_crc != crc || le32_to_cpu(ack.seq) != tp->seq) {
> +		dev_err(&tp->spi->dev, "recv crc or seq error\n");
> +		return -EINVAL;
> +	}
> +
> +	if (ack.cmd == VSC_TP_CMD_ACK || ack.cmd == VSC_TP_CMD_NACK
> ||
> +	    ack.cmd == VSC_TP_CMD_BUSY) {
> +		dev_err(&tp->spi->dev, "recv cmd ack error\n");
> +		return -EAGAIN;
> +	}
> +
> +	return min(le16_to_cpu(ack.len), ilen); }
> +
> +/**
> + * vsc_tp_xfer - transfer data to firmware
> + * @tp: vsc_tp device handle
> + * @cmd: the command to be sent to the device
> + * @obuf: the tx buffer to be sent to the device
> + * @olen: the length of tx buffer
> + * @ibuf: the rx buffer to receive from the device
> + * @ilen: the length of rx buffer
> + * Return: the length of received data in case of success,
> + *	otherwise negative value
> + */
> +int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen,
> +		void *ibuf, size_t ilen)
> +{
> +	struct vsc_tp_packet *pkt = tp->tx_buf;
> +	u32 crc;
> +	int ret;
> +
> +	if (!obuf || !ibuf || olen > VSC_TP_MAX_MSG_SIZE)
> +		return -EINVAL;
> +
> +	guard(mutex)(&tp->mutex);
> +
> +	pkt->sync = VSC_TP_PACKET_SYNC;
> +	pkt->cmd = cmd;
> +	pkt->len = cpu_to_le16(olen);
> +	pkt->seq = cpu_to_le32(++tp->seq);
> +	memcpy(pkt->buf, obuf, olen);
> +
> +	crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt) + olen);
> +	memcpy(pkt->buf + olen, &crc, sizeof(crc));
> +
> +	ret = vsc_tp_wakeup_request(tp);
> +	if (unlikely(ret))
> +		dev_err(&tp->spi->dev, "wakeup firmware failed ret: %d\n",
> ret);
> +	else
> +		ret = vsc_tp_xfer_helper(tp, pkt, ibuf, ilen);
> +
> +	vsc_tp_wakeup_release(tp);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_xfer, VSC_TP);
> +
> +/**
> + * vsc_tp_rom_xfer - transfer data to rom code
> + * @tp: vsc_tp device handle
> + * @obuf: the data buffer to be sent to the device
> + * @ibuf: the buffer to receive data from the device
> + * @len: the length of tx buffer and rx buffer
> + * Return: 0 in case of success, negative value in case of error  */
> +int vsc_tp_rom_xfer(struct vsc_tp *tp, const void *obuf, void *ibuf,
> +size_t len) {
> +	size_t words = len / sizeof(__be32);
> +	int ret;
> +
> +	if (len % sizeof(__be32) || len > VSC_TP_MAX_MSG_SIZE)
> +		return -EINVAL;
> +
> +	guard(mutex)(&tp->mutex);
> +
> +	/* rom xfer is big endian */
> +	cpu_to_be32_array(tp->tx_buf, obuf, words);
> +
> +	ret = read_poll_timeout(gpiod_get_value_cansleep, ret,
> +				!ret, VSC_TP_ROM_XFER_POLL_DELAY_US,
> +				VSC_TP_ROM_XFER_POLL_TIMEOUT_US,
> false,
> +				tp->wakeuphost);
> +	if (ret) {
> +		dev_err(&tp->spi->dev, "wait rom failed ret: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = vsc_tp_dev_xfer(tp, tp->tx_buf, tp->rx_buf, len);
> +	if (ret)
> +		return ret;
> +
> +	if (ibuf)
> +		cpu_to_be32_array(ibuf, tp->rx_buf, words);
> +
> +	return ret;
> +}
> +
> +/**
> + * vsc_tp_reset - reset vsc transport layer
> + * @tp: vsc_tp device handle
> + */
> +void vsc_tp_reset(struct vsc_tp *tp)
> +{
> +	disable_irq(tp->spi->irq);
> +
> +	/* toggle reset pin */
> +	gpiod_set_value_cansleep(tp->resetfw, 0);
> +	msleep(VSC_TP_RESET_PIN_TOGGLE_INTERVAL_MS);
> +	gpiod_set_value_cansleep(tp->resetfw, 1);
> +
> +	/* wait for ROM */
> +	msleep(VSC_TP_ROM_BOOTUP_DELAY_MS);
> +
> +	/*
> +	 * Set default host wakeup pin to non-active
> +	 * to avoid unexpected host irq interrupt.
> +	 */
> +	gpiod_set_value_cansleep(tp->wakeupfw, 1);
> +
> +	atomic_set(&tp->assert_cnt, 0);
> +
> +	enable_irq(tp->spi->irq);
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_reset, VSC_TP);
> +
> +/**
> + * vsc_tp_need_read - check if device has data to sent
> + * @tp: vsc_tp device handle
> + * Return: true if device has data to sent, otherwise false  */ bool
> +vsc_tp_need_read(struct vsc_tp *tp) {
> +	if (!atomic_read(&tp->assert_cnt))
> +		return false;
> +	if (!gpiod_get_value_cansleep(tp->wakeuphost))
> +		return false;
> +	if (!gpiod_get_value_cansleep(tp->wakeupfw))
> +		return false;
> +
> +	return true;
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_need_read, VSC_TP);
> +
> +/**
> + * vsc_tp_register_event_cb - register a callback function to receive
> +event
> + * @tp: vsc_tp device handle
> + * @event_cb: callback function
> + * @context: execution context of event callback
> + * Return: 0 in case of success, negative value in case of error  */
> +int vsc_tp_register_event_cb(struct vsc_tp *tp, vsc_tp_event_cb_t event_cb,
> +			    void *context)
> +{
> +	tp->event_notify = event_cb;
> +	tp->event_notify_context = context;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_register_event_cb, VSC_TP);
> +
> +/**
> + * vsc_tp_intr_synchronize - synchronize vsc_tp interrupt
> + * @tp: vsc_tp device handle
> + */
> +void vsc_tp_intr_synchronize(struct vsc_tp *tp) {
> +	synchronize_irq(tp->spi->irq);
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_synchronize, VSC_TP);
> +
> +/**
> + * vsc_tp_intr_enable - enable vsc_tp interrupt
> + * @tp: vsc_tp device handle
> + */
> +void vsc_tp_intr_enable(struct vsc_tp *tp) {
> +	enable_irq(tp->spi->irq);
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_enable, VSC_TP);
> +
> +/**
> + * vsc_tp_intr_disable - disable vsc_tp interrupt
> + * @tp: vsc_tp device handle
> + */
> +void vsc_tp_intr_disable(struct vsc_tp *tp) {
> +	disable_irq(tp->spi->irq);
> +}
> +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_disable, VSC_TP);
> +
> +static irqreturn_t vsc_tp_isr(int irq, void *data) {
> +	struct vsc_tp *tp = data;
> +
> +	atomic_inc(&tp->assert_cnt);
> +
> +	wake_up(&tp->xfer_wait);
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static irqreturn_t vsc_tp_thread_isr(int irq, void *data) {
> +	struct vsc_tp *tp = data;
> +
> +	if (tp->event_notify)
> +		tp->event_notify(tp->event_notify_context);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int vsc_tp_match_any(struct acpi_device *adev, void *data) {
> +	struct acpi_device **__adev = data;
> +
> +	*__adev = adev;
> +
> +	return 1;
> +}
> +
> +static int vsc_tp_probe(struct spi_device *spi) {
> +	struct platform_device_info pinfo = { 0 };
> +	struct device *dev = &spi->dev;
> +	struct platform_device *pdev;
> +	struct acpi_device *adev;
> +	struct vsc_tp *tp;
> +	int ret;
> +
> +	tp = devm_kzalloc(dev, sizeof(*tp), GFP_KERNEL);
> +	if (!tp)
> +		return -ENOMEM;
> +
> +	tp->tx_buf = devm_kzalloc(dev, VSC_TP_MAX_XFER_SIZE,
> GFP_KERNEL);
> +	if (!tp->tx_buf)
> +		return -ENOMEM;
> +
> +	tp->rx_buf = devm_kzalloc(dev, VSC_TP_MAX_XFER_SIZE,
> GFP_KERNEL);
> +	if (!tp->rx_buf)
> +		return -ENOMEM;
> +
> +	ret = devm_acpi_dev_add_driver_gpios(dev, vsc_tp_acpi_gpios);
> +	if (ret)
> +		return ret;
> +
> +	tp->wakeuphost = devm_gpiod_get(dev, "wakeuphost", GPIOD_IN);
> +	if (IS_ERR(tp->wakeuphost))
> +		return PTR_ERR(tp->wakeuphost);
> +
> +	tp->resetfw = devm_gpiod_get(dev, "resetfw", GPIOD_OUT_HIGH);
> +	if (IS_ERR(tp->resetfw))
> +		return PTR_ERR(tp->resetfw);
> +
> +	tp->wakeupfw = devm_gpiod_get(dev, "wakeupfw",
> GPIOD_OUT_HIGH);
> +	if (IS_ERR(tp->wakeupfw))
> +		return PTR_ERR(tp->wakeupfw);
> +
> +	atomic_set(&tp->assert_cnt, 0);
> +	init_waitqueue_head(&tp->xfer_wait);
> +	tp->spi = spi;
> +
> +	irq_set_status_flags(spi->irq, IRQ_DISABLE_UNLAZY);
> +	ret = devm_request_threaded_irq(dev, spi->irq, vsc_tp_isr,
> +					vsc_tp_thread_isr,
> +					IRQF_TRIGGER_FALLING |
> IRQF_ONESHOT,
> +					dev_name(dev), tp);
> +	if (ret)
> +		return ret;
> +
> +	mutex_init(&tp->mutex);
> +
> +	/* only one child acpi device */
> +	ret = acpi_dev_for_each_child(ACPI_COMPANION(dev),
> +				      vsc_tp_match_any, &adev);
> +	if (!ret) {
> +		ret = -ENODEV;
> +		goto err_destroy_lock;
> +	}
> +	pinfo.fwnode = acpi_fwnode_handle(adev);
> +
> +	pinfo.name = "intel_vsc";
> +	pinfo.data = &tp;
> +	pinfo.size_data = sizeof(tp);
> +	pinfo.id = PLATFORM_DEVID_NONE;
> +
> +	pdev = platform_device_register_full(&pinfo);
> +	if (IS_ERR(pdev)) {
> +		ret = PTR_ERR(pdev);
> +		goto err_destroy_lock;
> +	}
> +
> +	tp->pdev = pdev;
> +	spi_set_drvdata(spi, tp);
> +
> +	return 0;
> +
> +err_destroy_lock:
> +	mutex_destroy(&tp->mutex);
> +
> +	return ret;
> +}
> +
> +static void vsc_tp_remove(struct spi_device *spi) {
> +	struct vsc_tp *tp = spi_get_drvdata(spi);
> +
> +	platform_device_unregister(tp->pdev);
> +
> +	mutex_destroy(&tp->mutex);
> +}
> +
> +static const struct acpi_device_id vsc_tp_acpi_ids[] = {
> +	{ "INTC1009" }, /* Raptor Lake */
> +	{ "INTC1058" }, /* Tiger Lake */
> +	{ "INTC1094" }, /* Alder Lake */
> +	{}
> +};
> +MODULE_DEVICE_TABLE(acpi, vsc_tp_acpi_ids);
> +
> +static struct spi_driver vsc_tp_driver = {
> +	.probe = vsc_tp_probe,
> +	.remove = vsc_tp_remove,
> +	.driver = {
> +		.name = "vsc-tp",
> +		.acpi_match_table = vsc_tp_acpi_ids,
> +	},
> +};
> +module_spi_driver(vsc_tp_driver);
> +
> +MODULE_AUTHOR("Wentong Wu <wentong.wu@...el.com>");
> +MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@...el.com>");
> +MODULE_DESCRIPTION("Intel Visual Sensing Controller Transport Layer");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/misc/mei/vsc-tp.h b/drivers/misc/mei/vsc-tp.h new file
> mode 100644 index 0000000..f9513dd
> --- /dev/null
> +++ b/drivers/misc/mei/vsc-tp.h
> @@ -0,0 +1,50 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2023, Intel Corporation.
> + * Intel Visual Sensing Controller Transport Layer Linux driver  */
> +
> +#ifndef _VSC_TP_H_
> +#define _VSC_TP_H_
> +
> +#include <linux/types.h>
> +
> +#define VSC_TP_CMD_WRITE	0x01
> +#define VSC_TP_CMD_READ		0x02
> +
> +#define VSC_TP_CMD_ACK		0x10
> +#define VSC_TP_CMD_NACK		0x11
> +#define VSC_TP_CMD_BUSY		0x12
> +
> +struct vsc_tp;
> +
> +/**
> + * typedef vsc_event_cb_t - event callback function signature
> + * @context: the execution context of who registered this callback
> + *
> + * The callback function is called in interrupt context and the data
> + * payload is only valid during the call. If the user needs access
> + * the data payload later, it must copy the payload.
> + */
> +typedef void (*vsc_tp_event_cb_t)(void *context);
> +
> +int vsc_tp_rom_xfer(struct vsc_tp *tp, const void *obuf, void *ibuf,
> +		    size_t len);
> +
> +int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen,
> +		void *ibuf, size_t ilen);
> +
> +int vsc_tp_register_event_cb(struct vsc_tp *tp, vsc_tp_event_cb_t event_cb,
> +			     void *context);
> +
> +void vsc_tp_intr_enable(struct vsc_tp *tp); void
> +vsc_tp_intr_disable(struct vsc_tp *tp); void
> +vsc_tp_intr_synchronize(struct vsc_tp *tp);
> +
> +void vsc_tp_reset(struct vsc_tp *tp);
> +
> +bool vsc_tp_need_read(struct vsc_tp *tp);
> +
> +int vsc_tp_init(struct vsc_tp *tp, struct device *dev);
> +
> +#endif
> --
> 2.7.4

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ