pax_global_header00006660000000000000000000000064145230646050014517gustar00rootroot0000000000000052 comment=73a044d9633212fac54ea96cdd882ff5ab40573e ivsc-driver-0~git202311021215.73a044d9/000077500000000000000000000000001452306460500165615ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/Makefile000066400000000000000000000021411452306460500202170ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2022 Intel Corporation. obj-m += ljca.o ljca-y := drivers/mfd/ljca.o obj-m += spi-ljca.o spi-ljca-y := drivers/spi/spi-ljca.o obj-m += gpio-ljca.o gpio-ljca-y := drivers/gpio/gpio-ljca.o obj-m += i2c-ljca.o i2c-ljca-y := drivers/i2c/busses/i2c-ljca.o obj-m += mei-vsc.o mei-vsc-y := drivers/misc/mei/spi-vsc.o mei-vsc-y += drivers/misc/mei/hw-vsc.o obj-m += intel_vsc.o intel_vsc-y := drivers/misc/ivsc/intel_vsc.o obj-m += mei_csi.o mei_csi-y := drivers/misc/ivsc/mei_csi.o obj-m += mei_ace.o mei_ace-y := drivers/misc/ivsc/mei_ace.o obj-m += mei_pse.o mei_pse-y := drivers/misc/ivsc/mei_pse.o obj-m += mei_ace_debug.o mei_ace_debug-y := drivers/misc/ivsc/mei_ace_debug.o KERNELRELEASE ?= $(shell uname -r) KERNEL_SRC ?= /lib/modules/$(KERNELRELEASE)/build PWD := $(shell pwd) ccflags-y += -I$(src)/include/ ccflags-y += -I$(src)/backport-include/drivers/misc/mei/ all: $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules modules_install: $(MAKE) INSTALL_MOD_DIR=/updates -C $(KERNEL_SRC) M=$(PWD) modules_install clean: $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean ivsc-driver-0~git202311021215.73a044d9/README.md000066400000000000000000000100051452306460500200340ustar00rootroot00000000000000# ivsc-driver This repository supports Intel Vision Sensing Controller(IVSC) on Intel Alder Lake platforms. ## Build instructions: Three ways are available: 1. build with kernel source tree 2. build out of kernel source tree 3. build with dkms ### build with kernel source tree * Tested with kernel 5.12-rc4 and 5.13-rc5 * Check out kernel * Copy repo content to kernel source * Modify related Kconfig and Makefile * Add to drivers/mfd/Kconfig ``` config MFD_LJCA tristate "Intel La Jolla Cove Adapter support" select MFD_CORE depends on USB help This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO Adapter (LJCA). Additional drivers such as I2C_LJCA, GPIO_LJCA, etc. must be enabled in order to use the functionality of the device. ``` * add to drivers/mfd/Makefile ``` obj-$(CONFIG_MFD_LJCA) += ljca.o ``` * Add to drivers/spi/Kconfig ``` config SPI_LJCA tristate "INTEL La Jolla Cove Adapter SPI support" depends on MFD_LJCA help Select this option to enable SPI driver for the INTEL La Jolla Cove Adapter (LJCA) board. This driver can also be built as a module. If so, the module will be called spi-ljca. ``` * Add to drivers/spi/Makefile ``` obj-$(CONFIG_SPI_LJCA) += spi-ljca.o ``` * Add to drivers/gpio/Kconfig ``` config GPIO_LJCA tristate "INTEL La Jolla Cove Adapter GPIO support" depends on MFD_LJCA help Select this option to enable GPIO driver for the INTEL La Jolla Cove Adapter (LJCA) board. This driver can also be built as a module. If so, the module will be called gpio-ljca. ``` * Add to drivers/gpio/Makefile ``` obj-$(CONFIG_GPIO_LJCA) += gpio-ljca.o ``` * Add to drivers/i2c/busses/Kconfig ``` config I2C_LJCA tristate "I2C functionality of INTEL La Jolla Cove Adapter" depends on MFD_LJCA help If you say yes to this option, I2C functionality support of INTEL La Jolla Cove Adapter (LJCA) will be included. This driver can also be built as a module. If so, the module will be called i2c-ljca. ``` * Add to drivers/i2c/busses/Makefile ``` obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o ``` * Add to drivers/misc/mei/Kconfig ``` config INTEL_MEI_VSC tristate "Intel Vision Sensing Controller device with ME interface" select INTEL_MEI depends on X86 && SPI help MEI over SPI for Intel Vision Sensing Controller device ``` * Add to drivers/misc/mei/Makefile ``` obj-$(CONFIG_INTEL_MEI_VSC) += mei-vsc.o mei-vsc-objs := spi-vsc.o mei-vsc-objs += hw-vsc.o ``` * Add to drivers/misc/Kconfig ``` source "drivers/misc/ivsc/Kconfig" ``` * Add to drivers/misc/Makefile ``` obj-$(CONFIG_INTEL_VSC) += ivsc/ ``` * Enable the following settings in .config ``` CONFIG_MFD_LJCA=m CONFIG_I2C_LJCA=m CONFIG_SPI_LJCA=m CONFIG_GPIO_LJCA=m CONFIG_INTEL_MEI_VSC=m CONFIG_INTEL_VSC=m CONFIG_INTEL_VSC_CSI=m CONFIG_INTEL_VSC_ACE=m CONFIG_INTEL_VSC_PSE=m CONFIG_INTEL_VSC_ACE_DEBUG=m ``` ### build out of kernel source tree * Requires 5.13 or later kernel header installed on compiling machine * To compile: ``` $cd ivsc-driver $make -j`nproc` ``` * To install and use modules ``` $sudo make modules_install $sudo depmod -a ``` ### Build with dkms a dkms.conf file is also provided as an example for building with dkms which can be used by ```dkms``` ```add```, ```build``` and ```install```. ## Deployment: ivsc firmware bins should be copied to /lib/firmware/vsc. And on debugging platform the binaries(e.g. ov01a10 sensor) will be put as below: ``` /lib/firmware/vsc/soc_a1/ivsc_fw_a1.bin /lib/firmware/vsc/soc_a1/ivsc_pkg_ovti01a0_0_a1.bin /lib/firmware/vsc/soc_a1/ivsc_skucfg_ovti01a0_0_1_a1.bin ``` And on production platform the binaries(e.g. ov01a10 sensor) will be put as below: ``` /lib/firmware/vsc/soc_a1_prod/ivsc_fw_a1_prod.bin /lib/firmware/vsc/soc_a1_prod/ivsc_pkg_ovti01a0_0_a1_prod.bin /lib/firmware/vsc/soc_a1_prod/ivsc_skucfg_ovti01a0_0_1_a1_prod.bin ``` ivsc-driver-0~git202311021215.73a044d9/backport-include/000077500000000000000000000000001452306460500220075ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/000077500000000000000000000000001452306460500234655ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/misc/000077500000000000000000000000001452306460500244205ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/misc/mei/000077500000000000000000000000001452306460500251725ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/misc/mei/hbm.h000066400000000000000000000037141452306460500261160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2003-2018, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_HBM_H_ #define _MEI_HBM_H_ struct mei_device; struct mei_msg_hdr; struct mei_cl; struct mei_dma_data; /** * enum mei_hbm_state - host bus message protocol state * * @MEI_HBM_IDLE : protocol not started * @MEI_HBM_STARTING : start request message was sent * @MEI_HBM_CAP_SETUP : capabilities request message was sent * @MEI_HBM_DR_SETUP : dma ring setup request message was sent * @MEI_HBM_ENUM_CLIENTS : enumeration request was sent * @MEI_HBM_CLIENT_PROPERTIES : acquiring clients properties * @MEI_HBM_STARTED : enumeration was completed * @MEI_HBM_STOPPED : stopping exchange */ enum mei_hbm_state { MEI_HBM_IDLE = 0, MEI_HBM_STARTING, MEI_HBM_CAP_SETUP, MEI_HBM_DR_SETUP, MEI_HBM_ENUM_CLIENTS, MEI_HBM_CLIENT_PROPERTIES, MEI_HBM_STARTED, MEI_HBM_STOPPED, }; const char *mei_hbm_state_str(enum mei_hbm_state state); int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); void mei_hbm_idle(struct mei_device *dev); void mei_hbm_reset(struct mei_device *dev); int mei_hbm_start_req(struct mei_device *dev); int mei_hbm_start_wait(struct mei_device *dev); int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); bool mei_hbm_version_is_supported(struct mei_device *dev); int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); void mei_hbm_pg_resume(struct mei_device *dev); int mei_hbm_cl_notify_req(struct mei_device *dev, struct mei_cl *cl, u8 request); int mei_hbm_cl_dma_map_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_dma_unmap_req(struct mei_device *dev, struct mei_cl *cl); #endif /* _MEI_HBM_H_ */ ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/misc/mei/hw.h000066400000000000000000000514231452306460500257660ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2003-2022, Intel Corporation. All rights reserved * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_HW_TYPES_H_ #define _MEI_HW_TYPES_H_ #include #include /* * Timeouts in Seconds */ #define MEI_HW_READY_TIMEOUT 2 /* Timeout on ready message */ #define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */ #define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ #define MEI_CL_CONNECT_TIMEOUT_SLOW 30 /* HPS: Client Connect Timeout, slow FW */ #define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */ #define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ #define MEI_D0I3_TIMEOUT 5 /* D0i3 set/unset max response time */ #define MEI_HBM_TIMEOUT 1 /* 1 second */ #define MEI_HBM_TIMEOUT_SLOW 5 /* 5 second, slow FW */ #define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ #define MKHI_RCV_TIMEOUT_SLOW 10000 /* receive timeout in msec, slow FW */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) /* * FW page size for DMA allocations */ #define MEI_FW_PAGE_SIZE 4096UL #endif /* * MEI Version */ #define HBM_MINOR_VERSION 2 #define HBM_MAJOR_VERSION 2 /* * MEI version with PGI support */ #define HBM_MINOR_VERSION_PGI 1 #define HBM_MAJOR_VERSION_PGI 1 /* * MEI version with Dynamic clients support */ #define HBM_MINOR_VERSION_DC 0 #define HBM_MAJOR_VERSION_DC 2 /* * MEI version with immediate reply to enum request support */ #define HBM_MINOR_VERSION_IE 0 #define HBM_MAJOR_VERSION_IE 2 /* * MEI version with disconnect on connection timeout support */ #define HBM_MINOR_VERSION_DOT 0 #define HBM_MAJOR_VERSION_DOT 2 /* * MEI version with notification support */ #define HBM_MINOR_VERSION_EV 0 #define HBM_MAJOR_VERSION_EV 2 /* * MEI version with fixed address client support */ #define HBM_MINOR_VERSION_FA 0 #define HBM_MAJOR_VERSION_FA 2 /* * MEI version with OS ver message support */ #define HBM_MINOR_VERSION_OS 0 #define HBM_MAJOR_VERSION_OS 2 /* * MEI version with dma ring support */ #define HBM_MINOR_VERSION_DR 1 #define HBM_MAJOR_VERSION_DR 2 /* * MEI version with vm tag support */ #define HBM_MINOR_VERSION_VT 2 #define HBM_MAJOR_VERSION_VT 2 #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0) /* * MEI version with GSC support */ #define HBM_MINOR_VERSION_GSC 2 #define HBM_MAJOR_VERSION_GSC 2 #endif /* * MEI version with capabilities message support */ #define HBM_MINOR_VERSION_CAP 2 #define HBM_MAJOR_VERSION_CAP 2 /* * MEI version with client DMA support */ #define HBM_MINOR_VERSION_CD 2 #define HBM_MAJOR_VERSION_CD 2 /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f /* Host bus message command RESPONSE */ #define MEI_HBM_CMD_RES_MSK 0x80 /* * MEI Bus Message Command IDs */ #define HOST_START_REQ_CMD 0x01 #define HOST_START_RES_CMD 0x81 #define HOST_STOP_REQ_CMD 0x02 #define HOST_STOP_RES_CMD 0x82 #define ME_STOP_REQ_CMD 0x03 #define HOST_ENUM_REQ_CMD 0x04 #define HOST_ENUM_RES_CMD 0x84 #define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05 #define HOST_CLIENT_PROPERTIES_RES_CMD 0x85 #define CLIENT_CONNECT_REQ_CMD 0x06 #define CLIENT_CONNECT_RES_CMD 0x86 #define CLIENT_DISCONNECT_REQ_CMD 0x07 #define CLIENT_DISCONNECT_RES_CMD 0x87 #define MEI_FLOW_CONTROL_CMD 0x08 #define MEI_PG_ISOLATION_ENTRY_REQ_CMD 0x0a #define MEI_PG_ISOLATION_ENTRY_RES_CMD 0x8a #define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b #define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b #define MEI_HBM_ADD_CLIENT_REQ_CMD 0x0f #define MEI_HBM_ADD_CLIENT_RES_CMD 0x8f #define MEI_HBM_NOTIFY_REQ_CMD 0x10 #define MEI_HBM_NOTIFY_RES_CMD 0x90 #define MEI_HBM_NOTIFICATION_CMD 0x11 #define MEI_HBM_DMA_SETUP_REQ_CMD 0x12 #define MEI_HBM_DMA_SETUP_RES_CMD 0x92 #define MEI_HBM_CAPABILITIES_REQ_CMD 0x13 #define MEI_HBM_CAPABILITIES_RES_CMD 0x93 #define MEI_HBM_CLIENT_DMA_MAP_REQ_CMD 0x14 #define MEI_HBM_CLIENT_DMA_MAP_RES_CMD 0x94 #define MEI_HBM_CLIENT_DMA_UNMAP_REQ_CMD 0x15 #define MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD 0x95 /* * MEI Stop Reason * used by hbm_host_stop_request.reason */ enum mei_stop_reason_types { DRIVER_STOP_REQUEST = 0x00, DEVICE_D1_ENTRY = 0x01, DEVICE_D2_ENTRY = 0x02, DEVICE_D3_ENTRY = 0x03, SYSTEM_S1_ENTRY = 0x04, SYSTEM_S2_ENTRY = 0x05, SYSTEM_S3_ENTRY = 0x06, SYSTEM_S4_ENTRY = 0x07, SYSTEM_S5_ENTRY = 0x08 }; /** * enum mei_hbm_status - mei host bus messages return values * * @MEI_HBMS_SUCCESS : status success * @MEI_HBMS_CLIENT_NOT_FOUND : client not found * @MEI_HBMS_ALREADY_EXISTS : connection already established * @MEI_HBMS_REJECTED : connection is rejected * @MEI_HBMS_INVALID_PARAMETER : invalid parameter * @MEI_HBMS_NOT_ALLOWED : operation not allowed * @MEI_HBMS_ALREADY_STARTED : system is already started * @MEI_HBMS_NOT_STARTED : system not started * * @MEI_HBMS_MAX : sentinel */ enum mei_hbm_status { MEI_HBMS_SUCCESS = 0, MEI_HBMS_CLIENT_NOT_FOUND = 1, MEI_HBMS_ALREADY_EXISTS = 2, MEI_HBMS_REJECTED = 3, MEI_HBMS_INVALID_PARAMETER = 4, MEI_HBMS_NOT_ALLOWED = 5, MEI_HBMS_ALREADY_STARTED = 6, MEI_HBMS_NOT_STARTED = 7, MEI_HBMS_MAX }; /* * Client Connect Status * used by hbm_client_connect_response.status */ enum mei_cl_connect_status { MEI_CL_CONN_SUCCESS = MEI_HBMS_SUCCESS, MEI_CL_CONN_NOT_FOUND = MEI_HBMS_CLIENT_NOT_FOUND, MEI_CL_CONN_ALREADY_STARTED = MEI_HBMS_ALREADY_EXISTS, MEI_CL_CONN_OUT_OF_RESOURCES = MEI_HBMS_REJECTED, MEI_CL_CONN_MESSAGE_SMALL = MEI_HBMS_INVALID_PARAMETER, MEI_CL_CONN_NOT_ALLOWED = MEI_HBMS_NOT_ALLOWED, }; /* * Client Disconnect Status */ enum mei_cl_disconnect_status { MEI_CL_DISCONN_SUCCESS = MEI_HBMS_SUCCESS }; /** * enum mei_ext_hdr_type - extended header type used in * extended header TLV * * @MEI_EXT_HDR_NONE: sentinel * @MEI_EXT_HDR_VTAG: vtag header */ enum mei_ext_hdr_type { MEI_EXT_HDR_NONE = 0, MEI_EXT_HDR_VTAG = 1, MEI_EXT_HDR_GSC = 2, }; /** * struct mei_ext_hdr - extend header descriptor (TLV) * @type: enum mei_ext_hdr_type * @length: length excluding descriptor * @ext_payload: payload of the specific extended header * @hdr: place holder for actual header */ struct mei_ext_hdr { u8 type; u8 length; #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) u8 ext_payload[2]; u8 hdr[]; #else u8 data[]; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) __packed #endif ; /** * struct mei_ext_meta_hdr - extend header meta data * @count: number of headers * @size: total size of the extended header list excluding meta header * @reserved: reserved * @hdrs: extended headers TLV list */ struct mei_ext_meta_hdr { u8 count; u8 size; u8 reserved[2]; #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) struct mei_ext_hdr hdrs[]; #else u8 hdrs[]; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) __packed #endif ; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) /** * struct mei_ext_hdr_vtag - extend header for vtag * * @hdr: standard extend header * @vtag: virtual tag * @reserved: reserved */ struct mei_ext_hdr_vtag { struct mei_ext_hdr hdr; u8 vtag; u8 reserved; } __packed; #endif /* * Extended header iterator functions */ /** * mei_ext_hdr - extended header iterator begin * * @meta: meta header of the extended header list * * Return: * The first extended header */ static inline struct mei_ext_hdr *mei_ext_begin(struct mei_ext_meta_hdr *meta) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) return meta->hdrs; #else return (struct mei_ext_hdr *)meta->hdrs; #endif } /** * mei_ext_last - check if the ext is the last one in the TLV list * * @meta: meta header of the extended header list * @ext: a meta header on the list * * Return: true if ext is the last header on the list */ static inline bool mei_ext_last(struct mei_ext_meta_hdr *meta, struct mei_ext_hdr *ext) { return (u8 *)ext >= (u8 *)meta + sizeof(*meta) + (meta->size * 4); } #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0) struct mei_gsc_sgl { u32 low; u32 high; u32 length; } __packed; #define GSC_HECI_MSG_KERNEL 0 #define GSC_HECI_MSG_USER 1 #define GSC_ADDRESS_TYPE_GTT 0 #define GSC_ADDRESS_TYPE_PPGTT 1 #define GSC_ADDRESS_TYPE_PHYSICAL_CONTINUOUS 2 /* max of 64K */ #define GSC_ADDRESS_TYPE_PHYSICAL_SGL 3 /** * struct mei_ext_hdr_gsc_h2f - extended header: gsc host to firmware interface * * @hdr: extended header * @client_id: GSC_HECI_MSG_KERNEL or GSC_HECI_MSG_USER * @addr_type: GSC_ADDRESS_TYPE_{GTT, PPGTT, PHYSICAL_CONTINUOUS, PHYSICAL_SGL} * @fence_id: synchronization marker * @input_address_count: number of input sgl buffers * @output_address_count: number of output sgl buffers * @reserved: reserved * @sgl: sg list */ struct mei_ext_hdr_gsc_h2f { struct mei_ext_hdr hdr; u8 client_id; u8 addr_type; u32 fence_id; u8 input_address_count; u8 output_address_count; u8 reserved[2]; struct mei_gsc_sgl sgl[]; } __packed; /** * struct mei_ext_hdr_gsc_f2h - gsc firmware to host interface * * @hdr: extended header * @client_id: GSC_HECI_MSG_KERNEL or GSC_HECI_MSG_USER * @reserved: reserved * @fence_id: synchronization marker * @written: number of bytes written to firmware */ struct mei_ext_hdr_gsc_f2h { struct mei_ext_hdr hdr; u8 client_id; u8 reserved; u32 fence_id; u32 written; } __packed; #endif /** * mei_ext_next - following extended header on the TLV list * * @ext: current extend header * * Context: The function does not check for the overflows, * one should call mei_ext_last before. * * Return: The following extend header after @ext */ static inline struct mei_ext_hdr *mei_ext_next(struct mei_ext_hdr *ext) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) return (struct mei_ext_hdr *)(ext->hdr + (ext->length * 4)); #else return (struct mei_ext_hdr *)((u8 *)ext + (ext->length * 4)); #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0) /** * mei_ext_hdr_len - get ext header length in bytes * * @ext: extend header * * Return: extend header length in bytes */ static inline u32 mei_ext_hdr_len(const struct mei_ext_hdr *ext) { if (!ext) return 0; return ext->length * sizeof(u32); } #endif /** * struct mei_msg_hdr - MEI BUS Interface Section * * @me_addr: device address * @host_addr: host address * @length: message length * @reserved: reserved * @extended: message has extended header * @dma_ring: message is on dma ring * @internal: message is internal * @msg_complete: last packet of the message * @extension: extension of the header */ struct mei_msg_hdr { u32 me_addr:8; u32 host_addr:8; u32 length:9; u32 reserved:3; u32 extended:1; u32 dma_ring:1; u32 internal:1; u32 msg_complete:1; u32 extension[]; } __packed; /* The length is up to 9 bits */ #define MEI_MSG_MAX_LEN_MASK GENMASK(9, 0) struct mei_bus_message { u8 hbm_cmd; u8 data[]; } __packed; /** * struct hbm_cl_cmd - client specific host bus command * CONNECT, DISCONNECT, and FlOW CONTROL * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: address of the client in the driver * @data: generic data */ struct mei_hbm_cl_cmd { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 data; }; struct hbm_version { u8 minor_version; u8 major_version; } __packed; struct hbm_host_version_request { u8 hbm_cmd; u8 reserved; struct hbm_version host_version; } __packed; struct hbm_host_version_response { u8 hbm_cmd; u8 host_version_supported; struct hbm_version me_max_version; } __packed; struct hbm_host_stop_request { u8 hbm_cmd; u8 reason; u8 reserved[2]; } __packed; struct hbm_host_stop_response { u8 hbm_cmd; u8 reserved[3]; } __packed; struct hbm_me_stop_request { u8 hbm_cmd; u8 reason; u8 reserved[2]; } __packed; /** * enum hbm_host_enum_flags - enumeration request flags (HBM version >= 2.0) * * @MEI_HBM_ENUM_F_ALLOW_ADD: allow dynamic clients add * @MEI_HBM_ENUM_F_IMMEDIATE_ENUM: allow FW to send answer immediately */ enum hbm_host_enum_flags { MEI_HBM_ENUM_F_ALLOW_ADD = BIT(0), MEI_HBM_ENUM_F_IMMEDIATE_ENUM = BIT(1), }; /** * struct hbm_host_enum_request - enumeration request from host to fw * * @hbm_cmd : bus message command header * @flags : request flags * @reserved: reserved */ struct hbm_host_enum_request { u8 hbm_cmd; u8 flags; u8 reserved[2]; } __packed; struct hbm_host_enum_response { u8 hbm_cmd; u8 reserved[3]; u8 valid_addresses[32]; } __packed; /** * struct mei_client_properties - mei client properties * * @protocol_name: guid of the client * @protocol_version: client protocol version * @max_number_of_connections: number of possible connections. * @fixed_address: fixed me address (0 if the client is dynamic) * @single_recv_buf: 1 if all connections share a single receive buffer. * @vt_supported: the client support vtag * @reserved: reserved * @max_msg_length: MTU of the client */ struct mei_client_properties { uuid_le protocol_name; u8 protocol_version; u8 max_number_of_connections; u8 fixed_address; u8 single_recv_buf:1; u8 vt_supported:1; u8 reserved:6; u32 max_msg_length; } __packed; struct hbm_props_request { u8 hbm_cmd; u8 me_addr; u8 reserved[2]; } __packed; struct hbm_props_response { u8 hbm_cmd; u8 me_addr; u8 status; u8 reserved; struct mei_client_properties client_properties; } __packed; /** * struct hbm_add_client_request - request to add a client * might be sent by fw after enumeration has already completed * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @reserved: reserved * @client_properties: client properties */ struct hbm_add_client_request { u8 hbm_cmd; u8 me_addr; u8 reserved[2]; struct mei_client_properties client_properties; } __packed; /** * struct hbm_add_client_response - response to add a client * sent by the host to report client addition status to fw * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @status: if HBMS_SUCCESS then the client can now accept connections. * @reserved: reserved */ struct hbm_add_client_response { u8 hbm_cmd; u8 me_addr; u8 status; u8 reserved; } __packed; /** * struct hbm_power_gate - power gate request/response * * @hbm_cmd: bus message command header * @reserved: reserved */ struct hbm_power_gate { u8 hbm_cmd; u8 reserved[3]; } __packed; /** * struct hbm_client_connect_request - connect/disconnect request * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: address of the client in the driver * @reserved: reserved */ struct hbm_client_connect_request { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 reserved; } __packed; /** * struct hbm_client_connect_response - connect/disconnect response * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: address of the client in the driver * @status: status of the request */ struct hbm_client_connect_response { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 status; } __packed; #define MEI_FC_MESSAGE_RESERVED_LENGTH 5 struct hbm_flow_control { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH]; } __packed; #define MEI_HBM_NOTIFICATION_START 1 #define MEI_HBM_NOTIFICATION_STOP 0 /** * struct hbm_notification_request - start/stop notification request * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: address of the client in the driver * @start: start = 1 or stop = 0 asynchronous notifications */ struct hbm_notification_request { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 start; } __packed; /** * struct hbm_notification_response - start/stop notification response * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: - address of the client in the driver * @status: (mei_hbm_status) response status for the request * - MEI_HBMS_SUCCESS: successful stop/start * - MEI_HBMS_CLIENT_NOT_FOUND: if the connection could not be found. * - MEI_HBMS_ALREADY_STARTED: for start requests for a previously * started notification. * - MEI_HBMS_NOT_STARTED: for stop request for a connected client for whom * asynchronous notifications are currently disabled. * * @start: start = 1 or stop = 0 asynchronous notifications * @reserved: reserved */ struct hbm_notification_response { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 status; u8 start; u8 reserved[3]; } __packed; /** * struct hbm_notification - notification event * * @hbm_cmd: bus message command header * @me_addr: address of the client in ME * @host_addr: address of the client in the driver * @reserved: reserved for alignment */ struct hbm_notification { u8 hbm_cmd; u8 me_addr; u8 host_addr; u8 reserved; } __packed; /** * struct hbm_dma_mem_dscr - dma ring * * @addr_hi: the high 32bits of 64 bit address * @addr_lo: the low 32bits of 64 bit address * @size : size in bytes (must be power of 2) */ struct hbm_dma_mem_dscr { u32 addr_hi; u32 addr_lo; u32 size; } __packed; enum { DMA_DSCR_HOST = 0, DMA_DSCR_DEVICE = 1, DMA_DSCR_CTRL = 2, DMA_DSCR_NUM, }; /** * struct hbm_dma_setup_request - dma setup request * * @hbm_cmd: bus message command header * @reserved: reserved for alignment * @dma_dscr: dma descriptor for HOST, DEVICE, and CTRL */ struct hbm_dma_setup_request { u8 hbm_cmd; u8 reserved[3]; struct hbm_dma_mem_dscr dma_dscr[DMA_DSCR_NUM]; } __packed; /** * struct hbm_dma_setup_response - dma setup response * * @hbm_cmd: bus message command header * @status: 0 on success; otherwise DMA setup failed. * @reserved: reserved for alignment */ struct hbm_dma_setup_response { u8 hbm_cmd; u8 status; u8 reserved[2]; } __packed; /** * struct mei_dma_ring_ctrl - dma ring control block * * @hbuf_wr_idx: host circular buffer write index in slots * @reserved1: reserved for alignment * @hbuf_rd_idx: host circular buffer read index in slots * @reserved2: reserved for alignment * @dbuf_wr_idx: device circular buffer write index in slots * @reserved3: reserved for alignment * @dbuf_rd_idx: device circular buffer read index in slots * @reserved4: reserved for alignment */ struct hbm_dma_ring_ctrl { u32 hbuf_wr_idx; u32 reserved1; u32 hbuf_rd_idx; u32 reserved2; u32 dbuf_wr_idx; u32 reserved3; u32 dbuf_rd_idx; u32 reserved4; } __packed; /* virtual tag supported */ #define HBM_CAP_VT BIT(0) /* gsc extended header support */ #define HBM_CAP_GSC BIT(1) /* client dma supported */ #define HBM_CAP_CD BIT(2) /** * struct hbm_capability_request - capability request from host to fw * * @hbm_cmd : bus message command header * @capability_requested: bitmask of capabilities requested by host */ struct hbm_capability_request { u8 hbm_cmd; u8 capability_requested[3]; } __packed; /** * struct hbm_capability_response - capability response from fw to host * * @hbm_cmd : bus message command header * @capability_granted: bitmask of capabilities granted by FW */ struct hbm_capability_response { u8 hbm_cmd; u8 capability_granted[3]; } __packed; /** * struct hbm_client_dma_map_request - client dma map request from host to fw * * @hbm_cmd: bus message command header * @client_buffer_id: client buffer id * @reserved: reserved * @address_lsb: DMA address LSB * @address_msb: DMA address MSB * @size: DMA size */ struct hbm_client_dma_map_request { u8 hbm_cmd; u8 client_buffer_id; u8 reserved[2]; u32 address_lsb; u32 address_msb; u32 size; } __packed; /** * struct hbm_client_dma_unmap_request * client dma unmap request from the host to the firmware * * @hbm_cmd: bus message command header * @status: unmap status * @client_buffer_id: client buffer id * @reserved: reserved */ struct hbm_client_dma_unmap_request { u8 hbm_cmd; u8 status; u8 client_buffer_id; u8 reserved; } __packed; /** * struct hbm_client_dma_response * client dma unmap response from the firmware to the host * * @hbm_cmd: bus message command header * @status: command status */ struct hbm_client_dma_response { u8 hbm_cmd; u8 status; } __packed; #endif ivsc-driver-0~git202311021215.73a044d9/backport-include/drivers/misc/mei/mei_dev.h000066400000000000000000000566541452306460500267730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_DEV_H_ #define _MEI_DEV_H_ #include #include #include #include #include #include #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 3, 0) static inline int uuid_le_cmp(const uuid_le u1, const uuid_le u2) { return memcmp(&u1, &u2, sizeof(uuid_le)); } #endif #include "hw.h" #include "hbm.h" #define MEI_SLOT_SIZE sizeof(u32) #define MEI_RD_MSG_BUF_SIZE (128 * MEI_SLOT_SIZE) /* * Number of Maximum MEI Clients */ #define MEI_CLIENTS_MAX 256 /* * maximum number of consecutive resets */ #define MEI_MAX_CONSEC_RESET 3 /* * Number of File descriptors/handles * that can be opened to the driver. * * Limit to 255: 256 Total Clients * minus internal client for MEI Bus Messages */ #define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1) /* File state */ enum file_state { MEI_FILE_UNINITIALIZED = 0, MEI_FILE_INITIALIZING, MEI_FILE_CONNECTING, MEI_FILE_CONNECTED, MEI_FILE_DISCONNECTING, MEI_FILE_DISCONNECT_REPLY, MEI_FILE_DISCONNECT_REQUIRED, MEI_FILE_DISCONNECTED, }; /* MEI device states */ enum mei_dev_state { MEI_DEV_INITIALIZING = 0, MEI_DEV_INIT_CLIENTS, MEI_DEV_ENABLED, MEI_DEV_RESETTING, MEI_DEV_DISABLED, MEI_DEV_POWERING_DOWN, MEI_DEV_POWER_DOWN, MEI_DEV_POWER_UP }; /** * enum mei_dev_pxp_mode - MEI PXP mode state * * @MEI_DEV_PXP_DEFAULT: PCH based device, no initailization required * @MEI_DEV_PXP_INIT: device requires initialization, send setup message to firmware * @MEI_DEV_PXP_SETUP: device is in setup stage, waiting for firmware repsonse * @MEI_DEV_PXP_READY: device initialized */ enum mei_dev_pxp_mode { MEI_DEV_PXP_DEFAULT = 0, MEI_DEV_PXP_INIT = 1, MEI_DEV_PXP_SETUP = 2, MEI_DEV_PXP_READY = 3, }; const char *mei_dev_state_str(int state); enum mei_file_transaction_states { MEI_IDLE, MEI_WRITING, MEI_WRITE_COMPLETE, }; /** * enum mei_cb_file_ops - file operation associated with the callback * @MEI_FOP_READ: read * @MEI_FOP_WRITE: write * @MEI_FOP_CONNECT: connect * @MEI_FOP_DISCONNECT: disconnect * @MEI_FOP_DISCONNECT_RSP: disconnect response * @MEI_FOP_NOTIFY_START: start notification * @MEI_FOP_NOTIFY_STOP: stop notification * @MEI_FOP_DMA_MAP: request client dma map * @MEI_FOP_DMA_UNMAP: request client dma unmap */ enum mei_cb_file_ops { MEI_FOP_READ = 0, MEI_FOP_WRITE, MEI_FOP_CONNECT, MEI_FOP_DISCONNECT, MEI_FOP_DISCONNECT_RSP, MEI_FOP_NOTIFY_START, MEI_FOP_NOTIFY_STOP, MEI_FOP_DMA_MAP, MEI_FOP_DMA_UNMAP, }; /** * enum mei_cl_io_mode - io mode between driver and fw * * @MEI_CL_IO_TX_BLOCKING: send is blocking * @MEI_CL_IO_TX_INTERNAL: internal communication between driver and FW * * @MEI_CL_IO_RX_NONBLOCK: recv is non-blocking */ enum mei_cl_io_mode { MEI_CL_IO_TX_BLOCKING = BIT(0), MEI_CL_IO_TX_INTERNAL = BIT(1), MEI_CL_IO_RX_NONBLOCK = BIT(2), MEI_CL_IO_SGL = BIT(3), }; /* * Intel MEI message data struct */ struct mei_msg_data { size_t size; unsigned char *data; }; struct mei_dma_data { u8 buffer_id; void *vaddr; dma_addr_t daddr; size_t size; }; /** * struct mei_dma_dscr - dma address descriptor * * @vaddr: dma buffer virtual address * @daddr: dma buffer physical address * @size : dma buffer size */ struct mei_dma_dscr { void *vaddr; dma_addr_t daddr; size_t size; }; /* Maximum number of processed FW status registers */ #define MEI_FW_STATUS_MAX 6 /* Minimal buffer for FW status string (8 bytes in dw + space or '\0') */ #define MEI_FW_STATUS_STR_SZ (MEI_FW_STATUS_MAX * (8 + 1)) /* * struct mei_fw_status - storage of FW status data * * @count: number of actually available elements in array * @status: FW status registers */ struct mei_fw_status { int count; u32 status[MEI_FW_STATUS_MAX]; }; /** * struct mei_me_client - representation of me (fw) client * * @list: link in me client list * @refcnt: struct reference count * @props: client properties * @client_id: me client id * @tx_flow_ctrl_creds: flow control credits * @connect_count: number connections to this client * @bus_added: added to bus */ struct mei_me_client { struct list_head list; struct kref refcnt; struct mei_client_properties props; u8 client_id; u8 tx_flow_ctrl_creds; u8 connect_count; u8 bus_added; }; struct mei_cl; /** * struct mei_cl_cb - file operation callback structure * * @list: link in callback queue * @cl: file client who is running this operation * @fop_type: file operation type * @buf: buffer for data associated with the callback * @buf_idx: last read index * @vtag: virtual tag * @fp: pointer to file structure * @status: io status of the cb * @internal: communication between driver and FW flag * @blocking: transmission blocking mode */ struct mei_cl_cb { struct list_head list; struct mei_cl *cl; enum mei_cb_file_ops fop_type; struct mei_msg_data buf; size_t buf_idx; u8 vtag; const struct file *fp; int status; u32 internal:1; u32 blocking:1; #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0) struct mei_ext_hdr *ext_hdr; #endif }; /** * struct mei_cl_vtag - file pointer to vtag mapping structure * * @list: link in map queue * @fp: file pointer * @vtag: corresponding vtag * @pending_read: the read is pending on this file */ struct mei_cl_vtag { struct list_head list; const struct file *fp; u8 vtag; u8 pending_read:1; }; /** * struct mei_cl - me client host representation * carried in file->private_data * * @link: link in the clients list * @dev: mei parent device * @state: file operation state * @tx_wait: wait queue for tx completion * @rx_wait: wait queue for rx completion * @wait: wait queue for management operation * @ev_wait: notification wait queue * @ev_async: event async notification * @status: connection status * @me_cl: fw client connected * @fp: file associated with client * @host_client_id: host id * @vtag_map: vtag map * @tx_flow_ctrl_creds: transmit flow credentials * @rx_flow_ctrl_creds: receive flow credentials * @timer_count: watchdog timer for operation completion * @notify_en: notification - enabled/disabled * @notify_ev: pending notification event * @tx_cb_queued: number of tx callbacks in queue * @writing_state: state of the tx * @rd_pending: pending read credits * @rd_completed_lock: protects rd_completed queue * @rd_completed: completed read * @dma: dma settings * @dma_mapped: dma buffer is currently mapped. * * @cldev: device on the mei client bus */ struct mei_cl { struct list_head link; struct mei_device *dev; enum file_state state; wait_queue_head_t tx_wait; wait_queue_head_t rx_wait; wait_queue_head_t wait; wait_queue_head_t ev_wait; struct fasync_struct *ev_async; int status; struct mei_me_client *me_cl; const struct file *fp; u8 host_client_id; struct list_head vtag_map; u8 tx_flow_ctrl_creds; u8 rx_flow_ctrl_creds; u8 timer_count; u8 notify_en; u8 notify_ev; u8 tx_cb_queued; enum mei_file_transaction_states writing_state; struct list_head rd_pending; spinlock_t rd_completed_lock; /* protects rd_completed queue */ struct list_head rd_completed; struct mei_dma_data dma; u8 dma_mapped; struct mei_cl_device *cldev; }; #define MEI_TX_QUEUE_LIMIT_DEFAULT 50 #define MEI_TX_QUEUE_LIMIT_MAX 255 #define MEI_TX_QUEUE_LIMIT_MIN 30 /** * struct mei_hw_ops - hw specific ops * * @host_is_ready : query for host readiness * * @hw_is_ready : query if hw is ready * @hw_reset : reset hw * @hw_start : start hw after reset * @hw_config : configure hw * * @fw_status : get fw status registers * @trc_status : get trc status register * @pg_state : power gating state of the device * @pg_in_transition : is device now in pg transition * @pg_is_enabled : is power gating enabled * * @intr_clear : clear pending interrupts * @intr_enable : enable interrupts * @intr_disable : disable interrupts * @synchronize_irq : synchronize irqs * * @hbuf_free_slots : query for write buffer empty slots * @hbuf_is_ready : query if write buffer is empty * @hbuf_depth : query for write buffer depth * * @write : write a message to FW * * @rdbuf_full_slots : query how many slots are filled * * @read_hdr : get first 4 bytes (header) * @read : read a buffer from the FW */ struct mei_hw_ops { bool (*host_is_ready)(struct mei_device *dev); bool (*hw_is_ready)(struct mei_device *dev); int (*hw_reset)(struct mei_device *dev, bool enable); int (*hw_start)(struct mei_device *dev); int (*hw_config)(struct mei_device *dev); int (*fw_status)(struct mei_device *dev, struct mei_fw_status *fw_sts); int (*trc_status)(struct mei_device *dev, u32 *trc); enum mei_pg_state (*pg_state)(struct mei_device *dev); bool (*pg_in_transition)(struct mei_device *dev); bool (*pg_is_enabled)(struct mei_device *dev); void (*intr_clear)(struct mei_device *dev); void (*intr_enable)(struct mei_device *dev); void (*intr_disable)(struct mei_device *dev); void (*synchronize_irq)(struct mei_device *dev); int (*hbuf_free_slots)(struct mei_device *dev); bool (*hbuf_is_ready)(struct mei_device *dev); u32 (*hbuf_depth)(const struct mei_device *dev); int (*write)(struct mei_device *dev, const void *hdr, size_t hdr_len, const void *data, size_t data_len); int (*rdbuf_full_slots)(struct mei_device *dev); u32 (*read_hdr)(const struct mei_device *dev); int (*read)(struct mei_device *dev, unsigned char *buf, unsigned long len); }; /* MEI bus API*/ void mei_cl_bus_rescan_work(struct work_struct *work); void mei_cl_bus_dev_fixup(struct mei_cl_device *dev); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, u8 vtag, unsigned int mode); #else ssize_t __mei_cl_send(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, unsigned int mode); #endif #if LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0) ssize_t __mei_cl_send_timeout(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, unsigned int mode, unsigned long timeout); #endif ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, u8 *vtag, unsigned int mode, unsigned long timeout); bool mei_cl_bus_rx_event(struct mei_cl *cl); bool mei_cl_bus_notify_event(struct mei_cl *cl); void mei_cl_bus_remove_devices(struct mei_device *bus); int mei_cl_bus_init(void); void mei_cl_bus_exit(void); /** * enum mei_pg_event - power gating transition events * * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete * @MEI_PG_EVENT_RECEIVED: the driver received pg event * @MEI_PG_EVENT_INTR_WAIT: the driver is waiting for a pg event interrupt * @MEI_PG_EVENT_INTR_RECEIVED: the driver received pg event interrupt */ enum mei_pg_event { MEI_PG_EVENT_IDLE, MEI_PG_EVENT_WAIT, MEI_PG_EVENT_RECEIVED, MEI_PG_EVENT_INTR_WAIT, MEI_PG_EVENT_INTR_RECEIVED, }; /** * enum mei_pg_state - device internal power gating state * * @MEI_PG_OFF: device is not power gated - it is active * @MEI_PG_ON: device is power gated - it is in lower power state */ enum mei_pg_state { MEI_PG_OFF = 0, MEI_PG_ON = 1, }; const char *mei_pg_state_str(enum mei_pg_state state); /** * struct mei_fw_version - MEI FW version struct * * @platform: platform identifier * @major: major version field * @minor: minor version field * @buildno: build number version field * @hotfix: hotfix number version field */ struct mei_fw_version { u8 platform; u8 major; u16 minor; u16 buildno; u16 hotfix; }; #define MEI_MAX_FW_VER_BLOCKS 3 struct mei_dev_timeouts { unsigned long hw_ready; /* Timeout on ready message, in jiffies */ int connect; /* HPS: at least 2 seconds, in seconds */ unsigned long cl_connect; /* HPS: Client Connect Timeout, in jiffies */ int client_init; /* HPS: Clients Enumeration Timeout, in seconds */ unsigned long pgi; /* PG Isolation time response, in jiffies */ unsigned int d0i3; /* D0i3 set/unset max response time, in jiffies */ unsigned long hbm; /* HBM operation timeout, in jiffies */ unsigned long mkhi_recv; /* receive timeout, in jiffies */ }; /** * struct mei_device - MEI private device struct * * @dev : device on a bus * @cdev : character device * @minor : minor number allocated for device * * @write_list : write pending list * @write_waiting_list : write completion list * @ctrl_wr_list : pending control write list * @ctrl_rd_list : pending control read list * @tx_queue_limit: tx queues per client linit * * @file_list : list of opened handles * @open_handle_count: number of opened handles * * @device_lock : big device lock * @timer_work : MEI timer delayed work (timeouts) * * @recvd_hw_ready : hw ready message received flag * * @wait_hw_ready : wait queue for receive HW ready message form FW * @wait_pg : wait queue for receive PG message from FW * @wait_hbm_start : wait queue for receive HBM start message from FW * * @reset_count : number of consecutive resets * @dev_state : device state * @hbm_state : state of host bus message protocol #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) * @pxp_mode : PXP device mode #endif * @init_clients_timer : HBM init handshake timeout * * @pg_event : power gating event * @pg_domain : runtime PM domain * * @rd_msg_buf : control messages buffer * @rd_msg_hdr : read message header storage * @rd_msg_hdr_count : how many dwords were already read from header * * @hbuf_is_ready : query if the host host/write buffer is ready * @dr_dscr: DMA ring descriptors: TX, RX, and CTRL * * @version : HBM protocol version in use * @hbm_f_pg_supported : hbm feature pgi protocol * @hbm_f_dc_supported : hbm feature dynamic clients * @hbm_f_dot_supported : hbm feature disconnect on timeout * @hbm_f_ev_supported : hbm feature event notification * @hbm_f_fa_supported : hbm feature fixed address client * @hbm_f_ie_supported : hbm feature immediate reply to enum request * @hbm_f_os_supported : hbm feature support OS ver message * @hbm_f_dr_supported : hbm feature dma ring supported * @hbm_f_vt_supported : hbm feature vtag supported * @hbm_f_cap_supported : hbm feature capabilities message supported * @hbm_f_cd_supported : hbm feature client dma supported * * @fw_ver : FW versions * * @fw_f_fw_ver_supported : fw feature: fw version supported * * @me_clients_rwsem: rw lock over me_clients list * @me_clients : list of FW clients * @me_clients_map : FW clients bit map * @host_clients_map : host clients id pool * * @allow_fixed_address: allow user space to connect a fixed client * @override_fixed_address: force allow fixed address behavior * #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) * @timeouts: actual timeout values #endif * * @reset_work : work item for the device reset * @bus_rescan_work : work item for the bus rescan * * @device_list : mei client bus list * @cl_bus_lock : client bus list lock * * @kind : kind of mei device * * @dbgfs_dir : debugfs mei root directory * * @ops: : hw specific operations * @hw : hw specific data */ struct mei_device { struct device *dev; struct cdev cdev; int minor; struct list_head write_list; struct list_head write_waiting_list; struct list_head ctrl_wr_list; struct list_head ctrl_rd_list; u8 tx_queue_limit; struct list_head file_list; long open_handle_count; struct mutex device_lock; struct delayed_work timer_work; bool recvd_hw_ready; /* * waiting queue for receive message from FW */ wait_queue_head_t wait_hw_ready; wait_queue_head_t wait_pg; wait_queue_head_t wait_hbm_start; /* * mei device states */ unsigned long reset_count; enum mei_dev_state dev_state; enum mei_hbm_state hbm_state; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) enum mei_dev_pxp_mode pxp_mode; #endif u16 init_clients_timer; /* * Power Gating support */ enum mei_pg_event pg_event; #ifdef CONFIG_PM struct dev_pm_domain pg_domain; #endif /* CONFIG_PM */ unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; u32 rd_msg_hdr[MEI_RD_MSG_BUF_SIZE]; int rd_msg_hdr_count; /* write buffer */ bool hbuf_is_ready; struct mei_dma_dscr dr_dscr[DMA_DSCR_NUM]; struct hbm_version version; unsigned int hbm_f_pg_supported:1; unsigned int hbm_f_dc_supported:1; unsigned int hbm_f_dot_supported:1; unsigned int hbm_f_ev_supported:1; unsigned int hbm_f_fa_supported:1; unsigned int hbm_f_ie_supported:1; unsigned int hbm_f_os_supported:1; unsigned int hbm_f_dr_supported:1; unsigned int hbm_f_vt_supported:1; unsigned int hbm_f_cap_supported:1; unsigned int hbm_f_cd_supported:1; unsigned int hbm_f_gsc_supported:1; struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS]; unsigned int fw_f_fw_ver_supported:1; struct rw_semaphore me_clients_rwsem; struct list_head me_clients; DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX); bool allow_fixed_address; bool override_fixed_address; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) struct mei_dev_timeouts timeouts; #endif struct work_struct reset_work; struct work_struct bus_rescan_work; /* List of bus devices */ struct list_head device_list; struct mutex cl_bus_lock; const char *kind; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ const struct mei_hw_ops *ops; char hw[] __aligned(sizeof(void *)); }; static inline unsigned long mei_secs_to_jiffies(unsigned long sec) { return msecs_to_jiffies(sec * MSEC_PER_SEC); } /** * mei_data2slots - get slots number from a message length * * @length: size of the messages in bytes * * Return: number of slots */ static inline u32 mei_data2slots(size_t length) { return DIV_ROUND_UP(length, MEI_SLOT_SIZE); } /** * mei_hbm2slots - get slots number from a hbm message length * length + size of the mei message header * * @length: size of the messages in bytes * * Return: number of slots */ static inline u32 mei_hbm2slots(size_t length) { return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, MEI_SLOT_SIZE); } /** * mei_slots2data - get data in slots - bytes from slots * * @slots: number of available slots * * Return: number of bytes in slots */ static inline u32 mei_slots2data(int slots) { return slots * MEI_SLOT_SIZE; } /* * mei init function prototypes */ void mei_device_init(struct mei_device *dev, struct device *device, #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) bool slow_fw, #endif const struct mei_hw_ops *hw_ops); int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); int mei_restart(struct mei_device *dev); void mei_stop(struct mei_device *dev); void mei_cancel_work(struct mei_device *dev); void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state); int mei_dmam_ring_alloc(struct mei_device *dev); void mei_dmam_ring_free(struct mei_device *dev); bool mei_dma_ring_is_allocated(struct mei_device *dev); void mei_dma_ring_reset(struct mei_device *dev); void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len); void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len); u32 mei_dma_ring_empty_slots(struct mei_device *dev); /* * MEI interrupt functions prototype */ void mei_timer(struct work_struct *work); void mei_schedule_stall_timer(struct mei_device *dev); int mei_irq_read_handler(struct mei_device *dev, struct list_head *cmpl_list, s32 *slots); int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list); void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list); /* * Register Access Function */ static inline int mei_hw_config(struct mei_device *dev) { return dev->ops->hw_config(dev); } static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) { return dev->ops->pg_state(dev); } static inline bool mei_pg_in_transition(struct mei_device *dev) { return dev->ops->pg_in_transition(dev); } static inline bool mei_pg_is_enabled(struct mei_device *dev) { return dev->ops->pg_is_enabled(dev); } static inline int mei_hw_reset(struct mei_device *dev, bool enable) { return dev->ops->hw_reset(dev, enable); } static inline int mei_hw_start(struct mei_device *dev) { return dev->ops->hw_start(dev); } static inline void mei_clear_interrupts(struct mei_device *dev) { dev->ops->intr_clear(dev); } static inline void mei_enable_interrupts(struct mei_device *dev) { dev->ops->intr_enable(dev); } static inline void mei_disable_interrupts(struct mei_device *dev) { dev->ops->intr_disable(dev); } static inline void mei_synchronize_irq(struct mei_device *dev) { dev->ops->synchronize_irq(dev); } static inline bool mei_host_is_ready(struct mei_device *dev) { return dev->ops->host_is_ready(dev); } static inline bool mei_hw_is_ready(struct mei_device *dev) { return dev->ops->hw_is_ready(dev); } static inline bool mei_hbuf_is_ready(struct mei_device *dev) { return dev->ops->hbuf_is_ready(dev); } static inline int mei_hbuf_empty_slots(struct mei_device *dev) { return dev->ops->hbuf_free_slots(dev); } static inline u32 mei_hbuf_depth(const struct mei_device *dev) { return dev->ops->hbuf_depth(dev); } static inline int mei_write_message(struct mei_device *dev, const void *hdr, size_t hdr_len, const void *data, size_t data_len) { return dev->ops->write(dev, hdr, hdr_len, data, data_len); } static inline u32 mei_read_hdr(const struct mei_device *dev) { return dev->ops->read_hdr(dev); } static inline void mei_read_slots(struct mei_device *dev, unsigned char *buf, unsigned long len) { dev->ops->read(dev, buf, len); } static inline int mei_count_full_read_slots(struct mei_device *dev) { return dev->ops->rdbuf_full_slots(dev); } static inline int mei_trc_status(struct mei_device *dev, u32 *trc) { if (dev->ops->trc_status) return dev->ops->trc_status(dev, trc); return -EOPNOTSUPP; } static inline int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) { return dev->ops->fw_status(dev, fw_status); } bool mei_hbuf_acquire(struct mei_device *dev); bool mei_write_is_idle(struct mei_device *dev); #if IS_ENABLED(CONFIG_DEBUG_FS) void mei_dbgfs_register(struct mei_device *dev, const char *name); void mei_dbgfs_deregister(struct mei_device *dev); #else static inline void mei_dbgfs_register(struct mei_device *dev, const char *name) {} static inline void mei_dbgfs_deregister(struct mei_device *dev) {} #endif /* CONFIG_DEBUG_FS */ int mei_register(struct mei_device *dev, struct device *parent); void mei_deregister(struct mei_device *dev); #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d dma=%1d ext=%1d internal=%1d comp=%1d" #define MEI_HDR_PRM(hdr) \ (hdr)->host_addr, (hdr)->me_addr, \ (hdr)->length, (hdr)->dma_ring, (hdr)->extended, \ (hdr)->internal, (hdr)->msg_complete ssize_t mei_fw_status2str(struct mei_fw_status *fw_sts, char *buf, size_t len); /** * mei_fw_status_str - fetch and convert fw status registers to printable string * * @dev: the device structure * @buf: string buffer at minimal size MEI_FW_STATUS_STR_SZ * @len: buffer len must be >= MEI_FW_STATUS_STR_SZ * * Return: number of bytes written or < 0 on failure */ static inline ssize_t mei_fw_status_str(struct mei_device *dev, char *buf, size_t len) { struct mei_fw_status fw_status; int ret; buf[0] = '\0'; ret = mei_fw_status(dev, &fw_status); if (ret) return ret; ret = mei_fw_status2str(&fw_status, buf, MEI_FW_STATUS_STR_SZ); return ret; } #endif ivsc-driver-0~git202311021215.73a044d9/dkms.conf000066400000000000000000000015731452306460500203740ustar00rootroot00000000000000PACKAGE_NAME="ivsc-driver" PACKAGE_VERSION="1.0.0" MAKE="make KERNELRELEASE=$kernelver KERNEL_SRC=$kernel_source_dir" CLEAN="make KERNELRELEASE=$kernelver KERNEL_SRC=$kernel_source_dir clean" BUILT_MODULE_NAME[0]="ljca" DEST_MODULE_LOCATION[0]="/updates" BUILT_MODULE_NAME[1]="spi-ljca" DEST_MODULE_LOCATION[1]="/updates" BUILT_MODULE_NAME[2]="gpio-ljca" DEST_MODULE_LOCATION[2]="/updates" BUILT_MODULE_NAME[3]="i2c-ljca" DEST_MODULE_LOCATION[3]="/updates" BUILT_MODULE_NAME[4]="mei-vsc" DEST_MODULE_LOCATION[4]="/updates" BUILT_MODULE_NAME[5]="intel_vsc" DEST_MODULE_LOCATION[5]="/updates" BUILT_MODULE_NAME[6]="mei_csi" DEST_MODULE_LOCATION[6]="/updates" BUILT_MODULE_NAME[7]="mei_ace" DEST_MODULE_LOCATION[7]="/updates" BUILT_MODULE_NAME[8]="mei_pse" DEST_MODULE_LOCATION[8]="/updates" BUILT_MODULE_NAME[9]="mei_ace_debug" DEST_MODULE_LOCATION[9]="/updates" AUTOINSTALL="yes" ivsc-driver-0~git202311021215.73a044d9/drivers/000077500000000000000000000000001452306460500202375ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/gpio/000077500000000000000000000000001452306460500211755ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/gpio/gpio-ljca.c000066400000000000000000000305561452306460500232170ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-only /* * Intel La Jolla Cove Adapter USB-GPIO driver * * Copyright (c) 2021, Intel Corporation. */ #include #include #include #include #include #include #include #include #include #include #include #define GPIO_PAYLOAD_LEN(pin_num) \ (sizeof(struct gpio_packet) + (pin_num) * sizeof(struct gpio_op)) /* GPIO commands */ #define GPIO_CONFIG 1 #define GPIO_READ 2 #define GPIO_WRITE 3 #define GPIO_INT_EVENT 4 #define GPIO_INT_MASK 5 #define GPIO_INT_UNMASK 6 #define GPIO_CONF_DISABLE BIT(0) #define GPIO_CONF_INPUT BIT(1) #define GPIO_CONF_OUTPUT BIT(2) #define GPIO_CONF_PULLUP BIT(3) #define GPIO_CONF_PULLDOWN BIT(4) #define GPIO_CONF_DEFAULT BIT(5) #define GPIO_CONF_INTERRUPT BIT(6) #define GPIO_INT_TYPE BIT(7) #define GPIO_CONF_EDGE (1 << 7) #define GPIO_CONF_LEVEL (0 << 7) /* Intentional overlap with PULLUP / PULLDOWN */ #define GPIO_CONF_SET BIT(3) #define GPIO_CONF_CLR BIT(4) struct gpio_op { u8 index; u8 value; } __packed; struct gpio_packet { u8 num; struct gpio_op item[]; } __packed; struct ljca_gpio_dev { struct platform_device *pdev; struct gpio_chip gc; struct ljca_gpio_info *ctr_info; DECLARE_BITMAP(unmasked_irqs, MAX_GPIO_NUM); DECLARE_BITMAP(enabled_irqs, MAX_GPIO_NUM); DECLARE_BITMAP(reenable_irqs, MAX_GPIO_NUM); u8 *connect_mode; struct mutex irq_lock; struct work_struct work; struct mutex trans_lock; u8 obuf[256]; u8 ibuf[256]; }; static bool ljca_gpio_valid(struct ljca_gpio_dev *ljca_gpio, int gpio_id) { if (gpio_id >= ljca_gpio->ctr_info->num || !test_bit(gpio_id, ljca_gpio->ctr_info->valid_pin_map)) { dev_err(&ljca_gpio->pdev->dev, "invalid gpio gpio_id gpio_id:%d\n", gpio_id); return false; } return true; } static int gpio_config(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, u8 config) { struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf; int ret; if (!ljca_gpio_valid(ljca_gpio, gpio_id)) return -EINVAL; mutex_lock(&ljca_gpio->trans_lock); packet->item[0].index = gpio_id; packet->item[0].value = config | ljca_gpio->connect_mode[gpio_id]; packet->num = 1; ret = ljca_transfer(ljca_gpio->pdev, GPIO_CONFIG, packet, GPIO_PAYLOAD_LEN(packet->num), NULL, NULL); mutex_unlock(&ljca_gpio->trans_lock); return ret; } static int ljca_gpio_read(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id) { struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf; struct gpio_packet *ack_packet; int ret; int ibuf_len; if (!ljca_gpio_valid(ljca_gpio, gpio_id)) return -EINVAL; mutex_lock(&ljca_gpio->trans_lock); packet->num = 1; packet->item[0].index = gpio_id; ret = ljca_transfer(ljca_gpio->pdev, GPIO_READ, packet, GPIO_PAYLOAD_LEN(packet->num), ljca_gpio->ibuf, &ibuf_len); ack_packet = (struct gpio_packet *)ljca_gpio->ibuf; if (ret || !ibuf_len || ack_packet->num != packet->num) { dev_err(&ljca_gpio->pdev->dev, "%s failed gpio_id:%d ret %d %d", __func__, gpio_id, ret, ack_packet->num); mutex_unlock(&ljca_gpio->trans_lock); return -EIO; } mutex_unlock(&ljca_gpio->trans_lock); return (ack_packet->item[0].value > 0) ? 1 : 0; } static int ljca_gpio_write(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, int value) { struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf; int ret; mutex_lock(&ljca_gpio->trans_lock); packet->num = 1; packet->item[0].index = gpio_id; packet->item[0].value = (value & 1); ret = ljca_transfer(ljca_gpio->pdev, GPIO_WRITE, packet, GPIO_PAYLOAD_LEN(packet->num), NULL, NULL); mutex_unlock(&ljca_gpio->trans_lock); return ret; } static int ljca_gpio_get_value(struct gpio_chip *chip, unsigned int offset) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); return ljca_gpio_read(ljca_gpio, offset); } static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset, int val) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); int ret; ret = ljca_gpio_write(ljca_gpio, offset, val); if (ret) dev_err(chip->parent, "%s offset:%d val:%d set value failed %d\n", __func__, offset, val, ret); } static int ljca_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); u8 config = GPIO_CONF_INPUT | GPIO_CONF_CLR; return gpio_config(ljca_gpio, offset, config); } static int ljca_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int val) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); u8 config = GPIO_CONF_OUTPUT | GPIO_CONF_CLR; int ret; ret = gpio_config(ljca_gpio, offset, config); if (ret) return ret; ljca_gpio_set_value(chip, offset, val); return 0; } static int ljca_gpio_set_config(struct gpio_chip *chip, unsigned int offset, unsigned long config) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); if (!ljca_gpio_valid(ljca_gpio, offset)) return -EINVAL; ljca_gpio->connect_mode[offset] = 0; switch (pinconf_to_config_param(config)) { case PIN_CONFIG_BIAS_PULL_UP: ljca_gpio->connect_mode[offset] |= GPIO_CONF_PULLUP; break; case PIN_CONFIG_BIAS_PULL_DOWN: ljca_gpio->connect_mode[offset] |= GPIO_CONF_PULLDOWN; break; case PIN_CONFIG_DRIVE_PUSH_PULL: case PIN_CONFIG_PERSIST_STATE: break; default: return -ENOTSUPP; } return 0; } static int ljca_enable_irq(struct ljca_gpio_dev *ljca_gpio, int gpio_id, bool enable) { struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf; int ret; mutex_lock(&ljca_gpio->trans_lock); packet->num = 1; packet->item[0].index = gpio_id; packet->item[0].value = 0; dev_dbg(ljca_gpio->gc.parent, "%s %d", __func__, gpio_id); ret = ljca_transfer(ljca_gpio->pdev, enable == true ? GPIO_INT_UNMASK : GPIO_INT_MASK, packet, GPIO_PAYLOAD_LEN(packet->num), NULL, NULL); mutex_unlock(&ljca_gpio->trans_lock); return ret; } static void ljca_gpio_async(struct work_struct *work) { struct ljca_gpio_dev *ljca_gpio = container_of(work, struct ljca_gpio_dev, work); int gpio_id; int unmasked; for_each_set_bit (gpio_id, ljca_gpio->reenable_irqs, ljca_gpio->gc.ngpio) { clear_bit(gpio_id, ljca_gpio->reenable_irqs); unmasked = test_bit(gpio_id, ljca_gpio->unmasked_irqs); if (unmasked) ljca_enable_irq(ljca_gpio, gpio_id, true); } } void ljca_gpio_event_cb(struct platform_device *pdev, u8 cmd, const void *evt_data, int len) { const struct gpio_packet *packet = evt_data; struct ljca_gpio_dev *ljca_gpio = platform_get_drvdata(pdev); int i; int irq; if (cmd != GPIO_INT_EVENT) return; for (i = 0; i < packet->num; i++) { irq = irq_find_mapping(ljca_gpio->gc.irq.domain, packet->item[i].index); if (!irq) { dev_err(ljca_gpio->gc.parent, "gpio_id %d not mapped to IRQ\n", packet->item[i].index); return; } generic_handle_irq(irq); set_bit(packet->item[i].index, ljca_gpio->reenable_irqs); dev_dbg(ljca_gpio->gc.parent, "%s got one interrupt %d %d %d\n", __func__, i, packet->item[i].index, packet->item[i].value); } schedule_work(&ljca_gpio->work); } static void ljca_irq_unmask(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc); int gpio_id = irqd_to_hwirq(irqd); dev_dbg(ljca_gpio->gc.parent, "%s %d", __func__, gpio_id); set_bit(gpio_id, ljca_gpio->unmasked_irqs); } static void ljca_irq_mask(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc); int gpio_id = irqd_to_hwirq(irqd); dev_dbg(ljca_gpio->gc.parent, "%s %d", __func__, gpio_id); clear_bit(gpio_id, ljca_gpio->unmasked_irqs); } static int ljca_irq_set_type(struct irq_data *irqd, unsigned type) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc); int gpio_id = irqd_to_hwirq(irqd); ljca_gpio->connect_mode[gpio_id] = GPIO_CONF_INTERRUPT; switch (type) { case IRQ_TYPE_LEVEL_HIGH: ljca_gpio->connect_mode[gpio_id] |= GPIO_CONF_LEVEL | GPIO_CONF_PULLUP; break; case IRQ_TYPE_LEVEL_LOW: ljca_gpio->connect_mode[gpio_id] |= GPIO_CONF_LEVEL | GPIO_CONF_PULLDOWN; break; case IRQ_TYPE_EDGE_BOTH: break; case IRQ_TYPE_EDGE_RISING: ljca_gpio->connect_mode[gpio_id] |= GPIO_CONF_EDGE | GPIO_CONF_PULLUP; break; case IRQ_TYPE_EDGE_FALLING: ljca_gpio->connect_mode[gpio_id] |= GPIO_CONF_EDGE | GPIO_CONF_PULLDOWN; break; default: return -EINVAL; } dev_dbg(ljca_gpio->gc.parent, "%s %d %x\n", __func__, gpio_id, ljca_gpio->connect_mode[gpio_id]); return 0; } static void ljca_irq_bus_lock(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc); mutex_lock(&ljca_gpio->irq_lock); } static void ljca_irq_bus_unlock(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc); int gpio_id = irqd_to_hwirq(irqd); int enabled; int unmasked; enabled = test_bit(gpio_id, ljca_gpio->enabled_irqs); unmasked = test_bit(gpio_id, ljca_gpio->unmasked_irqs); dev_dbg(ljca_gpio->gc.parent, "%s %d %d %d\n", __func__, gpio_id, enabled, unmasked); if (enabled != unmasked) { if (unmasked) { gpio_config(ljca_gpio, gpio_id, 0); ljca_enable_irq(ljca_gpio, gpio_id, true); set_bit(gpio_id, ljca_gpio->enabled_irqs); } else { ljca_enable_irq(ljca_gpio, gpio_id, false); clear_bit(gpio_id, ljca_gpio->enabled_irqs); } } mutex_unlock(&ljca_gpio->irq_lock); } static unsigned int ljca_irq_startup(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); return gpiochip_lock_as_irq(gc, irqd_to_hwirq(irqd)); } static void ljca_irq_shutdown(struct irq_data *irqd) { struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); gpiochip_unlock_as_irq(gc, irqd_to_hwirq(irqd)); } static struct irq_chip ljca_gpio_irqchip = { .name = "ljca-irq", .irq_mask = ljca_irq_mask, .irq_unmask = ljca_irq_unmask, .irq_set_type = ljca_irq_set_type, .irq_bus_lock = ljca_irq_bus_lock, .irq_bus_sync_unlock = ljca_irq_bus_unlock, .irq_startup = ljca_irq_startup, .irq_shutdown = ljca_irq_shutdown, }; static int ljca_gpio_probe(struct platform_device *pdev) { struct ljca_gpio_dev *ljca_gpio; struct ljca_platform_data *pdata = dev_get_platdata(&pdev->dev); struct gpio_irq_chip *girq; ljca_gpio = devm_kzalloc(&pdev->dev, sizeof(*ljca_gpio), GFP_KERNEL); if (!ljca_gpio) return -ENOMEM; ljca_gpio->ctr_info = &pdata->gpio_info; ljca_gpio->connect_mode = devm_kcalloc(&pdev->dev, ljca_gpio->ctr_info->num, sizeof(*ljca_gpio->connect_mode), GFP_KERNEL); if (!ljca_gpio->connect_mode) return -ENOMEM; mutex_init(&ljca_gpio->irq_lock); mutex_init(&ljca_gpio->trans_lock); ljca_gpio->pdev = pdev; ljca_gpio->gc.direction_input = ljca_gpio_direction_input; ljca_gpio->gc.direction_output = ljca_gpio_direction_output; ljca_gpio->gc.get = ljca_gpio_get_value; ljca_gpio->gc.set = ljca_gpio_set_value; ljca_gpio->gc.set_config = ljca_gpio_set_config; ljca_gpio->gc.can_sleep = true; ljca_gpio->gc.parent = &pdev->dev; ljca_gpio->gc.base = -1; ljca_gpio->gc.ngpio = ljca_gpio->ctr_info->num; ljca_gpio->gc.label = ACPI_COMPANION(&pdev->dev) ? acpi_dev_name(ACPI_COMPANION(&pdev->dev)) : "ljca-gpio"; ljca_gpio->gc.owner = THIS_MODULE; platform_set_drvdata(pdev, ljca_gpio); ljca_register_event_cb(pdev, ljca_gpio_event_cb); girq = &ljca_gpio->gc.irq; girq->chip = &ljca_gpio_irqchip; girq->parent_handler = NULL; girq->num_parents = 0; girq->parents = NULL; girq->default_type = IRQ_TYPE_NONE; girq->handler = handle_simple_irq; INIT_WORK(&ljca_gpio->work, ljca_gpio_async); return devm_gpiochip_add_data(&pdev->dev, &ljca_gpio->gc, ljca_gpio); } static int ljca_gpio_remove(struct platform_device *pdev) { return 0; } static struct platform_driver ljca_gpio_driver = { .driver.name = "ljca-gpio", .probe = ljca_gpio_probe, .remove = ljca_gpio_remove, }; module_platform_driver(ljca_gpio_driver); MODULE_AUTHOR("Ye Xiang "); MODULE_AUTHOR("Zhang Lixu "); MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-GPIO driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ljca-gpio"); ivsc-driver-0~git202311021215.73a044d9/drivers/i2c/000077500000000000000000000000001452306460500207145ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/i2c/busses/000077500000000000000000000000001452306460500222205ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/i2c/busses/i2c-ljca.c000066400000000000000000000257431452306460500237630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-only /* * Intel La Jolla Cove Adapter USB-I2C driver * * Copyright (c) 2021, Intel Corporation. */ #include #include #include #include #include #include /* I2C commands */ enum i2c_cmd { I2C_INIT = 1, I2C_XFER, I2C_START, I2C_STOP, I2C_READ, I2C_WRITE, }; enum i2c_address_mode { I2C_ADDRESS_MODE_7BIT, I2C_ADDRESS_MODE_10BIT, }; enum xfer_type { READ_XFER_TYPE, WRITE_XFER_TYPE, }; #define DEFAULT_I2C_CONTROLLER_ID 1 #define DEFAULT_I2C_CAPACITY 0 #define DEFAULT_I2C_INTR_PIN 0 /* I2C r/w Flags */ #define I2C_SLAVE_TRANSFER_WRITE (0) #define I2C_SLAVE_TRANSFER_READ (1) /* i2c init flags */ #define I2C_INIT_FLAG_MODE_MASK (0x1 << 0) #define I2C_INIT_FLAG_MODE_POLLING (0x0 << 0) #define I2C_INIT_FLAG_MODE_INTERRUPT (0x1 << 0) #define I2C_FLAG_ADDR_16BIT (0x1 << 0) #define I2C_INIT_FLAG_FREQ_MASK (0x3 << 1) #define I2C_FLAG_FREQ_100K (0x0 << 1) #define I2C_FLAG_FREQ_400K (0x1 << 1) #define I2C_FLAG_FREQ_1M (0x2 << 1) /* I2C Transfer */ struct i2c_xfer { u8 id; u8 slave; u16 flag; /* speed, 8/16bit addr, addr increase, etc */ u16 addr; u16 len; u8 data[]; } __packed; /* I2C raw commands: Init/Start/Read/Write/Stop */ struct i2c_rw_packet { u8 id; __le16 len; u8 data[]; } __packed; #define LJCA_I2C_MAX_XFER_SIZE 256 #define LJCA_I2C_BUF_SIZE \ (LJCA_I2C_MAX_XFER_SIZE + sizeof(struct i2c_rw_packet)) struct ljca_i2c_dev { struct platform_device *pdev; struct ljca_i2c_info *ctr_info; struct i2c_adapter adap; u8 obuf[LJCA_I2C_BUF_SIZE]; u8 ibuf[LJCA_I2C_BUF_SIZE]; }; static u8 ljca_i2c_format_slave_addr(u8 slave_addr, enum i2c_address_mode mode) { if (mode == I2C_ADDRESS_MODE_7BIT) return slave_addr << 1; return 0xFF; } static int ljca_i2c_init(struct ljca_i2c_dev *ljca_i2c, u8 id) { struct i2c_rw_packet *w_packet = (struct i2c_rw_packet *)ljca_i2c->obuf; memset(w_packet, 0, sizeof(*w_packet)); w_packet->id = id; w_packet->len = cpu_to_le16(1); w_packet->data[0] = I2C_FLAG_FREQ_400K; return ljca_transfer(ljca_i2c->pdev, I2C_INIT, w_packet, sizeof(*w_packet) + 1, NULL, NULL); } static int ljca_i2c_start(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr, enum xfer_type type) { struct i2c_rw_packet *w_packet = (struct i2c_rw_packet *)ljca_i2c->obuf; struct i2c_rw_packet *r_packet = (struct i2c_rw_packet *)ljca_i2c->ibuf; int ret; int ibuf_len; memset(w_packet, 0, sizeof(*w_packet)); w_packet->id = ljca_i2c->ctr_info->id; w_packet->len = cpu_to_le16(1); w_packet->data[0] = ljca_i2c_format_slave_addr(slave_addr, I2C_ADDRESS_MODE_7BIT); w_packet->data[0] |= (type == READ_XFER_TYPE) ? I2C_SLAVE_TRANSFER_READ : I2C_SLAVE_TRANSFER_WRITE; ret = ljca_transfer(ljca_i2c->pdev, I2C_START, w_packet, sizeof(*w_packet) + 1, r_packet, &ibuf_len); if (ret || ibuf_len < sizeof(*r_packet)) return -EIO; if ((s16)le16_to_cpu(r_packet->len) < 0 || r_packet->id != w_packet->id) { dev_err(&ljca_i2c->adap.dev, "i2c start failed len:%d id:%d %d\n", (s16)le16_to_cpu(r_packet->len), r_packet->id, w_packet->id); return -EIO; } return 0; } static int ljca_i2c_stop(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr) { struct i2c_rw_packet *w_packet = (struct i2c_rw_packet *)ljca_i2c->obuf; struct i2c_rw_packet *r_packet = (struct i2c_rw_packet *)ljca_i2c->ibuf; int ret; int ibuf_len; memset(w_packet, 0, sizeof(*w_packet)); w_packet->id = ljca_i2c->ctr_info->id; w_packet->len = cpu_to_le16(1); w_packet->data[0] = 0; ret = ljca_transfer(ljca_i2c->pdev, I2C_STOP, w_packet, sizeof(*w_packet) + 1, r_packet, &ibuf_len); if (ret || ibuf_len < sizeof(*r_packet)) return -EIO; if ((s16)le16_to_cpu(r_packet->len) < 0 || r_packet->id != w_packet->id) { dev_err(&ljca_i2c->adap.dev, "i2c stop failed len:%d id:%d %d\n", (s16)le16_to_cpu(r_packet->len), r_packet->id, w_packet->id); return -EIO; } return 0; } static int ljca_i2c_pure_read(struct ljca_i2c_dev *ljca_i2c, u8 *data, int len) { struct i2c_rw_packet *w_packet = (struct i2c_rw_packet *)ljca_i2c->obuf; struct i2c_rw_packet *r_packet = (struct i2c_rw_packet *)ljca_i2c->ibuf; int ibuf_len; int ret; if (len > LJCA_I2C_MAX_XFER_SIZE) return -EINVAL; memset(w_packet, 0, sizeof(*w_packet)); w_packet->id = ljca_i2c->ctr_info->id; w_packet->len = cpu_to_le16(len); ret = ljca_transfer(ljca_i2c->pdev, I2C_READ, w_packet, sizeof(*w_packet) + 1, r_packet, &ibuf_len); if (ret) { dev_err(&ljca_i2c->adap.dev, "I2C_READ failed ret:%d\n", ret); return ret; } if (ibuf_len < sizeof(*r_packet)) return -EIO; if ((s16)le16_to_cpu(r_packet->len) != len || r_packet->id != w_packet->id) { dev_err(&ljca_i2c->adap.dev, "i2c raw read failed len:%d id:%d %d\n", (s16)le16_to_cpu(r_packet->len), r_packet->id, w_packet->id); return -EIO; } memcpy(data, r_packet->data, len); return 0; } static int ljca_i2c_read(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr, u8 *data, u8 len) { int ret; ret = ljca_i2c_start(ljca_i2c, slave_addr, READ_XFER_TYPE); if (ret) return ret; ret = ljca_i2c_pure_read(ljca_i2c, data, len); if (ret) { dev_err(&ljca_i2c->adap.dev, "i2c raw read failed ret:%d\n", ret); return ret; } return ljca_i2c_stop(ljca_i2c, slave_addr); } static int ljca_i2c_pure_write(struct ljca_i2c_dev *ljca_i2c, u8 *data, u8 len) { struct i2c_rw_packet *w_packet = (struct i2c_rw_packet *)ljca_i2c->obuf; struct i2c_rw_packet *r_packet = (struct i2c_rw_packet *)ljca_i2c->ibuf; int ret; int ibuf_len; if (len > LJCA_I2C_MAX_XFER_SIZE) return -EINVAL; memset(w_packet, 0, sizeof(*w_packet)); w_packet->id = ljca_i2c->ctr_info->id; w_packet->len = cpu_to_le16(len); memcpy(w_packet->data, data, len); ret = ljca_transfer(ljca_i2c->pdev, I2C_WRITE, w_packet, sizeof(*w_packet) + w_packet->len, r_packet, &ibuf_len); if (ret || ibuf_len < sizeof(*r_packet)) return -EIO; if ((s16)le16_to_cpu(r_packet->len) != len || r_packet->id != w_packet->id) { dev_err(&ljca_i2c->adap.dev, "i2c write failed len:%d id:%d/%d\n", (s16)le16_to_cpu(r_packet->len), r_packet->id, w_packet->id); return -EIO; } return 0; } static int ljca_i2c_write(struct ljca_i2c_dev *ljca_i2c, u8 slave_addr, u8 *data, u8 len) { int ret; if (!data) return -EINVAL; ret = ljca_i2c_start(ljca_i2c, slave_addr, WRITE_XFER_TYPE); if (ret) return ret; ret = ljca_i2c_pure_write(ljca_i2c, data, len); if (ret) return ret; return ljca_i2c_stop(ljca_i2c, slave_addr); } static int ljca_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msg, int num) { struct ljca_i2c_dev *ljca_i2c; struct i2c_msg *cur_msg; int i, ret; ljca_i2c = i2c_get_adapdata(adapter); if (!ljca_i2c) return -EINVAL; for (i = 0; i < num; i++) { cur_msg = &msg[i]; dev_dbg(&adapter->dev, "i:%d msg:(%d %d)\n", i, cur_msg->flags, cur_msg->len); if (cur_msg->flags & I2C_M_RD) ret = ljca_i2c_read(ljca_i2c, cur_msg->addr, cur_msg->buf, cur_msg->len); else ret = ljca_i2c_write(ljca_i2c, cur_msg->addr, cur_msg->buf, cur_msg->len); if (ret) return ret; } return num; } static u32 ljca_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_adapter_quirks ljca_i2c_quirks = { .max_read_len = LJCA_I2C_MAX_XFER_SIZE, .max_write_len = LJCA_I2C_MAX_XFER_SIZE, }; static const struct i2c_algorithm ljca_i2c_algo = { .master_xfer = ljca_i2c_xfer, .functionality = ljca_i2c_func, }; struct match_ids_walk_data { struct acpi_device *adev; const char *hid1; const char *uid2; const char *uid2_v2; }; static int match_device_ids(struct acpi_device *adev, void *data) { struct match_ids_walk_data *wd = data; if (acpi_dev_hid_uid_match(adev, wd->hid1, wd->uid2) || acpi_dev_hid_uid_match(adev, wd->hid1, wd->uid2_v2)) { wd->adev = adev; return 1; } return 0; } static void try_bind_acpi(struct platform_device *pdev, struct ljca_i2c_dev *ljca_i2c) { struct acpi_device *parent; struct acpi_device *cur = ACPI_COMPANION(&pdev->dev); const char *hid1; const char *uid1; char uid2[2] = { 0 }; char uid2_v2[5] = { 0 }; struct match_ids_walk_data wd = { 0 }; if (!cur) return; hid1 = acpi_device_hid(cur); uid1 = acpi_device_uid(cur); snprintf(uid2, sizeof(uid2), "%d", ljca_i2c->ctr_info->id); snprintf(uid2_v2, sizeof(uid2_v2), "VIC%d", ljca_i2c->ctr_info->id); /* * If the pdev is bound to the right acpi device, just forward it to the * adapter. Otherwise, we find that of current adapter manually. */ if (!uid1 || !strcmp(uid1, uid2) || !strcmp(uid1, uid2_v2)) { ACPI_COMPANION_SET(&ljca_i2c->adap.dev, cur); return; } dev_info(&pdev->dev, "hid %s uid %s new uid%s\n", hid1, uid1, uid2); parent = ACPI_COMPANION(pdev->dev.parent); if (!parent) return; wd.hid1 = hid1; wd.uid2 = uid2; wd.uid2_v2 = uid2_v2; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) acpi_dev_for_each_child(parent, match_device_ids, &wd); ACPI_COMPANION_SET(&ljca_i2c->adap.dev, wd.adev); #else list_for_each_entry(wd.adev, &parent->children, node) { if (match_device_ids(wd.adev, &wd)) { ACPI_COMPANION_SET(&ljca_i2c->adap.dev, wd.adev); return; } } #endif } static int ljca_i2c_probe(struct platform_device *pdev) { struct ljca_i2c_dev *ljca_i2c; struct ljca_platform_data *pdata = dev_get_platdata(&pdev->dev); int ret; ljca_i2c = devm_kzalloc(&pdev->dev, sizeof(*ljca_i2c), GFP_KERNEL); if (!ljca_i2c) return -ENOMEM; ljca_i2c->pdev = pdev; ljca_i2c->ctr_info = &pdata->i2c_info; ljca_i2c->adap.owner = THIS_MODULE; ljca_i2c->adap.class = I2C_CLASS_HWMON; ljca_i2c->adap.algo = &ljca_i2c_algo; ljca_i2c->adap.dev.parent = &pdev->dev; try_bind_acpi(pdev, ljca_i2c); ljca_i2c->adap.dev.of_node = pdev->dev.of_node; i2c_set_adapdata(&ljca_i2c->adap, ljca_i2c); snprintf(ljca_i2c->adap.name, sizeof(ljca_i2c->adap.name), "%s-%s-%d", "ljca-i2c", dev_name(pdev->dev.parent), ljca_i2c->ctr_info->id); platform_set_drvdata(pdev, ljca_i2c); ret = ljca_i2c_init(ljca_i2c, ljca_i2c->ctr_info->id); if (ret) { dev_err(&pdev->dev, "i2c init failed id:%d\n", ljca_i2c->ctr_info->id); return -EIO; } ret = i2c_add_adapter(&ljca_i2c->adap); if (ret) return ret; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) if (has_acpi_companion(&ljca_i2c->adap.dev)) acpi_dev_clear_dependencies(ACPI_COMPANION(&ljca_i2c->adap.dev)); #endif return 0; } static int ljca_i2c_remove(struct platform_device *pdev) { struct ljca_i2c_dev *ljca_i2c = platform_get_drvdata(pdev); i2c_del_adapter(&ljca_i2c->adap); return 0; } static struct platform_driver ljca_i2c_driver = { .driver.name = "ljca-i2c", .probe = ljca_i2c_probe, .remove = ljca_i2c_remove, }; module_platform_driver(ljca_i2c_driver); MODULE_AUTHOR("Ye Xiang "); MODULE_AUTHOR("Zhang Lixu "); MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-I2C driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ljca-i2c"); ivsc-driver-0~git202311021215.73a044d9/drivers/mfd/000077500000000000000000000000001452306460500210055ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/mfd/ljca.c000066400000000000000000000701261452306460500220700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-only /* * Intel La Jolla Cove Adapter USB driver * * Copyright (c) 2021, Intel Corporation. */ #include #include #include #include #include #include #include #include #include #include #include #include enum ljca_acpi_match_adr { LJCA_ACPI_MATCH_GPIO, LJCA_ACPI_MATCH_I2C1, LJCA_ACPI_MATCH_I2C2, LJCA_ACPI_MATCH_SPI1, }; static char *gpio_hids[] = { "INTC1074", /* TGL */ "INTC1096", /* ADL */ "INTC100B", /* RPL */ "INTC10D1", /* MTL */ }; static struct mfd_cell_acpi_match ljca_acpi_match_gpio; static char *i2c_hids[] = { "INTC1075", /* TGL */ "INTC1097", /* ADL */ "INTC100C", /* RPL */ "INTC10D2", /* MTL */ }; static struct mfd_cell_acpi_match ljca_acpi_match_i2cs[2]; static char *spi_hids[] = { "INTC1091", /* TGL */ "INTC1098", /* ADL */ "INTC100D", /* RPL */ "INTC10D3", /* MTL */ }; static struct mfd_cell_acpi_match ljca_acpi_match_spis[1]; struct ljca_msg { u8 type; u8 cmd; u8 flags; u8 len; u8 data[]; } __packed; struct fw_version { u8 major; u8 minor; u16 patch; u16 build; } __packed; /* stub types */ enum stub_type { MNG_STUB = 1, DIAG_STUB, GPIO_STUB, I2C_STUB, SPI_STUB, }; /* command Flags */ #define ACK_FLAG BIT(0) #define RESP_FLAG BIT(1) #define CMPL_FLAG BIT(2) /* MNG stub commands */ enum ljca_mng_cmd { MNG_GET_VERSION = 1, MNG_RESET_NOTIFY, MNG_RESET, MNG_ENUM_GPIO, MNG_ENUM_I2C, MNG_POWER_STATE_CHANGE, MNG_SET_DFU_MODE, MNG_ENUM_SPI, }; /* DIAG commands */ enum diag_cmd { DIAG_GET_STATE = 1, DIAG_GET_STATISTIC, DIAG_SET_TRACE_LEVEL, DIAG_SET_ECHO_MODE, DIAG_GET_FW_LOG, DIAG_GET_FW_COREDUMP, DIAG_TRIGGER_WDT, DIAG_TRIGGER_FAULT, DIAG_FEED_WDT, DIAG_GET_SECURE_STATE, }; struct ljca_i2c_ctr_info { u8 id; u8 capacity; u8 intr_pin; } __packed; struct ljca_i2c_descriptor { u8 num; struct ljca_i2c_ctr_info info[]; } __packed; struct ljca_spi_ctr_info { u8 id; u8 capacity; } __packed; struct ljca_spi_descriptor { u8 num; struct ljca_spi_ctr_info info[]; } __packed; struct ljca_bank_descriptor { u8 bank_id; u8 pin_num; /* 1 bit for each gpio, 1 means valid */ u32 valid_pins; } __packed; struct ljca_gpio_descriptor { u8 pins_per_bank; u8 bank_num; struct ljca_bank_descriptor bank_desc[]; } __packed; #define MAX_PACKET_SIZE 64 #define MAX_PAYLOAD_SIZE (MAX_PACKET_SIZE - sizeof(struct ljca_msg)) #define USB_WRITE_TIMEOUT 200 #define USB_WRITE_ACK_TIMEOUT 500 #define USB_ENUM_STUB_TIMEOUT 20 struct ljca_event_cb_entry { struct platform_device *pdev; ljca_event_cb_t notify; }; struct ljca_stub_packet { u8 *ibuf; u32 ibuf_len; }; struct ljca_stub { struct list_head list; u8 type; struct usb_interface *intf; spinlock_t event_cb_lock; struct ljca_stub_packet ipacket; /* for identify ack */ bool acked; int cur_cmd; struct ljca_event_cb_entry event_entry; }; static inline void *ljca_priv(const struct ljca_stub *stub) { return (char *)stub + sizeof(struct ljca_stub); } enum ljca_state { LJCA_STOPPED, LJCA_INITED, LJCA_RESET_HANDSHAKE, LJCA_RESET_SYNCED, LJCA_ENUM_GPIO_COMPLETE, LJCA_ENUM_I2C_COMPLETE, LJCA_ENUM_SPI_COMPLETE, LJCA_SUSPEND, LJCA_STARTED, LJCA_FAILED, }; struct ljca_dev { struct usb_device *udev; struct usb_interface *intf; u8 in_ep; /* the address of the bulk in endpoint */ u8 out_ep; /* the address of the bulk out endpoint */ /* the urb/buffer for read */ struct urb *in_urb; unsigned char *ibuf; size_t ibuf_len; int state; struct list_head stubs_list; /* to wait for an ongoing write ack */ wait_queue_head_t ack_wq; struct mfd_cell *cells; int cell_count; struct mutex mutex; }; /* parent of sub-devices */ struct device *sub_dev_parent; struct device *cur_dev; static int try_match_acpi_hid(struct acpi_device *child, char **hids, int hids_num) { struct acpi_device_id ids[2] = {}; int i; for (i = 0; i < hids_num; i++) { strlcpy(ids[0].id, hids[i], sizeof(ids[0].id)); if (!acpi_match_device_ids(child, ids)) return i; } return -ENODEV; } static int match_device_ids(struct acpi_device *adev, void *data) { int ret; int *child_count = data; dev_dbg(&adev->dev, "adev->dep_unmet %d %d\n", adev->dep_unmet, *child_count); /* dependency not ready */ if (adev->dep_unmet) return -ENODEV; ret = try_match_acpi_hid(adev, gpio_hids, ARRAY_SIZE(gpio_hids)); if (ret >= 0 && ret < ARRAY_SIZE(gpio_hids)) { ljca_acpi_match_gpio.pnpid = gpio_hids[ret]; (*child_count)++; return 0; } ret = try_match_acpi_hid(adev, i2c_hids, ARRAY_SIZE(i2c_hids)); if (ret >= 0 && ret < ARRAY_SIZE(i2c_hids)) { ljca_acpi_match_i2cs[0].pnpid = i2c_hids[ret]; ljca_acpi_match_i2cs[1].pnpid = i2c_hids[ret]; (*child_count)++; return 0; } ret = try_match_acpi_hid(adev, spi_hids, ARRAY_SIZE(spi_hids)); if (ret >= 0 && ret < ARRAY_SIZE(spi_hids)) { ljca_acpi_match_spis[0].pnpid = spi_hids[ret]; (*child_count)++; return 0; } return 0; } static int precheck_acpi_hid(struct usb_interface *intf) { struct device *parents[2]; struct acpi_device *adev; int i; int child_count; #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) struct acpi_device *child; #endif parents[0] = &intf->dev; parents[1] = intf->dev.parent->parent; if (!parents[0] || !parents[1]) return -ENODEV; adev = ACPI_COMPANION(parents[0]); if (!adev) return -ENODEV; acpi_dev_clear_dependencies(adev); sub_dev_parent = parents[0]; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) for (i = 0; i < ARRAY_SIZE(parents); i++) { child_count = 0; acpi_dev_for_each_child(ACPI_COMPANION(parents[i]), match_device_ids, &child_count); if (child_count > 0) { sub_dev_parent = parents[i]; break; } } #else for (i = 0; i < ARRAY_SIZE(parents); i++) { child_count = 0; list_for_each_entry(child, &(ACPI_COMPANION(parents[i])->children), node) { match_device_ids(child, &child_count); } if (child_count > 0) { sub_dev_parent = parents[i]; break; } } #endif return 0; } static bool ljca_validate(void *data, u32 data_len) { struct ljca_msg *header = (struct ljca_msg *)data; return (header->len + sizeof(*header) == data_len); } void ljca_dump(struct ljca_dev *ljca, void *buf, int len) { int i; u8 tmp[256] = { 0 }; int n = 0; if (!len) return; for (i = 0; i < len; i++) n += scnprintf(tmp + n, sizeof(tmp) - n - 1, "%02x ", ((u8 *)buf)[i]); dev_dbg(&ljca->intf->dev, "%s\n", tmp); } static struct ljca_stub *ljca_stub_alloc(struct ljca_dev *ljca, int priv_size) { struct ljca_stub *stub; stub = kzalloc(sizeof(*stub) + priv_size, GFP_KERNEL); if (!stub) return ERR_PTR(-ENOMEM); spin_lock_init(&stub->event_cb_lock); INIT_LIST_HEAD(&stub->list); list_add_tail(&stub->list, &ljca->stubs_list); dev_dbg(&ljca->intf->dev, "enuming a stub success\n"); return stub; } static struct ljca_stub *ljca_stub_find(struct ljca_dev *ljca, u8 type) { struct ljca_stub *stub; list_for_each_entry (stub, &ljca->stubs_list, list) { if (stub->type == type) return stub; } dev_err(&ljca->intf->dev, "usb stub not find, type: %d", type); return ERR_PTR(-ENODEV); } static void ljca_stub_notify(struct ljca_stub *stub, u8 cmd, const void *evt_data, int len) { unsigned long flags; spin_lock_irqsave(&stub->event_cb_lock, flags); if (stub->event_entry.notify && stub->event_entry.pdev) stub->event_entry.notify(stub->event_entry.pdev, cmd, evt_data, len); spin_unlock_irqrestore(&stub->event_cb_lock, flags); } static int ljca_parse(struct ljca_dev *ljca, struct ljca_msg *header) { struct ljca_stub *stub; stub = ljca_stub_find(ljca, header->type); if (IS_ERR(stub)) return PTR_ERR(stub); if (!(header->flags & ACK_FLAG)) { ljca_stub_notify(stub, header->cmd, header->data, header->len); return 0; } if (stub->cur_cmd != header->cmd) { dev_err(&ljca->intf->dev, "header->cmd:%x != stub->cur_cmd:%x", header->cmd, stub->cur_cmd); return -EINVAL; } stub->ipacket.ibuf_len = header->len; if (stub->ipacket.ibuf) memcpy(stub->ipacket.ibuf, header->data, header->len); stub->acked = true; wake_up(&ljca->ack_wq); return 0; } static int ljca_stub_write(struct ljca_stub *stub, u8 cmd, const void *obuf, int obuf_len, void *ibuf, int *ibuf_len, bool wait_ack, int timeout) { struct ljca_msg *header; struct ljca_dev *ljca = usb_get_intfdata(stub->intf); int ret; u8 flags = CMPL_FLAG; int actual; if (ljca->state == LJCA_STOPPED) return -ENODEV; if (obuf_len > MAX_PAYLOAD_SIZE) return -EINVAL; if (wait_ack) flags |= ACK_FLAG; stub->ipacket.ibuf_len = 0; header = kmalloc(sizeof(*header) + obuf_len, GFP_KERNEL); if (!header) return -ENOMEM; header->type = stub->type; header->cmd = cmd; header->flags = flags; header->len = obuf_len; memcpy(header->data, obuf, obuf_len); dev_dbg(&ljca->intf->dev, "send: type:%d cmd:%d flags:%d len:%d\n", header->type, header->cmd, header->flags, header->len); ljca_dump(ljca, header->data, header->len); mutex_lock(&ljca->mutex); stub->cur_cmd = cmd; stub->ipacket.ibuf = ibuf; stub->acked = false; usb_autopm_get_interface(ljca->intf); ret = usb_bulk_msg(ljca->udev, usb_sndbulkpipe(ljca->udev, ljca->out_ep), header, sizeof(struct ljca_msg) + obuf_len, &actual, USB_WRITE_TIMEOUT); kfree(header); if (ret || actual != sizeof(struct ljca_msg) + obuf_len) { dev_err(&ljca->intf->dev, "bridge write failed ret:%d total_len:%d\n ", ret, actual); goto error; } if (wait_ack) { ret = wait_event_timeout(ljca->ack_wq, stub->acked, msecs_to_jiffies(timeout)); if (!ret || !stub->acked) { dev_err(&ljca->intf->dev, "acked sem wait timed out ret:%d timeout:%d ack:%d\n", ret, timeout, stub->acked); ret = -ETIMEDOUT; goto error; } } if (ibuf_len) *ibuf_len = stub->ipacket.ibuf_len; stub->ipacket.ibuf = NULL; stub->ipacket.ibuf_len = 0; ret = 0; error: usb_autopm_put_interface(ljca->intf); mutex_unlock(&ljca->mutex); return ret; } static int ljca_transfer_internal(struct platform_device *pdev, u8 cmd, const void *obuf, int obuf_len, void *ibuf, int *ibuf_len, bool wait_ack) { struct ljca_platform_data *ljca_pdata; struct ljca_dev *ljca; struct ljca_stub *stub; if (!pdev) return -EINVAL; ljca = dev_get_drvdata(cur_dev); ljca_pdata = dev_get_platdata(&pdev->dev); stub = ljca_stub_find(ljca, ljca_pdata->type); if (IS_ERR(stub)) return PTR_ERR(stub); return ljca_stub_write(stub, cmd, obuf, obuf_len, ibuf, ibuf_len, wait_ack, USB_WRITE_ACK_TIMEOUT); } int ljca_transfer(struct platform_device *pdev, u8 cmd, const void *obuf, int obuf_len, void *ibuf, int *ibuf_len) { return ljca_transfer_internal(pdev, cmd, obuf, obuf_len, ibuf, ibuf_len, true); } EXPORT_SYMBOL_GPL(ljca_transfer); int ljca_transfer_noack(struct platform_device *pdev, u8 cmd, const void *obuf, int obuf_len) { return ljca_transfer_internal(pdev, cmd, obuf, obuf_len, NULL, NULL, false); } EXPORT_SYMBOL_GPL(ljca_transfer_noack); int ljca_register_event_cb(struct platform_device *pdev, ljca_event_cb_t event_cb) { struct ljca_platform_data *ljca_pdata; struct ljca_dev *ljca; struct ljca_stub *stub; unsigned long flags; if (!pdev) return -EINVAL; ljca = dev_get_drvdata(cur_dev); ljca_pdata = dev_get_platdata(&pdev->dev); stub = ljca_stub_find(ljca, ljca_pdata->type); if (IS_ERR(stub)) return PTR_ERR(stub); spin_lock_irqsave(&stub->event_cb_lock, flags); stub->event_entry.notify = event_cb; stub->event_entry.pdev = pdev; spin_unlock_irqrestore(&stub->event_cb_lock, flags); return 0; } EXPORT_SYMBOL_GPL(ljca_register_event_cb); void ljca_unregister_event_cb(struct platform_device *pdev) { struct ljca_platform_data *ljca_pdata; struct ljca_dev *ljca; struct ljca_stub *stub; unsigned long flags; ljca = dev_get_drvdata(cur_dev); ljca_pdata = dev_get_platdata(&pdev->dev); stub = ljca_stub_find(ljca, ljca_pdata->type); if (IS_ERR(stub)) return; spin_lock_irqsave(&stub->event_cb_lock, flags); stub->event_entry.notify = NULL; stub->event_entry.pdev = NULL; spin_unlock_irqrestore(&stub->event_cb_lock, flags); } EXPORT_SYMBOL_GPL(ljca_unregister_event_cb); static void ljca_stub_cleanup(struct ljca_dev *ljca) { struct ljca_stub *stub; struct ljca_stub *next; list_for_each_entry_safe (stub, next, &ljca->stubs_list, list) { list_del_init(&stub->list); kfree(stub); } } static void ljca_read_complete(struct urb *urb) { struct ljca_dev *ljca = urb->context; struct ljca_msg *header = urb->transfer_buffer; int len = urb->actual_length; int ret; dev_dbg(&ljca->intf->dev, "bulk read urb got message from fw, status:%d data_len:%d\n", urb->status, urb->actual_length); BUG_ON(!ljca); BUG_ON(!header); if (urb->status) { /* sync/async unlink faults aren't errors */ if (urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN) return; dev_err(&ljca->intf->dev, "read bulk urb transfer failed: %d\n", urb->status); goto resubmit; } dev_dbg(&ljca->intf->dev, "receive: type:%d cmd:%d flags:%d len:%d\n", header->type, header->cmd, header->flags, header->len); ljca_dump(ljca, header->data, header->len); if (!ljca_validate(header, len)) { dev_err(&ljca->intf->dev, "data not correct header->len:%d payload_len:%d\n ", header->len, len); goto resubmit; } ret = ljca_parse(ljca, header); if (ret) dev_err(&ljca->intf->dev, "failed to parse data: ret:%d type:%d len: %d", ret, header->type, header->len); resubmit: ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret) dev_err(&ljca->intf->dev, "failed submitting read urb, error %d\n", ret); } static int ljca_start(struct ljca_dev *ljca) { int ret; usb_fill_bulk_urb(ljca->in_urb, ljca->udev, usb_rcvbulkpipe(ljca->udev, ljca->in_ep), ljca->ibuf, ljca->ibuf_len, ljca_read_complete, ljca); ret = usb_submit_urb(ljca->in_urb, GFP_KERNEL); if (ret) { dev_err(&ljca->intf->dev, "failed submitting read urb, error %d\n", ret); } return ret; } struct ljca_mng_priv { long reset_id; }; static int ljca_mng_reset_handshake(struct ljca_stub *stub) { int ret; struct ljca_mng_priv *priv; __le32 reset_id; __le32 reset_id_ret = 0; int ilen; priv = ljca_priv(stub); reset_id = cpu_to_le32(priv->reset_id++); ret = ljca_stub_write(stub, MNG_RESET_NOTIFY, &reset_id, sizeof(reset_id), &reset_id_ret, &ilen, true, USB_WRITE_ACK_TIMEOUT); if (ret || ilen != sizeof(reset_id_ret) || reset_id_ret != reset_id) { dev_err(&stub->intf->dev, "MNG_RESET_NOTIFY failed reset_id:%d/%d ret:%d\n", le32_to_cpu(reset_id_ret), le32_to_cpu(reset_id), ret); return -EIO; } return 0; } static inline int ljca_mng_reset(struct ljca_stub *stub) { return ljca_stub_write(stub, MNG_RESET, NULL, 0, NULL, NULL, true, USB_WRITE_ACK_TIMEOUT); } static int ljca_add_mfd_cell(struct ljca_dev *ljca, struct mfd_cell *cell) { struct mfd_cell *new_cells; /* Enumerate the device even if it does not appear in DSDT */ if (!cell->acpi_match->pnpid) dev_warn(&ljca->intf->dev, "The HID of cell %s does not exist in DSDT\n", cell->name); new_cells = krealloc_array(ljca->cells, (ljca->cell_count + 1), sizeof(struct mfd_cell), GFP_KERNEL); if (!new_cells) return -ENOMEM; memcpy(&new_cells[ljca->cell_count], cell, sizeof(*cell)); ljca->cells = new_cells; ljca->cell_count++; return 0; } static int ljca_gpio_stub_init(struct ljca_dev *ljca, struct ljca_gpio_descriptor *desc) { struct ljca_stub *stub; struct mfd_cell cell = { 0 }; struct ljca_platform_data *pdata; int gpio_num = desc->pins_per_bank * desc->bank_num; int i; u32 valid_pin[MAX_GPIO_NUM / (sizeof(u32) * BITS_PER_BYTE)]; if (gpio_num > MAX_GPIO_NUM) return -EINVAL; stub = ljca_stub_alloc(ljca, sizeof(*pdata)); if (IS_ERR(stub)) return PTR_ERR(stub); stub->type = GPIO_STUB; stub->intf = ljca->intf; pdata = ljca_priv(stub); pdata->type = stub->type; pdata->gpio_info.num = gpio_num; for (i = 0; i < desc->bank_num; i++) valid_pin[i] = desc->bank_desc[i].valid_pins; bitmap_from_arr32(pdata->gpio_info.valid_pin_map, valid_pin, gpio_num); cell.name = "ljca-gpio"; cell.platform_data = pdata; cell.pdata_size = sizeof(*pdata); cell.acpi_match = &ljca_acpi_match_gpio; return ljca_add_mfd_cell(ljca, &cell); } static int ljca_mng_enum_gpio(struct ljca_stub *stub) { struct ljca_dev *ljca = usb_get_intfdata(stub->intf); struct ljca_gpio_descriptor *desc; int ret; int len; desc = kzalloc(MAX_PAYLOAD_SIZE, GFP_KERNEL); if (!desc) return -ENOMEM; ret = ljca_stub_write(stub, MNG_ENUM_GPIO, NULL, 0, desc, &len, true, USB_ENUM_STUB_TIMEOUT); if (ret || len != sizeof(*desc) + desc->bank_num * sizeof(desc->bank_desc[0])) { dev_err(&stub->intf->dev, "enum gpio failed ret:%d len:%d bank_num:%d\n", ret, len, desc->bank_num); kfree(desc); return -EIO; } ret = ljca_gpio_stub_init(ljca, desc); kfree(desc); return ret; } static int ljca_i2c_stub_init(struct ljca_dev *ljca, struct ljca_i2c_descriptor *desc) { struct ljca_stub *stub; struct ljca_platform_data *pdata; int i; int ret; stub = ljca_stub_alloc(ljca, desc->num * sizeof(*pdata)); if (IS_ERR(stub)) return PTR_ERR(stub); stub->type = I2C_STUB; stub->intf = ljca->intf; pdata = ljca_priv(stub); for (i = 0; i < desc->num; i++) { struct mfd_cell cell = { 0 }; pdata[i].type = stub->type; pdata[i].i2c_info.id = desc->info[i].id; pdata[i].i2c_info.capacity = desc->info[i].capacity; pdata[i].i2c_info.intr_pin = desc->info[i].intr_pin; cell.name = "ljca-i2c"; cell.platform_data = &pdata[i]; cell.pdata_size = sizeof(pdata[i]); if (i < ARRAY_SIZE(ljca_acpi_match_i2cs)) cell.acpi_match = &ljca_acpi_match_i2cs[i]; ret = ljca_add_mfd_cell(ljca, &cell); if (ret) return ret; } return 0; } static int ljca_mng_enum_i2c(struct ljca_stub *stub) { struct ljca_dev *ljca = usb_get_intfdata(stub->intf); struct ljca_i2c_descriptor *desc; int ret; int len; desc = kzalloc(MAX_PAYLOAD_SIZE, GFP_KERNEL); if (!desc) return -ENOMEM; ret = ljca_stub_write(stub, MNG_ENUM_I2C, NULL, 0, desc, &len, true, USB_ENUM_STUB_TIMEOUT); if (ret) { dev_err(&stub->intf->dev, "MNG_ENUM_I2C failed ret:%d len:%d num:%d\n", ret, len, desc->num); kfree(desc); return -EIO; } ret = ljca_i2c_stub_init(ljca, desc); kfree(desc); return ret; } static int ljca_spi_stub_init(struct ljca_dev *ljca, struct ljca_spi_descriptor *desc) { struct ljca_stub *stub; struct ljca_platform_data *pdata; int i; int ret; stub = ljca_stub_alloc(ljca, desc->num * sizeof(*pdata)); if (IS_ERR(stub)) return PTR_ERR(stub); stub->type = SPI_STUB; stub->intf = ljca->intf; pdata = ljca_priv(stub); for (i = 0; i < desc->num; i++) { struct mfd_cell cell = { 0 }; pdata[i].type = stub->type; pdata[i].spi_info.id = desc->info[i].id; pdata[i].spi_info.capacity = desc->info[i].capacity; cell.name = "ljca-spi"; cell.platform_data = &pdata[i]; cell.pdata_size = sizeof(pdata[i]); if (i < ARRAY_SIZE(ljca_acpi_match_spis)) cell.acpi_match = &ljca_acpi_match_spis[i]; ret = ljca_add_mfd_cell(ljca, &cell); if (ret) return ret; } return 0; } static int ljca_mng_enum_spi(struct ljca_stub *stub) { struct ljca_dev *ljca = usb_get_intfdata(stub->intf); struct ljca_spi_descriptor *desc; int ret; int len; desc = kzalloc(MAX_PAYLOAD_SIZE, GFP_KERNEL); if (!desc) return -ENOMEM; ret = ljca_stub_write(stub, MNG_ENUM_SPI, NULL, 0, desc, &len, true, USB_ENUM_STUB_TIMEOUT); if (ret) { dev_err(&stub->intf->dev, "MNG_ENUM_SPI failed ret:%d len:%d num:%d\n", ret, len, desc->num); kfree(desc); return -EIO; } ret = ljca_spi_stub_init(ljca, desc); kfree(desc); return ret; } static int ljca_mng_get_version(struct ljca_stub *stub, char *buf) { struct fw_version version = { 0 }; int ret; int len; if (!buf) return -EINVAL; ret = ljca_stub_write(stub, MNG_GET_VERSION, NULL, 0, &version, &len, true, USB_WRITE_ACK_TIMEOUT); if (ret || len < sizeof(struct fw_version)) { dev_err(&stub->intf->dev, "MNG_GET_VERSION failed ret:%d len:%d\n", ret, len); return ret; } return sysfs_emit(buf, "%d.%d.%d.%d\n", version.major, version.minor, le16_to_cpu(version.patch), le16_to_cpu(version.build)); } static inline int ljca_mng_set_dfu_mode(struct ljca_stub *stub) { return ljca_stub_write(stub, MNG_SET_DFU_MODE, NULL, 0, NULL, NULL, true, USB_WRITE_ACK_TIMEOUT); } static int ljca_mng_link(struct ljca_dev *ljca, struct ljca_stub *stub) { int ret; ret = ljca_mng_reset_handshake(stub); if (ret) return ret; ljca->state = LJCA_RESET_SYNCED; /* workaround for FW limitation, ignore return value of enum result */ ljca_mng_enum_gpio(stub); ljca->state = LJCA_ENUM_GPIO_COMPLETE; ljca_mng_enum_i2c(stub); ljca->state = LJCA_ENUM_I2C_COMPLETE; ljca_mng_enum_spi(stub); ljca->state = LJCA_ENUM_SPI_COMPLETE; return 0; } static int ljca_mng_init(struct ljca_dev *ljca) { struct ljca_stub *stub; struct ljca_mng_priv *priv; int ret; stub = ljca_stub_alloc(ljca, sizeof(*priv)); if (IS_ERR(stub)) return PTR_ERR(stub); priv = ljca_priv(stub); if (!priv) return -ENOMEM; priv->reset_id = 0; stub->type = MNG_STUB; stub->intf = ljca->intf; ret = ljca_mng_link(ljca, stub); if (ret) dev_err(&ljca->intf->dev, "mng stub link done ret:%d state:%d\n", ret, ljca->state); return ret; } static inline int ljca_diag_get_fw_log(struct ljca_stub *stub, void *buf) { int ret; int len; if (!buf) return -EINVAL; ret = ljca_stub_write(stub, DIAG_GET_FW_LOG, NULL, 0, buf, &len, true, USB_WRITE_ACK_TIMEOUT); if (ret) return ret; return len; } static inline int ljca_diag_get_coredump(struct ljca_stub *stub, void *buf) { int ret; int len; if (!buf) return -EINVAL; ret = ljca_stub_write(stub, DIAG_GET_FW_COREDUMP, NULL, 0, buf, &len, true, USB_WRITE_ACK_TIMEOUT); if (ret) return ret; return len; } static inline int ljca_diag_set_trace_level(struct ljca_stub *stub, u8 level) { return ljca_stub_write(stub, DIAG_SET_TRACE_LEVEL, &level, sizeof(level), NULL, NULL, true, USB_WRITE_ACK_TIMEOUT); } static int ljca_diag_init(struct ljca_dev *ljca) { struct ljca_stub *stub; stub = ljca_stub_alloc(ljca, 0); if (IS_ERR(stub)) return PTR_ERR(stub); stub->type = DIAG_STUB; stub->intf = ljca->intf; return 0; } static void ljca_delete(struct ljca_dev *ljca) { mutex_destroy(&ljca->mutex); usb_free_urb(ljca->in_urb); usb_put_intf(ljca->intf); usb_put_dev(ljca->udev); kfree(ljca->ibuf); kfree(ljca->cells); kfree(ljca); } static int ljca_init(struct ljca_dev *ljca) { mutex_init(&ljca->mutex); init_waitqueue_head(&ljca->ack_wq); INIT_LIST_HEAD(&ljca->stubs_list); ljca->state = LJCA_INITED; return 0; } static void ljca_stop(struct ljca_dev *ljca) { usb_kill_urb(ljca->in_urb); } static ssize_t cmd_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct usb_interface *intf = to_usb_interface(dev); struct ljca_dev *ljca = usb_get_intfdata(intf); struct ljca_stub *mng_stub = ljca_stub_find(ljca, MNG_STUB); struct ljca_stub *diag_stub = ljca_stub_find(ljca, DIAG_STUB); if (sysfs_streq(buf, "dfu")) ljca_mng_set_dfu_mode(mng_stub); else if (sysfs_streq(buf, "reset")) ljca_mng_reset(mng_stub); else if (sysfs_streq(buf, "debug")) ljca_diag_set_trace_level(diag_stub, 3); return count; } static ssize_t cmd_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n", "supported cmd: [dfu, reset, debug]"); } static DEVICE_ATTR_RW(cmd); static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct ljca_dev *ljca = usb_get_intfdata(intf); struct ljca_stub *stub = ljca_stub_find(ljca, MNG_STUB); return ljca_mng_get_version(stub, buf); } static DEVICE_ATTR_RO(version); static ssize_t log_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct ljca_dev *ljca = usb_get_intfdata(intf); struct ljca_stub *diag_stub = ljca_stub_find(ljca, DIAG_STUB); return ljca_diag_get_fw_log(diag_stub, buf); } static DEVICE_ATTR_RO(log); static ssize_t coredump_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct ljca_dev *ljca = usb_get_intfdata(intf); struct ljca_stub *diag_stub = ljca_stub_find(ljca, DIAG_STUB); return ljca_diag_get_coredump(diag_stub, buf); } static DEVICE_ATTR_RO(coredump); static struct attribute *ljca_attrs[] = { &dev_attr_version.attr, &dev_attr_cmd.attr, &dev_attr_log.attr, &dev_attr_coredump.attr, NULL, }; ATTRIBUTE_GROUPS(ljca); static int ljca_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct ljca_dev *ljca; struct usb_endpoint_descriptor *bulk_in, *bulk_out; int ret; cur_dev = &intf->dev; ret = precheck_acpi_hid(intf); if (ret) return ret; /* allocate memory for our device state and initialize it */ ljca = kzalloc(sizeof(*ljca), GFP_KERNEL); if (!ljca) return -ENOMEM; ljca_init(ljca); ljca->udev = usb_get_dev(interface_to_usbdev(intf)); ljca->intf = usb_get_intf(intf); /* set up the endpoint information use only the first bulk-in and bulk-out endpoints */ ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL); if (ret) { dev_err(&intf->dev, "Could not find both bulk-in and bulk-out endpoints\n"); goto error; } ljca->ibuf_len = usb_endpoint_maxp(bulk_in); ljca->in_ep = bulk_in->bEndpointAddress; ljca->ibuf = kzalloc(ljca->ibuf_len, GFP_KERNEL); if (!ljca->ibuf) { ret = -ENOMEM; goto error; } ljca->in_urb = usb_alloc_urb(0, GFP_KERNEL); if (!ljca->in_urb) { ret = -ENOMEM; goto error; } ljca->out_ep = bulk_out->bEndpointAddress; dev_dbg(&intf->dev, "bulk_in size:%zu addr:%d bulk_out addr:%d\n", ljca->ibuf_len, ljca->in_ep, ljca->out_ep); /* save our data pointer in this intf device */ usb_set_intfdata(intf, ljca); ret = ljca_start(ljca); if (ret) { dev_err(&intf->dev, "bridge read start failed ret %d\n", ret); goto error; } ret = ljca_mng_init(ljca); if (ret) { dev_err(&intf->dev, "register mng stub failed ret %d\n", ret); goto error_stop; } ret = ljca_diag_init(ljca); if (ret) { dev_err(&intf->dev, "register diag stub failed ret %d\n", ret); goto error_stop; } ret = mfd_add_hotplug_devices(sub_dev_parent, ljca->cells, ljca->cell_count); if (ret) { dev_err(&intf->dev, "failed to add mfd devices to core %d\n", ljca->cell_count); goto error_stop; } ljca->state = LJCA_STARTED; dev_info(&intf->dev, "LJCA USB device init success\n"); return 0; error_stop: ljca_stop(ljca); error: dev_err(&intf->dev, "LJCA USB device init failed\n"); /* this frees allocated memory */ ljca_stub_cleanup(ljca); ljca_delete(ljca); return ret; } static void ljca_disconnect(struct usb_interface *intf) { struct ljca_dev *ljca; ljca = usb_get_intfdata(intf); ljca_stop(ljca); ljca->state = LJCA_STOPPED; mfd_remove_devices(sub_dev_parent); ljca_stub_cleanup(ljca); usb_set_intfdata(intf, NULL); ljca_delete(ljca); dev_info(&intf->dev, "LJCA disconnected\n"); } static int ljca_suspend(struct usb_interface *intf, pm_message_t message) { struct ljca_dev *ljca = usb_get_intfdata(intf); ljca_stop(ljca); ljca->state = LJCA_SUSPEND; dev_dbg(&intf->dev, "LJCA suspend\n"); return 0; } static int ljca_resume(struct usb_interface *intf) { struct ljca_dev *ljca = usb_get_intfdata(intf); ljca->state = LJCA_STARTED; dev_dbg(&intf->dev, "LJCA resume\n"); return ljca_start(ljca); } static const struct usb_device_id ljca_table[] = { {USB_DEVICE(0x8086, 0x0b63)}, {} }; MODULE_DEVICE_TABLE(usb, ljca_table); static struct usb_driver ljca_driver = { .name = "ljca", .probe = ljca_probe, .disconnect = ljca_disconnect, .suspend = ljca_suspend, .resume = ljca_resume, .id_table = ljca_table, .dev_groups = ljca_groups, .supports_autosuspend = 1, }; module_usb_driver(ljca_driver); MODULE_AUTHOR("Ye Xiang "); MODULE_AUTHOR("Zhang Lixu "); MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver"); MODULE_LICENSE("GPL v2"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/000077500000000000000000000000001452306460500211725ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/000077500000000000000000000000001452306460500221365ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/Kconfig000066400000000000000000000020651452306460500234440ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2021, Intel Corporation. All rights reserved. config INTEL_VSC tristate "Intel VSC" select INTEL_MEI_VSC help Add support of Intel Visual Sensing Controller (IVSC). config INTEL_VSC_CSI tristate "Intel VSC CSI client" depends on INTEL_VSC select INTEL_MEI help Add CSI support for Intel Visual Sensing Controller (IVSC). config INTEL_VSC_ACE tristate "Intel VSC ACE client" depends on INTEL_VSC select INTEL_MEI help Add ACE support for Intel Visual Sensing Controller (IVSC). config INTEL_VSC_PSE tristate "Intel VSC PSE client" depends on DEBUG_FS select INTEL_MEI select INTEL_MEI_VSC help Add PSE support for Intel Visual Sensing Controller (IVSC) to expose debugging information in files under /sys/kernel/debug/. config INTEL_VSC_ACE_DEBUG tristate "Intel VSC ACE debug client" depends on DEBUG_FS select INTEL_MEI select INTEL_MEI_VSC help Add ACE debug support for Intel Visual Sensing Controller (IVSC) to expose debugging information in files under /sys/kernel/debug/. ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/Makefile000066400000000000000000000005001452306460500235710ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # # Copyright (c) 2021, Intel Corporation. All rights reserved. obj-$(CONFIG_INTEL_VSC) += intel_vsc.o obj-$(CONFIG_INTEL_VSC_CSI) += mei_csi.o obj-$(CONFIG_INTEL_VSC_ACE) += mei_ace.o obj-$(CONFIG_INTEL_VSC_PSE) += mei_pse.o obj-$(CONFIG_INTEL_VSC_ACE_DEBUG) += mei_ace_debug.o ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/intel_vsc.c000066400000000000000000000112371452306460500242740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Intel Corporation */ #include #include #include #include #include #include #include "intel_vsc.h" #define ACE_PRIVACY_ON 2 struct intel_vsc { spinlock_t lock; struct mutex mutex; void *csi; struct vsc_csi_ops *csi_ops; uint16_t csi_registerred; void *ace; struct vsc_ace_ops *ace_ops; uint16_t ace_registerred; }; static struct intel_vsc vsc; static int check_component_ready(void) { int ret = -1; unsigned long flags; spin_lock_irqsave(&vsc.lock, flags); if (vsc.ace_registerred && vsc.csi_registerred) ret = 0; spin_unlock_irqrestore(&vsc.lock, flags); return ret; } static void update_camera_status(struct vsc_camera_status *status, struct camera_status *s) { if (status && s) { status->owner = s->camera_owner; status->exposure_level = s->exposure_level; status->status = VSC_PRIVACY_OFF; if (s->privacy_stat == ACE_PRIVACY_ON) status->status = VSC_PRIVACY_ON; } } int vsc_register_ace(void *ace, struct vsc_ace_ops *ops) { unsigned long flags; if (ace && ops) { if (ops->ipu_own_camera && ops->ace_own_camera) { spin_lock_irqsave(&vsc.lock, flags); vsc.ace = ace; vsc.ace_ops = ops; vsc.ace_registerred = true; spin_unlock_irqrestore(&vsc.lock, flags); return 0; } } pr_err("register ace failed\n"); return -1; } EXPORT_SYMBOL_GPL(vsc_register_ace); void vsc_unregister_ace(void) { unsigned long flags; spin_lock_irqsave(&vsc.lock, flags); vsc.ace_registerred = false; spin_unlock_irqrestore(&vsc.lock, flags); } EXPORT_SYMBOL_GPL(vsc_unregister_ace); int vsc_register_csi(void *csi, struct vsc_csi_ops *ops) { unsigned long flags; if (csi && ops) { if (ops->set_privacy_callback && ops->set_owner && ops->set_mipi_conf) { spin_lock_irqsave(&vsc.lock, flags); vsc.csi = csi; vsc.csi_ops = ops; vsc.csi_registerred = true; spin_unlock_irqrestore(&vsc.lock, flags); return 0; } } pr_err("register csi failed\n"); return -1; } EXPORT_SYMBOL_GPL(vsc_register_csi); void vsc_unregister_csi(void) { unsigned long flags; spin_lock_irqsave(&vsc.lock, flags); vsc.csi_registerred = false; spin_unlock_irqrestore(&vsc.lock, flags); } EXPORT_SYMBOL_GPL(vsc_unregister_csi); int vsc_acquire_camera_sensor(struct vsc_mipi_config *config, vsc_privacy_callback_t callback, void *handle, struct vsc_camera_status *status) { int ret; struct camera_status s; struct mipi_conf conf = { 0 }; struct vsc_csi_ops *csi_ops; struct vsc_ace_ops *ace_ops; if (!config) return -EINVAL; ret = check_component_ready(); if (ret < 0) { pr_info("intel vsc not ready\n"); return -EAGAIN; } mutex_lock(&vsc.mutex); /* no need check component again here */ csi_ops = vsc.csi_ops; ace_ops = vsc.ace_ops; csi_ops->set_privacy_callback(vsc.csi, callback, handle); ret = ace_ops->ipu_own_camera(vsc.ace, &s); if (ret) { pr_err("ipu own camera failed\n"); goto err; } update_camera_status(status, &s); ret = csi_ops->set_owner(vsc.csi, CSI_IPU); if (ret) { pr_err("ipu own csi failed\n"); goto err; } conf.lane_num = config->lane_num; conf.freq = config->freq; ret = csi_ops->set_mipi_conf(vsc.csi, &conf); if (ret) { pr_err("config mipi failed\n"); goto err; } err: mutex_unlock(&vsc.mutex); msleep(100); return ret; } EXPORT_SYMBOL_GPL(vsc_acquire_camera_sensor); int vsc_release_camera_sensor(struct vsc_camera_status *status) { int ret; struct camera_status s; struct vsc_csi_ops *csi_ops; struct vsc_ace_ops *ace_ops; ret = check_component_ready(); if (ret < 0) { pr_info("intel vsc not ready\n"); return -EAGAIN; } mutex_lock(&vsc.mutex); /* no need check component again here */ csi_ops = vsc.csi_ops; ace_ops = vsc.ace_ops; csi_ops->set_privacy_callback(vsc.csi, NULL, NULL); ret = csi_ops->set_owner(vsc.csi, CSI_FW); if (ret) { pr_err("vsc own csi failed\n"); goto err; } ret = ace_ops->ace_own_camera(vsc.ace, &s); if (ret) { pr_err("vsc own camera failed\n"); goto err; } update_camera_status(status, &s); err: mutex_unlock(&vsc.mutex); return ret; } EXPORT_SYMBOL_GPL(vsc_release_camera_sensor); static int __init intel_vsc_init(void) { memset(&vsc, 0, sizeof(struct intel_vsc)); spin_lock_init(&vsc.lock); mutex_init(&vsc.mutex); vsc.csi_registerred = false; vsc.ace_registerred = false; return 0; } static void __exit intel_vsc_exit(void) { } module_init(intel_vsc_init); module_exit(intel_vsc_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_SOFTDEP("post: mei_csi mei_ace"); MODULE_DESCRIPTION("Device driver for Intel VSC"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/intel_vsc.h000066400000000000000000000076631452306460500243110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _INTEL_VSC_H_ #define _INTEL_VSC_H_ #include /* csi power state definition */ enum csi_power_state { POWER_OFF = 0, POWER_ON, }; /* csi ownership definition */ enum csi_owner { CSI_FW = 0, CSI_IPU, }; /* mipi configuration structure */ struct mipi_conf { uint32_t lane_num; uint32_t freq; /* for future use */ uint32_t rsvd[2]; } __packed; /* camera status structure */ struct camera_status { uint8_t camera_owner : 2; uint8_t privacy_stat : 2; /* for future use */ uint8_t rsvd : 4; uint32_t exposure_level; } __packed; struct vsc_ace_ops { /** * @brief ace own camera ownership * * @param ace The pointer of ace client device * @param status The pointer of camera status * * @return 0 on success, negative on failure */ int (*ace_own_camera)(void *ace, struct camera_status *status); /** * @brief ipu own camera ownership * * @param ace The pointer of ace client device * @param status The pointer of camera status * * @return 0 on success, negative on failure */ int (*ipu_own_camera)(void *ace, struct camera_status *status); /** * @brief get current camera status * * @param ace The pointer of ace client device * @param status The pointer of camera status * * @return 0 on success, negative on failure */ int (*get_camera_status)(void *ace, struct camera_status *status); }; struct vsc_csi_ops { /** * @brief set csi ownership * * @param csi The pointer of csi client device * @param owner The csi ownership going to set * * @return 0 on success, negative on failure */ int (*set_owner)(void *csi, enum csi_owner owner); /** * @brief get current csi ownership * * @param csi The pointer of csi client device * @param owner The pointer of csi ownership * * @return 0 on success, negative on failure */ int (*get_owner)(void *csi, enum csi_owner *owner); /** * @brief configure csi with provided parameter * * @param csi The pointer of csi client device * @param config The pointer of csi configuration * parameter going to set * * @return 0 on success, negative on failure */ int (*set_mipi_conf)(void *csi, struct mipi_conf *conf); /** * @brief get the current csi configuration * * @param csi The pointer of csi client device * @param config The pointer of csi configuration parameter * holding the returned result * * @return 0 on success, negative on failure */ int (*get_mipi_conf)(void *csi, struct mipi_conf *conf); /** * @brief set csi power state * * @param csi The pointer of csi client device * @param status csi power status going to set * * @return 0 on success, negative on failure */ int (*set_power_state)(void *csi, enum csi_power_state state); /** * @brief get csi power state * * @param csi The pointer of csi client device * @param status The pointer of variable holding csi power status * * @return 0 on success, negative on failure */ int (*get_power_state)(void *csi, enum csi_power_state *state); /** * @brief set csi privacy callback * * @param csi The pointer of csi client device * @param callback The pointer of privacy callback function * @param handle Privacy callback runtime context */ void (*set_privacy_callback)(void *csi, vsc_privacy_callback_t callback, void *handle); }; /** * @brief register ace client * * @param ace The pointer of ace client device * @param ops The pointer of ace ops * * @return 0 on success, negative on failure */ int vsc_register_ace(void *ace, struct vsc_ace_ops *ops); /** * @brief unregister ace client */ void vsc_unregister_ace(void); /** * @brief register csi client * * @param csi The pointer of csi client device * @param ops The pointer of csi ops * * @return 0 on success, negative on failure */ int vsc_register_csi(void *csi, struct vsc_csi_ops *ops); /** * @brief unregister csi client */ void vsc_unregister_csi(void); #endif ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/mei_ace.c000066400000000000000000000302461452306460500236710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Intel Corporation */ #include #include #include #include #include #include #include #include #include "intel_vsc.h" #define ACE_TIMEOUT (5 * HZ) #define MEI_ACE_DRIVER_NAME "vsc_ace" #define UUID_GET_FW_ID UUID_LE(0x6167DCFB, 0x72F1, 0x4584, \ 0xBF, 0xE3, 0x84, 0x17, 0x71, 0xAA, 0x79, 0x0B) enum notif_rsp { NOTIF = 0, REPLY = 1, }; enum notify_type { FW_READY = 8, EXCEPTION = 10, WATCHDOG_TIMEOUT = 15, MANAGEMENT_NOTIF = 16, NOTIFICATION = 27, }; enum message_source { FW_MSG = 0, DRV_MSG = 1, }; enum notify_event_type { STATE_NOTIF = 0x1, CTRL_NOTIF = 0x2, DPHY_NOTIF = 0x3, }; enum ace_cmd_type { ACE_CMD_GET = 3, ACE_CMD_SET = 4, }; enum ace_cmd_id { IPU_OWN_CAMERA = 0x13, ACE_OWN_CAMERA = 0x14, GET_CAMERA_STATUS = 0x15, GET_FW_ID = 0x1A, }; struct ace_cmd_hdr { uint32_t module_id : 16; uint32_t instance_id : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t param_size : 20; uint32_t cmd_id : 8; uint32_t final_block : 1; uint32_t init_block : 1; uint32_t _hw_rsvd_2 : 2; } __packed; union ace_cmd_param { uuid_le uuid; uint32_t param; }; struct ace_cmd { struct ace_cmd_hdr hdr; union ace_cmd_param param; } __packed; union ace_notif_hdr { struct _response { uint32_t status : 24; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t param_size : 20; uint32_t cmd_id : 8; uint32_t final_block : 1; uint32_t init_block : 1; uint32_t _hw_rsvd_2 : 2; } __packed response; struct _notify { uint32_t rsvd2 : 16; uint32_t notif_type : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t rsvd1 : 30; uint32_t _hw_rsvd_2 : 2; } __packed notify; struct _management { uint32_t event_id : 16; uint32_t notif_type : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t event_data_size : 16; uint32_t request_target : 1; uint32_t request_type : 5; uint32_t request_id : 8; uint32_t _hw_rsvd_2 : 2; } __packed management; }; union ace_notif_cont { uint16_t module_id; uint8_t state_notif; struct camera_status stat; }; struct ace_notif { union ace_notif_hdr hdr; union ace_notif_cont cont; } __packed; struct mei_ace { struct mei_cl_device *cldev; struct mutex cmd_mutex; struct ace_notif *cmd_resp; struct completion response; struct completion reply; struct ace_notif *reply_notif; uint16_t module_id; uint16_t init_wait_q_woken; wait_queue_head_t init_wait_q; }; static inline void init_cmd_hdr(struct ace_cmd_hdr *hdr) { memset(hdr, 0, sizeof(struct ace_cmd_hdr)); hdr->type = ACE_CMD_SET; hdr->msg_tgt = DRV_MSG; hdr->init_block = 1; hdr->final_block = 1; } static uint16_t get_fw_id(struct mei_ace *ace) { int ret; ret = wait_event_interruptible(ace->init_wait_q, ace->init_wait_q_woken); if (ret < 0) dev_warn(&ace->cldev->dev, "incorrect fw id sent to fw\n"); return ace->module_id; } static int construct_command(struct mei_ace *ace, struct ace_cmd *cmd, enum ace_cmd_id cmd_id) { struct ace_cmd_hdr *hdr = &cmd->hdr; union ace_cmd_param *param = &cmd->param; init_cmd_hdr(hdr); hdr->cmd_id = cmd_id; switch (cmd_id) { case GET_FW_ID: param->uuid = UUID_GET_FW_ID; hdr->param_size = sizeof(param->uuid); break; case ACE_OWN_CAMERA: param->param = 0; hdr->module_id = get_fw_id(ace); hdr->param_size = sizeof(param->param); break; case IPU_OWN_CAMERA: case GET_CAMERA_STATUS: hdr->module_id = get_fw_id(ace); break; default: dev_err(&ace->cldev->dev, "sending not supported command"); break; } return hdr->param_size + sizeof(cmd->hdr); } static int send_command_sync(struct mei_ace *ace, struct ace_cmd *cmd, size_t len) { int ret; struct ace_cmd_hdr *cmd_hdr = &cmd->hdr; union ace_notif_hdr *resp_hdr = &ace->cmd_resp->hdr; union ace_notif_hdr *reply_hdr = &ace->reply_notif->hdr; reinit_completion(&ace->response); reinit_completion(&ace->reply); ret = mei_cldev_send(ace->cldev, (uint8_t *)cmd, len); if (ret < 0) { dev_err(&ace->cldev->dev, "send command fail %d\n", ret); return ret; } ret = wait_for_completion_killable_timeout(&ace->reply, ACE_TIMEOUT); if (ret < 0) { dev_err(&ace->cldev->dev, "command %d notify reply error\n", cmd_hdr->cmd_id); return ret; } else if (ret == 0) { dev_err(&ace->cldev->dev, "command %d notify reply timeout\n", cmd_hdr->cmd_id); ret = -ETIMEDOUT; return ret; } if (reply_hdr->response.cmd_id != cmd_hdr->cmd_id) { dev_err(&ace->cldev->dev, "reply notify mismatch, sent %d but got %d\n", cmd_hdr->cmd_id, reply_hdr->response.cmd_id); return -1; } ret = reply_hdr->response.status; if (ret) { dev_err(&ace->cldev->dev, "command %d reply wrong status = %d\n", cmd_hdr->cmd_id, ret); return -1; } ret = wait_for_completion_killable_timeout(&ace->response, ACE_TIMEOUT); if (ret < 0) { dev_err(&ace->cldev->dev, "command %d response error\n", cmd_hdr->cmd_id); return ret; } else if (ret == 0) { dev_err(&ace->cldev->dev, "command %d response timeout\n", cmd_hdr->cmd_id); ret = -ETIMEDOUT; return ret; } if (resp_hdr->management.request_id != cmd_hdr->cmd_id) { dev_err(&ace->cldev->dev, "command response mismatch, sent %d but got %d\n", cmd_hdr->cmd_id, resp_hdr->management.request_id); return -1; } return 0; } static int trigger_get_fw_id(struct mei_ace *ace) { int ret; struct ace_cmd cmd; size_t cmd_len; cmd_len = construct_command(ace, &cmd, GET_FW_ID); ret = mei_cldev_send(ace->cldev, (uint8_t *)&cmd, cmd_len); if (ret < 0) dev_err(&ace->cldev->dev, "send get fw id command fail %d\n", ret); return ret; } static int set_camera_ownership(struct mei_ace *ace, enum ace_cmd_id cmd_id, struct camera_status *status) { struct ace_cmd cmd; size_t cmd_len; union ace_notif_cont *cont; int ret; cmd_len = construct_command(ace, &cmd, cmd_id); mutex_lock(&ace->cmd_mutex); ret = send_command_sync(ace, &cmd, cmd_len); if (!ret) { cont = &ace->cmd_resp->cont; memcpy(status, &cont->stat, sizeof(*status)); } mutex_unlock(&ace->cmd_mutex); return ret; } int ipu_own_camera(void *ace, struct camera_status *status) { struct mei_ace *p_ace = (struct mei_ace *)ace; return set_camera_ownership(p_ace, IPU_OWN_CAMERA, status); } int ace_own_camera(void *ace, struct camera_status *status) { struct mei_ace *p_ace = (struct mei_ace *)ace; return set_camera_ownership(p_ace, ACE_OWN_CAMERA, status); } int get_camera_status(void *ace, struct camera_status *status) { int ret; struct ace_cmd cmd; size_t cmd_len; union ace_notif_cont *cont; struct mei_ace *p_ace = (struct mei_ace *)ace; cmd_len = construct_command(p_ace, &cmd, GET_CAMERA_STATUS); mutex_lock(&p_ace->cmd_mutex); ret = send_command_sync(p_ace, &cmd, cmd_len); if (!ret) { cont = &p_ace->cmd_resp->cont; memcpy(status, &cont->stat, sizeof(*status)); } mutex_unlock(&p_ace->cmd_mutex); return ret; } static struct vsc_ace_ops ace_ops = { .ace_own_camera = ace_own_camera, .ipu_own_camera = ipu_own_camera, .get_camera_status = get_camera_status, }; static void handle_notify(struct mei_ace *ace, struct ace_notif *resp, int len) { union ace_notif_hdr *hdr = &resp->hdr; struct mei_cl_device *cldev = ace->cldev; if (hdr->notify.msg_tgt != FW_MSG || hdr->notify.type != NOTIFICATION) { dev_err(&cldev->dev, "recv incorrect notification\n"); return; } switch (hdr->notify.notif_type) { /* firmware ready notification sent to driver * after HECI client connected with firmware. */ case FW_READY: dev_info(&cldev->dev, "firmware ready\n"); trigger_get_fw_id(ace); break; case MANAGEMENT_NOTIF: if (hdr->management.event_id == CTRL_NOTIF) { switch (hdr->management.request_id) { case GET_FW_ID: dev_warn(&cldev->dev, "shouldn't reach here\n"); break; case ACE_OWN_CAMERA: case IPU_OWN_CAMERA: case GET_CAMERA_STATUS: memcpy(ace->cmd_resp, resp, len); if (!completion_done(&ace->response)) complete(&ace->response); break; default: dev_err(&cldev->dev, "incorrect command id notif\n"); break; } } break; case EXCEPTION: dev_err(&cldev->dev, "firmware exception\n"); break; case WATCHDOG_TIMEOUT: dev_err(&cldev->dev, "firmware watchdog timeout\n"); break; default: dev_err(&cldev->dev, "recv unknown notification(%d)\n", hdr->notify.notif_type); break; } } /* callback for command response receive */ static void mei_ace_rx(struct mei_cl_device *cldev) { struct mei_ace *ace = mei_cldev_get_drvdata(cldev); int ret; struct ace_notif resp; union ace_notif_hdr *hdr = &resp.hdr; ret = mei_cldev_recv(cldev, (uint8_t *)&resp, sizeof(resp)); if (ret < 0) { dev_err(&cldev->dev, "failure in recv %d\n", ret); return; } else if (ret < sizeof(union ace_notif_hdr)) { dev_err(&cldev->dev, "recv small data %d\n", ret); return; } switch (hdr->notify.rsp) { case REPLY: if (hdr->response.cmd_id == GET_FW_ID) { ace->module_id = resp.cont.module_id; ace->init_wait_q_woken = true; wake_up_all(&ace->init_wait_q); dev_info(&cldev->dev, "recv firmware id\n"); } else { memcpy(ace->reply_notif, &resp, ret); if (!completion_done(&ace->reply)) complete(&ace->reply); } break; case NOTIF: handle_notify(ace, &resp, ret); break; default: dev_err(&cldev->dev, "recv unknown response(%d)\n", hdr->notify.rsp); break; } } static int mei_ace_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) { struct mei_ace *ace; int ret; uint8_t *addr; size_t ace_size = sizeof(struct mei_ace); size_t reply_size = sizeof(struct ace_notif); size_t response_size = sizeof(struct ace_notif); ace = kzalloc(ace_size + response_size + reply_size, GFP_KERNEL); if (!ace) return -ENOMEM; addr = (uint8_t *)ace; ace->cmd_resp = (struct ace_notif *)(addr + ace_size); addr = (uint8_t *)ace->cmd_resp; ace->reply_notif = (struct ace_notif *)(addr + response_size); ace->cldev = cldev; ace->init_wait_q_woken = false; init_waitqueue_head(&ace->init_wait_q); mutex_init(&ace->cmd_mutex); init_completion(&ace->response); init_completion(&ace->reply); mei_cldev_set_drvdata(cldev, ace); ret = mei_cldev_enable(cldev); if (ret < 0) { dev_err(&cldev->dev, "couldn't enable ace client ret=%d\n", ret); goto err_out; } ret = mei_cldev_register_rx_cb(cldev, mei_ace_rx); if (ret) { dev_err(&cldev->dev, "couldn't register rx cb ret=%d\n", ret); goto err_disable; } trigger_get_fw_id(ace); vsc_register_ace(ace, &ace_ops); return 0; err_disable: mei_cldev_disable(cldev); err_out: kfree(ace); return ret; } static void mei_ace_remove(struct mei_cl_device *cldev) { struct mei_ace *ace = mei_cldev_get_drvdata(cldev); vsc_unregister_ace(); if (!completion_done(&ace->response)) complete(&ace->response); if (!completion_done(&ace->reply)) complete(&ace->reply); if (wq_has_sleeper(&ace->init_wait_q)) wake_up_all(&ace->init_wait_q); mei_cldev_disable(cldev); /* wait until no buffer access */ mutex_lock(&ace->cmd_mutex); mutex_unlock(&ace->cmd_mutex); kfree(ace); } #define MEI_UUID_ACE UUID_LE(0x5DB76CF6, 0x0A68, 0x4ED6, \ 0x9B, 0x78, 0x03, 0x61, 0x63, 0x5E, 0x24, 0x47) static const struct mei_cl_device_id mei_ace_tbl[] = { { .uuid = MEI_UUID_ACE, .version = MEI_CL_VERSION_ANY }, /* required last entry */ { } }; MODULE_DEVICE_TABLE(mei, mei_ace_tbl); static struct mei_cl_driver mei_ace_driver = { .id_table = mei_ace_tbl, .name = MEI_ACE_DRIVER_NAME, .probe = mei_ace_probe, .remove = mei_ace_remove, }; static int __init mei_ace_init(void) { int ret; ret = mei_cldev_driver_register(&mei_ace_driver); if (ret) { pr_err("mei ace driver registration failed: %d\n", ret); return ret; } return 0; } static void __exit mei_ace_exit(void) { mei_cldev_driver_unregister(&mei_ace_driver); } module_init(mei_ace_init); module_exit(mei_ace_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Device driver for Intel VSC ACE client"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/mei_ace_debug.c000066400000000000000000000346601452306460500250430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_RECV_SIZE 8192 #define MAX_LOG_SIZE 0x40000000 #define LOG_CONFIG_PARAM_COUNT 7 #define COMMAND_TIMEOUT (5 * HZ) #define ACE_LOG_FILE "/var/log/vsc_ace.log" #define MEI_ACE_DEBUG_DRIVER_NAME "vsc_ace_debug" enum notif_rsp { NOTIF = 0, REPLY = 1, }; enum message_source { FW_MSG = 0, DRV_MSG = 1, }; enum notify_type { LOG_BUFFER_STATUS = 6, FW_READY = 8, MANAGEMENT_NOTIF = 16, NOTIFICATION = 27, }; enum notify_event_type { STATE_NOTIF = 1, CTRL_NOTIF = 2, }; enum ace_cmd_id { GET_FW_VER = 0, LOG_CONFIG = 6, SET_SYS_TIME = 20, GET_FW_ID = 26, }; enum ace_cmd_type { ACE_CMD_GET = 3, ACE_CMD_SET = 4, }; struct firmware_version { uint32_t type; uint32_t len; uint16_t major; uint16_t minor; uint16_t hotfix; uint16_t build; } __packed; union tracing_config { struct _uart_config { uint32_t instance; uint32_t baudrate; } __packed uart; struct _i2c_config { uint32_t instance; uint32_t speed; uint32_t address; uint32_t reg; } __packed i2c; }; struct log_config { uint32_t aging_period; uint32_t fifo_period; uint32_t enable; uint32_t priority_mask[16]; uint32_t tracing_method; uint32_t tracing_format; union tracing_config config; } __packed; struct ace_cmd_hdr { uint32_t module_id : 16; uint32_t instance_id : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t param_size : 20; uint32_t cmd_id : 8; uint32_t final_block : 1; uint32_t init_block : 1; uint32_t _hw_rsvd_2 : 2; } __packed; union ace_cmd_param { uuid_le uuid; uint64_t time; struct log_config config; }; struct ace_cmd { struct ace_cmd_hdr hdr; union ace_cmd_param param; } __packed; union ace_notif_hdr { struct _response { uint32_t status : 24; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t param_size : 20; uint32_t cmd_id : 8; uint32_t final_block : 1; uint32_t init_block : 1; uint32_t _hw_rsvd_2 : 2; } __packed response; struct _notify { uint32_t rsvd2 : 16; uint32_t notif_type : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t rsvd1 : 30; uint32_t _hw_rsvd_2 : 2; } __packed notify; struct _log_notify { uint32_t rsvd0 : 12; uint32_t source_core : 4; uint32_t notif_type : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t rsvd1 : 30; uint32_t _hw_rsvd_2 : 2; } __packed log_notify; struct _management { uint32_t event_id : 16; uint32_t notif_type : 8; uint32_t type : 5; uint32_t rsp : 1; uint32_t msg_tgt : 1; uint32_t _hw_rsvd_0 : 1; uint32_t event_data_size : 16; uint32_t request_target : 1; uint32_t request_type : 5; uint32_t request_id : 8; uint32_t _hw_rsvd_2 : 2; } __packed management; }; union ace_notif_cont { uint16_t module_id; struct firmware_version version; }; struct ace_notif { union ace_notif_hdr hdr; union ace_notif_cont cont; } __packed; struct mei_ace_debug { struct mei_cl_device *cldev; struct mutex cmd_mutex; struct ace_notif cmd_resp; struct completion response; struct completion reply; struct ace_notif reply_notif; loff_t pos; struct file *ace_file; uint8_t *recv_buf; struct dentry *dfs_dir; }; static inline void init_cmd_hdr(struct ace_cmd_hdr *hdr) { memset(hdr, 0, sizeof(struct ace_cmd_hdr)); hdr->type = ACE_CMD_SET; hdr->msg_tgt = DRV_MSG; hdr->init_block = 1; hdr->final_block = 1; } static int construct_command(struct mei_ace_debug *ad, struct ace_cmd *cmd, enum ace_cmd_id cmd_id, void *user_data) { struct ace_cmd_hdr *hdr = &cmd->hdr; union ace_cmd_param *param = &cmd->param; init_cmd_hdr(hdr); hdr->cmd_id = cmd_id; switch (cmd_id) { case GET_FW_VER: hdr->type = ACE_CMD_GET; break; case SET_SYS_TIME: param->time = ktime_get_ns(); hdr->param_size = sizeof(param->time); break; case GET_FW_ID: memcpy(¶m->uuid, user_data, sizeof(param->uuid)); hdr->param_size = sizeof(param->uuid); break; case LOG_CONFIG: memcpy(¶m->config, user_data, sizeof(param->config)); hdr->param_size = sizeof(param->config); break; default: dev_err(&ad->cldev->dev, "sending not supported command"); break; } return hdr->param_size + sizeof(cmd->hdr); } static int send_command_sync(struct mei_ace_debug *ad, struct ace_cmd *cmd, size_t len) { int ret; struct ace_cmd_hdr *cmd_hdr = &cmd->hdr; union ace_notif_hdr *reply_hdr = &ad->reply_notif.hdr; reinit_completion(&ad->reply); ret = mei_cldev_send(ad->cldev, (uint8_t *)cmd, len); if (ret < 0) { dev_err(&ad->cldev->dev, "send command fail %d\n", ret); return ret; } ret = wait_for_completion_killable_timeout(&ad->reply, COMMAND_TIMEOUT); if (ret < 0) { dev_err(&ad->cldev->dev, "command %d notify reply error\n", cmd_hdr->cmd_id); return ret; } else if (ret == 0) { dev_err(&ad->cldev->dev, "command %d notify reply timeout\n", cmd_hdr->cmd_id); ret = -ETIMEDOUT; return ret; } if (reply_hdr->response.cmd_id != cmd_hdr->cmd_id) { dev_err(&ad->cldev->dev, "reply notify mismatch, sent %d but got %d\n", cmd_hdr->cmd_id, reply_hdr->response.cmd_id); return -1; } ret = reply_hdr->response.status; if (ret) { dev_err(&ad->cldev->dev, "command %d reply wrong status = %d\n", cmd_hdr->cmd_id, ret); return -1; } return 0; } static int set_system_time(struct mei_ace_debug *ad) { struct ace_cmd cmd; size_t cmd_len; int ret; cmd_len = construct_command(ad, &cmd, SET_SYS_TIME, NULL); mutex_lock(&ad->cmd_mutex); ret = send_command_sync(ad, &cmd, cmd_len); mutex_unlock(&ad->cmd_mutex); return ret; } static ssize_t ad_time_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_ace_debug *ad = file->private_data; int ret; ret = set_system_time(ad); if (ret) return ret; return count; } static int config_log(struct mei_ace_debug *ad, struct log_config *config) { struct ace_cmd cmd; size_t cmd_len; int ret; cmd_len = construct_command(ad, &cmd, LOG_CONFIG, config); mutex_lock(&ad->cmd_mutex); ret = send_command_sync(ad, &cmd, cmd_len); mutex_unlock(&ad->cmd_mutex); return ret; } static ssize_t ad_log_config_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct mei_ace_debug *ad = file->private_data; int ret; uint8_t *buf; struct log_config config = {0}; buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); if (IS_ERR(buf)) return PTR_ERR(buf); ret = sscanf(buf, "%u %u %u %u %u %u %u", &config.aging_period, &config.fifo_period, &config.enable, &config.priority_mask[0], &config.priority_mask[1], &config.tracing_format, &config.tracing_method); if (ret != LOG_CONFIG_PARAM_COUNT) { dev_err(&ad->cldev->dev, "please input all the required parameters\n"); return -EINVAL; } ret = config_log(ad, &config); if (ret) return ret; return count; } static int get_firmware_version(struct mei_ace_debug *ad, struct firmware_version *version) { struct ace_cmd cmd; size_t cmd_len; union ace_notif_cont *cont; int ret; cmd_len = construct_command(ad, &cmd, GET_FW_VER, NULL); mutex_lock(&ad->cmd_mutex); ret = send_command_sync(ad, &cmd, cmd_len); if (!ret) { cont = &ad->reply_notif.cont; memcpy(version, &cont->version, sizeof(*version)); } mutex_unlock(&ad->cmd_mutex); return ret; } static ssize_t ad_firmware_version_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mei_ace_debug *ad = file->private_data; int ret, pos; struct firmware_version version; unsigned long addr = get_zeroed_page(GFP_KERNEL); if (!addr) return -ENOMEM; ret = get_firmware_version(ad, &version); if (ret) goto out; pos = snprintf((char *)addr, PAGE_SIZE, "firmware version: %u.%u.%u.%u\n", version.major, version.minor, version.hotfix, version.build); ret = simple_read_from_buffer(buf, count, ppos, (char *)addr, pos); out: free_page(addr); return ret; } #define AD_DFS_ADD_FILE(name) \ debugfs_create_file(#name, 0644, ad->dfs_dir, ad, \ &ad_dfs_##name##_fops) #define AD_DFS_FILE_OPS(name) \ static const struct file_operations ad_dfs_##name##_fops = { \ .read = ad_##name##_read, \ .write = ad_##name##_write, \ .open = simple_open, \ } #define AD_DFS_FILE_READ_OPS(name) \ static const struct file_operations ad_dfs_##name##_fops = { \ .read = ad_##name##_read, \ .open = simple_open, \ } #define AD_DFS_FILE_WRITE_OPS(name) \ static const struct file_operations ad_dfs_##name##_fops = { \ .write = ad_##name##_write, \ .open = simple_open, \ } AD_DFS_FILE_WRITE_OPS(time); AD_DFS_FILE_WRITE_OPS(log_config); AD_DFS_FILE_READ_OPS(firmware_version); static void handle_notify(struct mei_ace_debug *ad, struct ace_notif *notif, int len) { int ret; struct file *file; loff_t *pos; union ace_notif_hdr *hdr = ¬if->hdr; struct mei_cl_device *cldev = ad->cldev; if (hdr->notify.msg_tgt != FW_MSG || hdr->notify.type != NOTIFICATION) { dev_err(&cldev->dev, "recv wrong notification\n"); return; } switch (hdr->notify.notif_type) { case FW_READY: /* firmware ready notification sent to driver * after HECI client connected with firmware. */ dev_info(&cldev->dev, "firmware ready\n"); break; case LOG_BUFFER_STATUS: if (ad->pos < MAX_LOG_SIZE) { pos = &ad->pos; file = ad->ace_file; ret = kernel_write(file, (uint8_t *)notif + sizeof(*hdr), len - sizeof(*hdr), pos); if (ret < 0) dev_err(&cldev->dev, "error in writing log %d\n", ret); else *pos += ret; } else dev_warn(&cldev->dev, "already exceed max log size\n"); break; case MANAGEMENT_NOTIF: if (hdr->management.event_id == CTRL_NOTIF) { switch (hdr->management.request_id) { case GET_FW_VER: case LOG_CONFIG: case SET_SYS_TIME: case GET_FW_ID: memcpy(&ad->cmd_resp, notif, len); if (!completion_done(&ad->response)) complete(&ad->response); break; default: dev_err(&cldev->dev, "wrong command id(%d) notif\n", hdr->management.request_id); break; } } break; default: dev_info(&cldev->dev, "unexpected notify(%d)\n", hdr->notify.notif_type); break; } } /* callback for command response receive */ static void mei_ace_debug_rx(struct mei_cl_device *cldev) { struct mei_ace_debug *ad = mei_cldev_get_drvdata(cldev); int ret; struct ace_notif *notif; union ace_notif_hdr *hdr; ret = mei_cldev_recv(cldev, ad->recv_buf, MAX_RECV_SIZE); if (ret < 0) { dev_err(&cldev->dev, "failure in recv %d\n", ret); return; } else if (ret < sizeof(union ace_notif_hdr)) { dev_err(&cldev->dev, "recv small data %d\n", ret); return; } notif = (struct ace_notif *)ad->recv_buf; hdr = ¬if->hdr; switch (hdr->notify.rsp) { case REPLY: memcpy(&ad->reply_notif, notif, sizeof(struct ace_notif)); if (!completion_done(&ad->reply)) complete(&ad->reply); break; case NOTIF: handle_notify(ad, notif, ret); break; default: dev_err(&cldev->dev, "unexpected response(%d)\n", hdr->notify.rsp); break; } } static int mei_ace_debug_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) { struct mei_ace_debug *ad; int ret; uint32_t order = get_order(MAX_RECV_SIZE); ad = kzalloc(sizeof(struct mei_ace_debug), GFP_KERNEL); if (!ad) return -ENOMEM; ad->recv_buf = (uint8_t *)__get_free_pages(GFP_KERNEL, order); if (!ad->recv_buf) { kfree(ad); return -ENOMEM; } ad->cldev = cldev; mutex_init(&ad->cmd_mutex); init_completion(&ad->response); init_completion(&ad->reply); mei_cldev_set_drvdata(cldev, ad); ret = mei_cldev_enable(cldev); if (ret < 0) { dev_err(&cldev->dev, "couldn't enable ace debug client ret=%d\n", ret); goto err_out; } ret = mei_cldev_register_rx_cb(cldev, mei_ace_debug_rx); if (ret) { dev_err(&cldev->dev, "couldn't register ace debug rx cb ret=%d\n", ret); goto err_disable; } ad->ace_file = filp_open(ACE_LOG_FILE, O_CREAT | O_RDWR | O_LARGEFILE | O_TRUNC, 0600); if (IS_ERR(ad->ace_file)) { dev_err(&cldev->dev, "filp_open(%s) failed\n", ACE_LOG_FILE); ret = PTR_ERR(ad->ace_file); goto err_disable; } ad->pos = 0; ad->dfs_dir = debugfs_create_dir("vsc_ace", NULL); if (ad->dfs_dir) { AD_DFS_ADD_FILE(log_config); AD_DFS_ADD_FILE(time); AD_DFS_ADD_FILE(firmware_version); } return 0; err_disable: mei_cldev_disable(cldev); err_out: free_pages((unsigned long)ad->recv_buf, order); kfree(ad); return ret; } static void mei_ace_debug_remove(struct mei_cl_device *cldev) { uint32_t order = get_order(MAX_RECV_SIZE); struct mei_ace_debug *ad = mei_cldev_get_drvdata(cldev); if (!completion_done(&ad->response)) complete(&ad->response); if (!completion_done(&ad->reply)) complete(&ad->reply); mei_cldev_disable(cldev); debugfs_remove_recursive(ad->dfs_dir); filp_close(ad->ace_file, NULL); /* wait until no buffer access */ mutex_lock(&ad->cmd_mutex); mutex_unlock(&ad->cmd_mutex); free_pages((unsigned long)ad->recv_buf, order); kfree(ad); } #define MEI_UUID_ACE_DEBUG UUID_LE(0xFB285857, 0xFC24, 0x4BF3, 0xBD, \ 0x80, 0x2A, 0xBC, 0x44, 0xE3, 0xC2, 0x0B) static const struct mei_cl_device_id mei_ace_debug_tbl[] = { { MEI_ACE_DEBUG_DRIVER_NAME, MEI_UUID_ACE_DEBUG, MEI_CL_VERSION_ANY }, /* required last entry */ { } }; MODULE_DEVICE_TABLE(mei, mei_ace_debug_tbl); static struct mei_cl_driver mei_ace_debug_driver = { .id_table = mei_ace_debug_tbl, .name = MEI_ACE_DEBUG_DRIVER_NAME, .probe = mei_ace_debug_probe, .remove = mei_ace_debug_remove, }; static int __init mei_ace_debug_init(void) { int ret; ret = mei_cldev_driver_register(&mei_ace_debug_driver); if (ret) { pr_err("mei ace debug driver registration failed: %d\n", ret); return ret; } return 0; } static void __exit mei_ace_debug_exit(void) { mei_cldev_driver_unregister(&mei_ace_debug_driver); } module_init(mei_ace_debug_init); module_exit(mei_ace_debug_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Device driver for Intel VSC ACE debug client"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/mei_csi.c000066400000000000000000000226521452306460500237210ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include "intel_vsc.h" #define CSI_TIMEOUT (5 * HZ) #define MEI_CSI_DRIVER_NAME "vsc_csi" /** * Identify the command id that can be downloaded * to firmware, as well as the privacy notify id * used when processing privacy actions. * * This enumeration is local to the mei csi. */ enum csi_cmd_id { /* used to set csi ownership */ CSI_SET_OWNER, /* used to get csi ownership */ CSI_GET_OWNER, /* used to configurate mipi */ CSI_SET_MIPI_CONF, /* used to get current mipi configuration */ CSI_GET_MIPI_CONF, /* used to set csi power state */ CSI_SET_POWER_STATE, /* used to get csi power state */ CSI_GET_POWER_STATE, /* privacy notification id used when privacy state changes */ CSI_PRIVACY_NOTIF, }; enum privacy_state { PRIVACY_OFF = 0, PRIVACY_ON, }; /** * CSI command structure. */ struct csi_cmd { uint32_t cmd_id; union _cmd_param { uint32_t param; struct mipi_conf conf; } param; } __packed; /** * CSI command response structure. */ struct csi_notif { uint32_t cmd_id; int status; union _resp_cont { uint32_t cont; struct mipi_conf conf; } cont; } __packed; struct mei_csi { struct mei_cl_device *cldev; struct mutex cmd_mutex; struct csi_notif *notif; struct completion response; spinlock_t privacy_lock; void *handle; vsc_privacy_callback_t callback; }; static int mei_csi_send(struct mei_csi *csi, uint8_t *buf, size_t len) { struct csi_cmd *cmd = (struct csi_cmd *)buf; int ret; reinit_completion(&csi->response); ret = mei_cldev_send(csi->cldev, buf, len); if (ret < 0) { dev_err(&csi->cldev->dev, "send command fail %d\n", ret); return ret; } ret = wait_for_completion_killable_timeout(&csi->response, CSI_TIMEOUT); if (ret < 0) { dev_err(&csi->cldev->dev, "command %d response error\n", cmd->cmd_id); return ret; } else if (ret == 0) { dev_err(&csi->cldev->dev, "command %d response timeout\n", cmd->cmd_id); ret = -ETIMEDOUT; return ret; } ret = csi->notif->status; if (ret == -1) { dev_info(&csi->cldev->dev, "privacy on, command id = %d\n", cmd->cmd_id); ret = 0; } else if (ret) { dev_err(&csi->cldev->dev, "command %d response fail %d\n", cmd->cmd_id, ret); return ret; } if (csi->notif->cmd_id != cmd->cmd_id) { dev_err(&csi->cldev->dev, "command response id mismatch, sent %d but got %d\n", cmd->cmd_id, csi->notif->cmd_id); ret = -1; } return ret; } static int csi_set_owner(void *csi, enum csi_owner owner) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_SET_OWNER; cmd.param.param = owner; cmd_len += sizeof(cmd.param.param); mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); mutex_unlock(&p_csi->cmd_mutex); return ret; } static int csi_get_owner(void *csi, enum csi_owner *owner) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_GET_OWNER; mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); if (!ret) *owner = p_csi->notif->cont.cont; mutex_unlock(&p_csi->cmd_mutex); return ret; } static int csi_set_mipi_conf(void *csi, struct mipi_conf *conf) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_SET_MIPI_CONF; cmd.param.conf.freq = conf->freq; cmd.param.conf.lane_num = conf->lane_num; cmd_len += sizeof(cmd.param.conf); mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); mutex_unlock(&p_csi->cmd_mutex); return ret; } static int csi_get_mipi_conf(void *csi, struct mipi_conf *conf) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); struct mipi_conf *res; int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_GET_MIPI_CONF; mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); if (!ret) { res = &p_csi->notif->cont.conf; conf->freq = res->freq; conf->lane_num = res->lane_num; } mutex_unlock(&p_csi->cmd_mutex); return ret; } static int csi_set_power_state(void *csi, enum csi_power_state state) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_SET_POWER_STATE; cmd.param.param = state; cmd_len += sizeof(cmd.param.param); mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); mutex_unlock(&p_csi->cmd_mutex); return ret; } static int csi_get_power_state(void *csi, enum csi_power_state *state) { struct csi_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; struct mei_csi *p_csi = (struct mei_csi *)csi; cmd.cmd_id = CSI_GET_POWER_STATE; mutex_lock(&p_csi->cmd_mutex); ret = mei_csi_send(p_csi, (uint8_t *)&cmd, cmd_len); if (!ret) *state = p_csi->notif->cont.cont; mutex_unlock(&p_csi->cmd_mutex); return ret; } static void csi_set_privacy_callback(void *csi, vsc_privacy_callback_t callback, void *handle) { unsigned long flags; struct mei_csi *p_csi = (struct mei_csi *)csi; spin_lock_irqsave(&p_csi->privacy_lock, flags); p_csi->callback = callback; p_csi->handle = handle; spin_unlock_irqrestore(&p_csi->privacy_lock, flags); } static struct vsc_csi_ops csi_ops = { .set_owner = csi_set_owner, .get_owner = csi_get_owner, .set_mipi_conf = csi_set_mipi_conf, .get_mipi_conf = csi_get_mipi_conf, .set_power_state = csi_set_power_state, .get_power_state = csi_get_power_state, .set_privacy_callback = csi_set_privacy_callback, }; static void privacy_notify(struct mei_csi *csi, uint8_t state) { unsigned long flags; void *handle; vsc_privacy_callback_t callback; spin_lock_irqsave(&csi->privacy_lock, flags); callback = csi->callback; handle = csi->handle; spin_unlock_irqrestore(&csi->privacy_lock, flags); if (callback) callback(handle, state); } /** * callback for command response receive */ static void mei_csi_rx(struct mei_cl_device *cldev) { int ret; struct csi_notif notif = {0}; struct mei_csi *csi = mei_cldev_get_drvdata(cldev); ret = mei_cldev_recv(cldev, (uint8_t *)¬if, sizeof(struct csi_notif)); if (ret < 0) { dev_err(&cldev->dev, "failure in recv %d\n", ret); return; } switch (notif.cmd_id) { case CSI_PRIVACY_NOTIF: switch (notif.cont.cont) { case PRIVACY_ON: privacy_notify(csi, 0); dev_info(&cldev->dev, "privacy on\n"); break; case PRIVACY_OFF: privacy_notify(csi, 1); dev_info(&cldev->dev, "privacy off\n"); break; default: dev_err(&cldev->dev, "recv privacy wrong state\n"); break; } break; case CSI_SET_OWNER: case CSI_GET_OWNER: case CSI_SET_MIPI_CONF: case CSI_GET_MIPI_CONF: case CSI_SET_POWER_STATE: case CSI_GET_POWER_STATE: memcpy(csi->notif, ¬if, ret); if (!completion_done(&csi->response)) complete(&csi->response); break; default: dev_err(&cldev->dev, "recv not supported notification(%d)\n", notif.cmd_id); break; } } static int mei_csi_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) { struct mei_csi *csi; int ret; uint8_t *p; size_t csi_size = sizeof(struct mei_csi); p = kzalloc(csi_size + sizeof(struct csi_notif), GFP_KERNEL); if (!p) return -ENOMEM; csi = (struct mei_csi *)p; csi->notif = (struct csi_notif *)(p + csi_size); csi->cldev = cldev; mutex_init(&csi->cmd_mutex); init_completion(&csi->response); spin_lock_init(&csi->privacy_lock); mei_cldev_set_drvdata(cldev, csi); ret = mei_cldev_enable(cldev); if (ret < 0) { dev_err(&cldev->dev, "couldn't enable csi client ret=%d\n", ret); goto err_out; } ret = mei_cldev_register_rx_cb(cldev, mei_csi_rx); if (ret) { dev_err(&cldev->dev, "couldn't register rx event ret=%d\n", ret); goto err_disable; } vsc_register_csi(csi, &csi_ops); return 0; err_disable: mei_cldev_disable(cldev); err_out: kfree(csi); return ret; } static void mei_csi_remove(struct mei_cl_device *cldev) { struct mei_csi *csi = mei_cldev_get_drvdata(cldev); vsc_unregister_csi(); if (!completion_done(&csi->response)) complete(&csi->response); mei_cldev_disable(cldev); /* wait until no buffer access */ mutex_lock(&csi->cmd_mutex); mutex_unlock(&csi->cmd_mutex); kfree(csi); } #define MEI_UUID_CSI UUID_LE(0x92335FCF, 0x3203, 0x4472, \ 0xAF, 0x93, 0x7b, 0x44, 0x53, 0xAC, 0x29, 0xDA) static const struct mei_cl_device_id mei_csi_tbl[] = { { .uuid = MEI_UUID_CSI, .version = MEI_CL_VERSION_ANY }, /* required last entry */ { } }; MODULE_DEVICE_TABLE(mei, mei_csi_tbl); static struct mei_cl_driver mei_csi_driver = { .id_table = mei_csi_tbl, .name = MEI_CSI_DRIVER_NAME, .probe = mei_csi_probe, .remove = mei_csi_remove, }; static int __init mei_csi_init(void) { int ret; ret = mei_cldev_driver_register(&mei_csi_driver); if (ret) { pr_err("mei csi driver registration failed: %d\n", ret); return ret; } return 0; } static void __exit mei_csi_exit(void) { mei_cldev_driver_unregister(&mei_csi_driver); } module_init(mei_csi_init); module_exit(mei_csi_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Device driver for Intel VSC CSI client"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/ivsc/mei_pse.c000066400000000000000000000466251452306460500237400ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #define MEI_PSE_DRIVER_NAME "vsc_pse" #define PSE_TIMEOUT (5 * HZ) #define CONT_OFFSET offsetof(struct pse_notif, cont) #define NOTIF_HEADER_LEN 8 #define MAX_RECV_SIZE 8192 #define MAX_LOG_SIZE 0x40000000 #define EM_LOG_FILE "/var/log/vsc_em.log" #define SEM_LOG_FILE "/var/log/vsc_sem.log" #define PM_SUBSYS_MAX 2 #define PM_STATE_NAME_LEN 16 #define DEV_NUM 64 #define DEV_NAME_LEN 32 #define FORMAT "|%16.32s |%12u " #define FORMAT_TAIL "|\n" #define CONSTRUCTED_FORMAT (FORMAT FORMAT FORMAT FORMAT FORMAT_TAIL) #define TITLE "| Device Name | Block Count " #define TITLE_TAIL "|" #define CONSTRUCTED_TITLE (TITLE TITLE TITLE TITLE TITLE_TAIL) enum pse_cmd_id { LOG_ONOFF = 0, SET_WATERMARK = 1, DUMP_TRACE = 2, SET_TIMEOUT = 3, SET_LOG_LEVEL = 4, SET_TIME = 5, GET_TIME = 6, DUMP_POWER_DATA = 7, TRACE_DATA_NOTIF = 8, GET_FW_VER = 10, }; enum pm_state { ACTIVE = 0, CORE_HALT, CORE_CLK_GATE, DEEP_SLEEP, STATE_MAX, }; struct fw_version { uint32_t major; uint32_t minor; uint32_t hotfix; uint32_t build; } __packed; struct dev_info { char name[DEV_NAME_LEN]; uint32_t block_cnt; } __packed; struct dev_list { struct dev_info dev[DEV_NUM]; uint32_t dev_cnt; } __packed; struct pm_status { char name[PM_STATE_NAME_LEN]; uint64_t start; uint64_t end; uint64_t duration; uint64_t count; } __packed; struct pm_subsys { uint64_t total; struct pm_status status[STATE_MAX]; struct dev_list dev; uint16_t crc; } __packed; struct pm_data { struct pm_subsys subsys[PM_SUBSYS_MAX]; } __packed; struct pse_cmd { uint32_t cmd_id; union _cmd_param { uint32_t param; uint64_t time; } param; } __packed; struct pse_notif { uint32_t cmd_id; int8_t status; uint8_t source; int16_t size; union _resp_cont { uint64_t time; struct fw_version ver; } cont; } __packed; struct mei_pse { struct mei_cl_device *cldev; struct mutex cmd_mutex; struct pse_notif notif; struct completion response; uint8_t *recv_buf; uint8_t *pm_data; uint32_t pm_data_pos; loff_t em_pos; struct file *em_file; loff_t sem_pos; struct file *sem_file; struct dentry *dfs_dir; }; static int mei_pse_send(struct mei_pse *pse, struct pse_cmd *cmd, size_t len) { int ret; reinit_completion(&pse->response); ret = mei_cldev_send(pse->cldev, (uint8_t *)cmd, len); if (ret < 0) { dev_err(&pse->cldev->dev, "send command fail %d\n", ret); return ret; } ret = wait_for_completion_killable_timeout(&pse->response, PSE_TIMEOUT); if (ret < 0) { dev_err(&pse->cldev->dev, "command %d response error\n", cmd->cmd_id); return ret; } else if (ret == 0) { dev_err(&pse->cldev->dev, "command %d response timeout\n", cmd->cmd_id); ret = -ETIMEDOUT; return ret; } ret = pse->notif.status; if (ret) { dev_err(&pse->cldev->dev, "command %d response fail %d\n", cmd->cmd_id, ret); return ret; } if (pse->notif.cmd_id != cmd->cmd_id) { dev_err(&pse->cldev->dev, "command response id mismatch, sent %d but got %d\n", cmd->cmd_id, pse->notif.cmd_id); ret = -1; } return ret; } static int pse_log_onoff(struct mei_pse *pse, uint8_t onoff) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = LOG_ONOFF; cmd.param.param = onoff; cmd_len += sizeof(cmd.param.param); mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_log_onoff_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret; uint8_t state; ret = kstrtou8_from_user(buf, count, 0, &state); if (ret) return ret; pse_log_onoff(pse, state); return count; } static int pse_set_watermark(struct mei_pse *pse, int val) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; if (val < -1 || val > 100) { dev_err(&pse->cldev->dev, "error water mark value\n"); return -1; } cmd.cmd_id = SET_WATERMARK; cmd.param.param = val; cmd_len += sizeof(cmd.param.param); mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_watermark_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret, val; ret = kstrtoint_from_user(buf, count, 0, &val); if (ret) return ret; pse_set_watermark(pse, val); return count; } static int pse_dump_trace(struct mei_pse *pse) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = DUMP_TRACE; mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_dump_trace_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret; uint8_t val; ret = kstrtou8_from_user(buf, count, 0, &val); if (ret) return ret; if (!val) return -EINVAL; pse_dump_trace(pse); return count; } static int pse_set_timeout(struct mei_pse *pse, int val) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; if (val < -1 || val > 999) { dev_err(&pse->cldev->dev, "error timeout value\n"); return -1; } cmd.cmd_id = SET_TIMEOUT; cmd.param.param = val; cmd_len += sizeof(cmd.param.param); mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_timeout_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret, val; ret = kstrtoint_from_user(buf, count, 0, &val); if (ret) return ret; pse_set_timeout(pse, val); return count; } static int pse_set_log_level(struct mei_pse *pse, int val) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; if (val < 0 || val > 4) { dev_err(&pse->cldev->dev, "unsupported log level\n"); return -1; } cmd.cmd_id = SET_LOG_LEVEL; cmd.param.param = val; cmd_len += sizeof(cmd.param.param); mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_log_level_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret, val; ret = kstrtoint_from_user(buf, count, 0, &val); if (ret) return ret; pse_set_log_level(pse, val); return count; } static int pse_set_time(struct mei_pse *pse) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = SET_TIME; cmd.param.time = ktime_get_ns(); cmd_len += sizeof(cmd.param.time); mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_time_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret; ret = pse_set_time(pse); if (ret) return ret; return count; } static int pse_get_time(struct mei_pse *pse, uint64_t *val) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = GET_TIME; mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); if (!ret) { *val = pse->notif.cont.time; dev_info(&pse->cldev->dev, "time = (%llu) nanoseconds\n", *val); } return ret; } static ssize_t pse_time_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret, pos; uint64_t val; unsigned long addr = get_zeroed_page(GFP_KERNEL); if (!addr) return -ENOMEM; ret = pse_get_time(pse, &val); if (ret) goto out; pos = snprintf((char *)addr, PAGE_SIZE, "pse time = (%llu) nanoseconds\n", val); ret = simple_read_from_buffer(buf, count, ppos, (char *)addr, pos); out: free_page(addr); return ret; } static int pse_dump_power_data(struct mei_pse *pse) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = DUMP_POWER_DATA; mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); mutex_unlock(&pse->cmd_mutex); return ret; } static int dump_power_state_data(struct mei_pse *pse, char *addr, int pos, int len) { const char * const names[] = {"EM7D", "SEM"}; const char *title = "| power states | duration(ms) | count | percentage(%) |"; struct pm_subsys *subsys; uint64_t total_duration, duration, num, frac; int i, j; for (i = 0; i < PM_SUBSYS_MAX; i++) { subsys = &((struct pm_data *)pse->pm_data)->subsys[i]; pos += snprintf((char *)addr + pos, len - pos, "power state of %s:\n", names[i]); pos += snprintf((char *)addr + pos, len - pos, "%s\n", title); total_duration = 0; for (j = 0; j < STATE_MAX; j++) total_duration += subsys->status[j].duration; for (j = 0; j < STATE_MAX; j++) { duration = subsys->status[j].duration * 100; num = duration / total_duration; frac = (duration % total_duration * 10000000 / total_duration + 5) / 10; pos += snprintf((char *)addr + pos, len - pos, "|%13.16s |%13llu |%6llu |%7u.%06u |\n", subsys->status[j].name, subsys->status[j].duration, subsys->status[j].count, (uint32_t)num, (uint32_t)frac); } pos += snprintf((char *)addr + pos, len - pos, "\n"); } return pos; } static int dump_dev_power_data(struct mei_pse *pse, char *addr, int pos, int len) { const char * const names[] = {"EM7D", "SEM"}; struct pm_subsys *subsys; int i, j; const char *title = CONSTRUCTED_TITLE; const char *format = CONSTRUCTED_FORMAT; for (i = 0; i < PM_SUBSYS_MAX; i++) { subsys = &((struct pm_data *)pse->pm_data)->subsys[i]; pos += snprintf((char *)addr + pos, len - pos, "device list of %s:\n", names[i]); pos += snprintf((char *)addr + pos, len - pos, "%s\n", title); for (j = 0; j < subsys->dev.dev_cnt; j += 4) { switch (subsys->dev.dev_cnt - j) { case 1: pos += snprintf((char *)addr + pos, len - pos, format, subsys->dev.dev[j].name, subsys->dev.dev[j].block_cnt, "", 0, "", 0, "", 0); break; case 2: pos += snprintf((char *)addr + pos, len - pos, format, subsys->dev.dev[j].name, subsys->dev.dev[j].block_cnt, subsys->dev.dev[j+1].name, subsys->dev.dev[j+1].block_cnt, "", 0, "", 0); break; case 3: pos += snprintf((char *)addr + pos, len - pos, format, subsys->dev.dev[j].name, subsys->dev.dev[j].block_cnt, subsys->dev.dev[j+1].name, subsys->dev.dev[j+1].block_cnt, subsys->dev.dev[j+2].name, subsys->dev.dev[j+2].block_cnt, "", 0); break; default: pos += snprintf((char *)addr + pos, len - pos, format, subsys->dev.dev[j].name, subsys->dev.dev[j].block_cnt, subsys->dev.dev[j+1].name, subsys->dev.dev[j+1].block_cnt, subsys->dev.dev[j+2].name, subsys->dev.dev[j+2].block_cnt, subsys->dev.dev[j+3].name, subsys->dev.dev[j+3].block_cnt); break; } } if (i < PM_SUBSYS_MAX - 1) pos += snprintf((char *)addr + pos, len - pos, "\n"); } return pos; } static ssize_t pse_power_data_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; unsigned long addr = get_zeroed_page(GFP_KERNEL); int ret, pos = 0; if (!addr) return -ENOMEM; ret = pse_dump_power_data(pse); if (ret) goto out; pos = dump_power_state_data(pse, (char *)addr, pos, PAGE_SIZE); pos = dump_dev_power_data(pse, (char *)addr, pos, PAGE_SIZE); ret = simple_read_from_buffer(buf, count, ppos, (char *)addr, pos); out: free_page(addr); return ret; } static int pse_get_fw_ver(struct mei_pse *pse, struct fw_version *ver) { struct pse_cmd cmd; size_t cmd_len = sizeof(cmd.cmd_id); int ret; cmd.cmd_id = GET_FW_VER; mutex_lock(&pse->cmd_mutex); ret = mei_pse_send(pse, &cmd, cmd_len); if (!ret) { memcpy(ver, &pse->notif.cont.ver, sizeof(*ver)); dev_info(&pse->cldev->dev, "fw version: %u.%u.%u.%u\n", ver->major, ver->minor, ver->hotfix, ver->build); } mutex_unlock(&pse->cmd_mutex); return ret; } static ssize_t pse_fw_ver_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mei_pse *pse = file->private_data; int ret, pos; struct fw_version ver; unsigned long addr = get_zeroed_page(GFP_KERNEL); if (!addr) return -ENOMEM; ret = pse_get_fw_ver(pse, &ver); if (ret) goto out; pos = snprintf((char *)addr, PAGE_SIZE, "fw version: %u.%u.%u.%u\n", ver.major, ver.minor, ver.hotfix, ver.build); ret = simple_read_from_buffer(buf, count, ppos, (char *)addr, pos); out: free_page(addr); return ret; } #define PSE_DFS_ADD_FILE(name) \ debugfs_create_file(#name, 0644, pse->dfs_dir, pse, \ &pse_dfs_##name##_fops) #define PSE_DFS_FILE_OPS(name) \ static const struct file_operations pse_dfs_##name##_fops = { \ .read = pse_##name##_read, \ .write = pse_##name##_write, \ .open = simple_open, \ } #define PSE_DFS_FILE_READ_OPS(name) \ static const struct file_operations pse_dfs_##name##_fops = { \ .read = pse_##name##_read, \ .open = simple_open, \ } #define PSE_DFS_FILE_WRITE_OPS(name) \ static const struct file_operations pse_dfs_##name##_fops = { \ .write = pse_##name##_write, \ .open = simple_open, \ } PSE_DFS_FILE_WRITE_OPS(log_onoff); PSE_DFS_FILE_WRITE_OPS(watermark); PSE_DFS_FILE_WRITE_OPS(dump_trace); PSE_DFS_FILE_WRITE_OPS(timeout); PSE_DFS_FILE_WRITE_OPS(log_level); PSE_DFS_FILE_OPS(time); PSE_DFS_FILE_READ_OPS(fw_ver); PSE_DFS_FILE_READ_OPS(power_data); /* callback for command response receive */ static void mei_pse_rx(struct mei_cl_device *cldev) { int ret; struct pse_notif *notif; struct mei_pse *pse = mei_cldev_get_drvdata(cldev); struct file *file; loff_t *pos; ret = mei_cldev_recv(cldev, pse->recv_buf, MAX_RECV_SIZE); if (ret < 0) { dev_err(&cldev->dev, "failure in recv %d\n", ret); return; } notif = (struct pse_notif *)pse->recv_buf; switch (notif->cmd_id) { case TRACE_DATA_NOTIF: if (notif->source) { file = pse->sem_file; pos = &pse->sem_pos; } else { file = pse->em_file; pos = &pse->em_pos; } if (*pos < MAX_LOG_SIZE) { ret = kernel_write(file, pse->recv_buf + CONT_OFFSET, ret - CONT_OFFSET, pos); if (ret < 0) dev_err(&cldev->dev, "error in writing log %d\n", ret); else *pos += ret; } else dev_warn(&cldev->dev, "already exceed max log size\n"); break; case LOG_ONOFF: case SET_WATERMARK: case DUMP_TRACE: case SET_TIMEOUT: case SET_LOG_LEVEL: case SET_TIME: case GET_TIME: case GET_FW_VER: memcpy(&pse->notif, notif, ret); if (!completion_done(&pse->response)) complete(&pse->response); break; case DUMP_POWER_DATA: if (notif->status == 0) { memcpy(pse->pm_data + pse->pm_data_pos, pse->recv_buf + NOTIF_HEADER_LEN, ret - NOTIF_HEADER_LEN); pse->pm_data_pos += ret - NOTIF_HEADER_LEN; if (pse->pm_data_pos >= sizeof(struct pm_data)) { pse->pm_data_pos = 0; memcpy(&pse->notif, notif, NOTIF_HEADER_LEN); if (!completion_done(&pse->response)) complete(&pse->response); } } else { dev_err(&cldev->dev, "error in recving power data\n"); pse->pm_data_pos = 0; memcpy(&pse->notif, notif, NOTIF_HEADER_LEN); if (!completion_done(&pse->response)) complete(&pse->response); } break; default: dev_err(&cldev->dev, "recv not supported notification\n"); break; } } static int mei_pse_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) { struct mei_pse *pse; int ret; uint32_t order = get_order(MAX_RECV_SIZE); pse = kzalloc(sizeof(struct mei_pse), GFP_KERNEL); if (!pse) return -ENOMEM; pse->recv_buf = (uint8_t *)__get_free_pages(GFP_KERNEL, order); if (!pse->recv_buf) { kfree(pse); return -ENOMEM; } pse->pm_data = (uint8_t *)__get_free_pages(GFP_KERNEL, order); if (!pse->pm_data) { free_pages((unsigned long)pse->recv_buf, order); kfree(pse); return -ENOMEM; } pse->pm_data_pos = 0; pse->cldev = cldev; mutex_init(&pse->cmd_mutex); init_completion(&pse->response); mei_cldev_set_drvdata(cldev, pse); ret = mei_cldev_enable(cldev); if (ret < 0) { dev_err(&cldev->dev, "couldn't enable pse client ret=%d\n", ret); goto err_out; } ret = mei_cldev_register_rx_cb(cldev, mei_pse_rx); if (ret) { dev_err(&cldev->dev, "couldn't register rx event ret=%d\n", ret); goto err_disable; } pse->em_file = filp_open(EM_LOG_FILE, O_CREAT | O_RDWR | O_LARGEFILE | O_TRUNC, 0600); if (IS_ERR(pse->em_file)) { dev_err(&cldev->dev, "filp_open(%s) failed\n", EM_LOG_FILE); ret = PTR_ERR(pse->em_file); goto err_disable; } pse->em_pos = 0; pse->sem_file = filp_open(SEM_LOG_FILE, O_CREAT | O_RDWR | O_LARGEFILE | O_TRUNC, 0600); if (IS_ERR(pse->sem_file)) { dev_err(&cldev->dev, "filp_open(%s) failed\n", SEM_LOG_FILE); ret = PTR_ERR(pse->sem_file); goto err_close; } pse->sem_pos = 0; pse->dfs_dir = debugfs_create_dir("vsc_pse", NULL); if (pse->dfs_dir) { PSE_DFS_ADD_FILE(log_onoff); PSE_DFS_ADD_FILE(watermark); PSE_DFS_ADD_FILE(dump_trace); PSE_DFS_ADD_FILE(timeout); PSE_DFS_ADD_FILE(log_level); PSE_DFS_ADD_FILE(time); PSE_DFS_ADD_FILE(fw_ver); PSE_DFS_ADD_FILE(power_data); } return 0; err_close: filp_close(pse->em_file, NULL); err_disable: mei_cldev_disable(cldev); err_out: free_pages((unsigned long)pse->pm_data, order); free_pages((unsigned long)pse->recv_buf, order); kfree(pse); return ret; } static void mei_pse_remove(struct mei_cl_device *cldev) { struct mei_pse *pse = mei_cldev_get_drvdata(cldev); uint32_t order = get_order(MAX_RECV_SIZE); if (!completion_done(&pse->response)) complete(&pse->response); mei_cldev_disable(cldev); debugfs_remove_recursive(pse->dfs_dir); filp_close(pse->em_file, NULL); filp_close(pse->sem_file, NULL); /* wait until no buffer acccess */ mutex_lock(&pse->cmd_mutex); mutex_unlock(&pse->cmd_mutex); free_pages((unsigned long)pse->pm_data, order); free_pages((unsigned long)pse->recv_buf, order); kfree(pse); } #define MEI_UUID_PSE UUID_LE(0xD035E00C, 0x6DAE, 0x4B6D, \ 0xB4, 0x7A, 0xF8, 0x8E, 0x30, 0x2A, 0x40, 0x4E) static const struct mei_cl_device_id mei_pse_tbl[] = { { MEI_PSE_DRIVER_NAME, MEI_UUID_PSE, MEI_CL_VERSION_ANY }, /* required last entry */ { } }; MODULE_DEVICE_TABLE(mei, mei_pse_tbl); static struct mei_cl_driver mei_pse_driver = { .id_table = mei_pse_tbl, .name = MEI_PSE_DRIVER_NAME, .probe = mei_pse_probe, .remove = mei_pse_remove, }; static int __init mei_pse_init(void) { int ret; ret = mei_cldev_driver_register(&mei_pse_driver); if (ret) { pr_err("mei pse driver registration failed: %d\n", ret); return ret; } return 0; } static void __exit mei_pse_exit(void) { mei_cldev_driver_unregister(&mei_pse_driver); } module_init(mei_pse_init); module_exit(mei_pse_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Device driver for Intel VSC PSE client"); ivsc-driver-0~git202311021215.73a044d9/drivers/misc/mei/000077500000000000000000000000001452306460500217445ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/misc/mei/hw-vsc.c000066400000000000000000001230231452306460500233200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include #include #include #include #include #include #include #include #include #include #include "hw-vsc.h" static int spi_dev_xfer(struct mei_vsc_hw *hw, void *out_data, void *in_data, int len) { hw->xfer.tx_buf = out_data; hw->xfer.rx_buf = in_data; hw->xfer.len = len; spi_message_init_with_transfers(&hw->msg, &hw->xfer, 1); return spi_sync_locked(hw->spi, &hw->msg); } #define SPI_XFER_PACKET_CRC(pkt) (*(u32 *)(pkt->buf + pkt->hdr.len)) static int spi_validate_packet(struct mei_vsc_hw *hw, struct spi_xfer_packet *pkt) { u32 base_crc; u32 crc; struct spi_xfer_hdr *hdr = &pkt->hdr; base_crc = SPI_XFER_PACKET_CRC(pkt); crc = ~crc32(~0, (u8 *)pkt, sizeof(struct spi_xfer_hdr) + pkt->hdr.len); if (base_crc != crc) { dev_err(&hw->spi->dev, "%s crc error cmd %x 0x%x 0x%x\n", __func__, hdr->cmd, base_crc, crc); return -EINVAL; } if (hdr->cmd == CMD_SPI_FATAL_ERR) { dev_err(&hw->spi->dev, "receive fatal error from FW cmd %d %d %d.\nCore dump: %s\n", hdr->cmd, hdr->seq, hw->seq, (char *)pkt->buf); return -EIO; } else if (hdr->cmd == CMD_SPI_NACK || hdr->cmd == CMD_SPI_BUSY || hdr->seq != hw->seq) { dev_err(&hw->spi->dev, "receive error from FW cmd %d %d %d\n", hdr->cmd, hdr->seq, hw->seq); return -EAGAIN; } return 0; } static inline bool spi_rom_xfer_asserted(struct mei_vsc_hw *hw) { return gpiod_get_value_cansleep(hw->wakeuphost); } static inline bool spi_xfer_asserted(struct mei_vsc_hw *hw) { return atomic_read(&hw->lock_cnt) > 0 && gpiod_get_value_cansleep(hw->wakeuphost); } static void spi_xfer_lock(struct mei_vsc_hw *hw) { gpiod_set_value_cansleep(hw->wakeupfw, 0); } static void spi_xfer_unlock(struct mei_vsc_hw *hw) { atomic_dec_if_positive(&hw->lock_cnt); gpiod_set_value_cansleep(hw->wakeupfw, 1); } static bool spi_xfer_locked(struct mei_vsc_hw *hw) { return !gpiod_get_value_cansleep(hw->wakeupfw); } static bool spi_need_read(struct mei_vsc_hw *hw) { return spi_xfer_asserted(hw) && !spi_xfer_locked(hw); } #define WAIT_FW_ASSERTED_TIMEOUT (2 * HZ) static int spi_xfer_wait_asserted(struct mei_vsc_hw *hw) { wait_event_timeout(hw->xfer_wait, spi_xfer_asserted(hw), WAIT_FW_ASSERTED_TIMEOUT); dev_dbg(&hw->spi->dev, "%s %d %d %d\n", __func__, atomic_read(&hw->lock_cnt), gpiod_get_value_cansleep(hw->wakeupfw), gpiod_get_value_cansleep(hw->wakeuphost)); if (!spi_xfer_asserted(hw)) return -ETIME; else return 0; } static int spi_wakeup_request(struct mei_vsc_hw *hw) { /* wakeup spi slave and wait for response */ spi_xfer_lock(hw); return spi_xfer_wait_asserted(hw); } static void spi_wakeup_release(struct mei_vsc_hw *hw) { return spi_xfer_unlock(hw); } static int find_sync_byte(u8 *buf, int len) { int i; for (i = 0; i < len; i++) if (buf[i] == PACKET_SYNC) return i; return -1; } #define PACKET_PADDING_SIZE 1 #define MAX_XFER_COUNT 5 static int mei_vsc_xfer_internal(struct mei_vsc_hw *hw, struct spi_xfer_packet *pkt, struct spi_xfer_packet *ack_pkt) { u8 *rx_buf = hw->rx_buf1; u8 *tx_buf = hw->tx_buf1; int next_xfer_len = PACKET_SIZE(pkt) + XFER_TIMEOUT_BYTES; int offset = 0; bool synced = false; int len; int count_down = MAX_XFER_COUNT; int ret = 0; int i; dev_dbg(&hw->spi->dev, "spi tx pkt begin: %s %d %d\n", __func__, spi_xfer_asserted(hw), gpiod_get_value_cansleep(hw->wakeupfw)); memcpy(tx_buf, pkt, PACKET_SIZE(pkt)); memset(rx_buf, 0, MAX_XFER_BUFFER_SIZE); do { dev_dbg(&hw->spi->dev, "spi tx pkt partial ing: %s %d %d %d %d\n", __func__, spi_xfer_asserted(hw), gpiod_get_value_cansleep(hw->wakeupfw), next_xfer_len, synced); count_down--; ret = spi_dev_xfer(hw, tx_buf, rx_buf, next_xfer_len); if (ret) return ret; memset(tx_buf, 0, MAX_XFER_BUFFER_SIZE); if (!synced) { i = find_sync_byte(rx_buf, next_xfer_len); if (i >= 0) { synced = true; len = next_xfer_len - i; } else { continue; } } else { i = 0; len = min_t(int, next_xfer_len, sizeof(*ack_pkt) - offset); } memcpy(&ack_pkt[offset], &rx_buf[i], len); offset += len; if (offset >= sizeof(ack_pkt->hdr)) next_xfer_len = PACKET_SIZE(ack_pkt) - offset + PACKET_PADDING_SIZE; } while (next_xfer_len > 0 && count_down > 0); dev_dbg(&hw->spi->dev, "spi tx pkt done: %s %d %d cmd %d %d %d %d\n", __func__, next_xfer_len, count_down, ack_pkt->hdr.sync, ack_pkt->hdr.cmd, ack_pkt->hdr.len, ack_pkt->hdr.seq); if (next_xfer_len > 0) return -EAGAIN; return spi_validate_packet(hw, ack_pkt); } static int mei_vsc_xfer(struct mei_vsc_hw *hw, u8 cmd, void *tx, u32 tx_len, void *rx, int rx_max_len, u32 *rx_len) { struct spi_xfer_packet *pkt; struct spi_xfer_packet *ack_pkt; u32 *crc; int ret; if (!tx || !rx || tx_len > MAX_SPI_MSG_SIZE) return -EINVAL; if (rx_len) *rx_len = 0; pkt = kzalloc(sizeof(*pkt) + sizeof(*ack_pkt), GFP_KERNEL); ack_pkt = pkt + 1; if (!pkt || !ack_pkt) return -ENOMEM; pkt->hdr.sync = PACKET_SYNC; pkt->hdr.cmd = cmd; pkt->hdr.seq = ++hw->seq; pkt->hdr.len = tx_len; memcpy(pkt->buf, tx, tx_len); crc = (u32 *)(pkt->buf + tx_len); *crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt->hdr) + tx_len); mutex_lock(&hw->mutex); ret = spi_wakeup_request(hw); if (ret) { dev_err(&hw->spi->dev, "wakeup vsc FW failed\n"); goto out; } ret = mei_vsc_xfer_internal(hw, pkt, ack_pkt); if (ret) goto out; if (ack_pkt->hdr.len > 0) { int len; len = (ack_pkt->hdr.len < rx_max_len) ? ack_pkt->hdr.len : rx_max_len; memcpy(rx, ack_pkt->buf, len); if (rx_len) *rx_len = len; } out: spi_wakeup_release(hw); mutex_unlock(&hw->mutex); kfree(pkt); return ret; } static int mei_vsc_read_raw(struct mei_vsc_hw *hw, u8 *buf, u32 max_len, u32 *len) { struct host_timestamp ts = { 0 }; ts.realtime = ktime_to_ns(ktime_get_real()); ts.boottime = ktime_to_ns(ktime_get_boottime()); return mei_vsc_xfer(hw, CMD_SPI_READ, &ts, sizeof(ts), buf, max_len, len); } static int mei_vsc_write_raw(struct mei_vsc_hw *hw, u8 *buf, u32 len) { u8 status = 0; int rx_len; return mei_vsc_xfer(hw, CMD_SPI_WRITE, buf, len, &status, sizeof(status), &rx_len); } #define LOADER_XFER_RETRY_COUNT 25 static int spi_rom_dev_xfer(struct mei_vsc_hw *hw, void *out_data, void *in_data, int len) { int ret; int i; u32 *tmp = out_data; int retry = 0; if (len % 4 != 0) return -EINVAL; for (i = 0; i < len / 4; i++) tmp[i] = ___constant_swab32(tmp[i]); mutex_lock(&hw->mutex); while (retry < LOADER_XFER_RETRY_COUNT) { if (!spi_rom_xfer_asserted(hw)) break; msleep(20); retry++; } if (retry >= LOADER_XFER_RETRY_COUNT) { dev_err(&hw->spi->dev, "%s retry %d times gpio %d\n", __func__, retry, spi_rom_xfer_asserted(hw)); mutex_unlock(&hw->mutex); return -EAGAIN; } ret = spi_dev_xfer(hw, out_data, in_data, len); mutex_unlock(&hw->mutex); if (!in_data || ret) return ret; tmp = in_data; for (i = 0; i < len / 4; i++) tmp[i] = ___constant_swab32(tmp[i]); return 0; } #define VSC_RESET_PIN_TOGGLE_INTERVAL 20 #define VSC_ROM_BOOTUP_DELAY_TIME 10 static int vsc_reset(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); gpiod_set_value_cansleep(hw->resetfw, 1); msleep(VSC_RESET_PIN_TOGGLE_INTERVAL); gpiod_set_value_cansleep(hw->resetfw, 0); msleep(VSC_RESET_PIN_TOGGLE_INTERVAL); gpiod_set_value_cansleep(hw->resetfw, 1); msleep(VSC_ROM_BOOTUP_DELAY_TIME); /* set default host wake pin to 1, which try to avoid unexpected host irq interrupt */ gpiod_set_value_cansleep(hw->wakeupfw, 1); return 0; } /* %s is sensor name, need to be get and format in runtime */ static char *fw_name_template[][3] = { { "vsc/soc_a1/ivsc_fw_a1.bin", "vsc/soc_a1/ivsc_pkg_%s_0_a1.bin", "vsc/soc_a1/ivsc_skucfg_%s_0_1_a1.bin", }, { "vsc/soc_a1_prod/ivsc_fw_a1_prod.bin", "vsc/soc_a1_prod/ivsc_pkg_%s_0_a1_prod.bin", "vsc/soc_a1_prod/ivsc_skucfg_%s_0_1_a1_prod.bin", }, }; static int check_silicon(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_rom_master_frame *frame = (struct vsc_rom_master_frame *)hw->fw.tx_buf; struct vsc_rom_slave_token *token = (struct vsc_rom_slave_token *)hw->fw.rx_buf; int ret; u32 efuse1; u32 strap; dev_dbg(dev->dev, "%s size %zu %zu\n", __func__, sizeof(*frame), sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DUMP_MEM; frame->data.dump_mem.addr = EFUSE1_ADDR; frame->data.dump_mem.len = 4; ret = spi_rom_dev_xfer(hw, frame, token, VSC_ROM_SPI_PKG_SIZE); if (ret || token->token == VSC_TOKEN_ERROR) { dev_err(dev->dev, "%s %d %d %d\n", __func__, __LINE__, ret, token->token); return ret; } memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_RESERVED; ret = spi_rom_dev_xfer(hw, frame, token, VSC_ROM_SPI_PKG_SIZE); if (ret || token->token == VSC_TOKEN_ERROR || token->token != VSC_TOKEN_DUMP_RESP) { dev_err(dev->dev, "%s %d %d %d\n", __func__, __LINE__, ret, token->token); return -EIO; } efuse1 = *(u32 *)token->payload; dev_dbg(dev->dev, "%s efuse1=%d\n", __func__, efuse1); /* to check the silicon main and sub version */ hw->fw.main_ver = (efuse1 >> SI_MAINSTEPPING_VERSION_OFFSET) & SI_MAINSTEPPING_VERSION_MASK; hw->fw.sub_ver = (efuse1 >> SI_SUBSTEPPING_VERSION_OFFSET) & SI_SUBSTEPPING_VERSION_MASK; if (hw->fw.main_ver != SI_MAINSTEPPING_VERSION_A) { dev_err(dev->dev, "%s: silicon main version error(%d)\n", __func__, hw->fw.main_ver); return -EINVAL; } if (hw->fw.sub_ver != SI_SUBSTEPPING_VERSION_0 && hw->fw.sub_ver != SI_SUBSTEPPING_VERSION_1) { dev_dbg(dev->dev, "%s: silicon sub version error(%d)\n", __func__, hw->fw.sub_ver); return -EINVAL; } /* to get the silicon strap key: debug or production ? */ memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DUMP_MEM; frame->data.dump_mem.addr = STRAP_ADDR; frame->data.dump_mem.len = 4; ret = spi_rom_dev_xfer(hw, frame, token, VSC_ROM_SPI_PKG_SIZE); if (ret || token->token == VSC_TOKEN_ERROR) { dev_err(dev->dev, "%s: transfer failed or invalid token\n", __func__); return ret; } frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_RESERVED; ret = spi_rom_dev_xfer(hw, frame, token, VSC_ROM_SPI_PKG_SIZE); if (ret || token->token == VSC_TOKEN_ERROR || token->token != VSC_TOKEN_DUMP_RESP) { dev_err(dev->dev, "%s: transfer failed or invalid token-> (token = %d)\n", __func__, token->token); return -EINVAL; } dev_dbg(dev->dev, "%s: getting the memory(0x%0x), step 2 payload: 0x%0x\n", __func__, STRAP_ADDR, *(u32 *)token->payload); strap = *(u32 *)token->payload; dev_dbg(dev->dev, "%s: strap = 0x%x\n", __func__, strap); /* to check the silicon strap key source */ hw->fw.key_src = (strap >> SI_STRAP_KEY_SRC_OFFSET) & SI_STRAP_KEY_SRC_MASK; dev_dbg(dev->dev, "%s: silicon version check done: %s%s\n", __func__, hw->fw.sub_ver == SI_SUBSTEPPING_VERSION_0 ? "A0" : "A1", hw->fw.key_src == SI_STRAP_KEY_SRC_DEBUG ? "" : "-prod"); if (hw->fw.sub_ver == SI_SUBSTEPPING_VERSION_1) { if (hw->fw.key_src == SI_STRAP_KEY_SRC_DEBUG) { snprintf(hw->fw.fw_file_name, sizeof(hw->fw.fw_file_name), fw_name_template[0][0]); snprintf(hw->fw.sensor_file_name, sizeof(hw->fw.sensor_file_name), fw_name_template[0][1], hw->cam_sensor_name); snprintf(hw->fw.sku_cnf_file_name, sizeof(hw->fw.sku_cnf_file_name), fw_name_template[0][2], hw->cam_sensor_name); } else { snprintf(hw->fw.fw_file_name, sizeof(hw->fw.fw_file_name), fw_name_template[1][0]); snprintf(hw->fw.sensor_file_name, sizeof(hw->fw.sensor_file_name), fw_name_template[1][1], hw->cam_sensor_name); snprintf(hw->fw.sku_cnf_file_name, sizeof(hw->fw.sku_cnf_file_name), fw_name_template[1][2], hw->cam_sensor_name); } } return 0; } static int parse_main_fw(struct mei_device *dev, const struct firmware *fw) { struct bootloader_sign *bootloader = NULL; struct firmware_sign *arc_sem = NULL; struct firmware_sign *em7d = NULL; struct firmware_sign *ace_run = NULL; struct firmware_sign *ace_vis = NULL; struct firmware_sign *ace_conf = NULL; struct vsc_boot_img *img = (struct vsc_boot_img *)fw->data; struct mei_vsc_hw *hw = to_vsc_hw(dev); struct manifest *man = NULL; struct fragment *bootl_frag = &hw->fw.frags[BOOT_IMAGE_TYPE]; struct fragment *arcsem_frag = &hw->fw.frags[ARC_SEM_IMG_TYPE]; struct fragment *acer_frag = &hw->fw.frags[ACER_IMG_TYPE]; struct fragment *acev_frag = &hw->fw.frags[ACEV_IMG_TYPE]; struct fragment *acec_frag = &hw->fw.frags[ACEC_IMG_TYPE]; struct fragment *em7d_frag = &hw->fw.frags[EM7D_IMG_TYPE]; struct firmware_sign *firmwares[IMG_CNT_MAX]; int i; if (!img || img->magic != VSC_FILE_MAGIC) { dev_err(dev->dev, "image file error\n"); return -EINVAL; } if (img->image_count < IMG_BOOT_ARC_EM7D || img->image_count > IMG_CNT_MAX) { dev_err(dev->dev, "%s: image count error: image_count=0x%x\n", __func__, img->image_count); return -EINVAL; } dev_dbg(dev->dev, "%s: img->image_count=%d\n", __func__, img->image_count); /* only two lower bytes are used */ hw->fw.fw_option = img->option & 0xFFFF; /* image not include bootloader */ hw->fw.fw_cnt = img->image_count - 1; bootloader = (struct bootloader_sign *)(img->image_loc + img->image_count); if ((u8 *)bootloader > (fw->data + fw->size)) return -EINVAL; if (bootloader->magic != VSC_FW_MAGIC) { dev_err(dev->dev, "bootloader signed magic error! magic number 0x%08x, image size 0x%08x\n", bootloader->magic, bootloader->image_size); return -EINVAL; } man = (struct manifest *)((char *)bootloader->image + bootloader->image_size - SIG_SIZE - sizeof(struct manifest) - CSSHEADER_SIZE); if (man->svn == MAX_SVN_VALUE) hw->fw.svn = MAX_SVN_VALUE; else if (hw->fw.svn == 0) hw->fw.svn = man->svn; dev_dbg(dev->dev, "%s: svn: 0x%08X", __func__, hw->fw.svn); /* currently only support silicon versoin A0 | A1 */ if ((hw->fw.sub_ver == SI_SUBSTEPPING_VERSION_0 && hw->fw.svn != MAX_SVN_VALUE) || (hw->fw.sub_ver == SI_SUBSTEPPING_VERSION_1 && hw->fw.svn == MAX_SVN_VALUE)) { dev_err(dev->dev, "silicon version and image svn not matched(A%s:0x%x)\n", hw->fw.sub_ver == SI_SUBSTEPPING_VERSION_0 ? "0" : "1", hw->fw.svn); return -EINVAL; } for (i = 0; i < img->image_count - 1; i++) { if (i == 0) { firmwares[i] = (struct firmware_sign *)(bootloader->image + bootloader->image_size); dev_dbg(dev->dev, "FW (%d/%d) magic number 0x%08x, image size 0x%08x\n", i, img->image_count, firmwares[i]->magic, firmwares[i]->image_size); continue; } firmwares[i] = (struct firmware_sign *)(firmwares[i - 1]->image + firmwares[i - 1]->image_size); if ((u8 *)firmwares[i] > fw->data + fw->size) return -EINVAL; dev_dbg(dev->dev, "FW (%d/%d) magic number 0x%08x, image size 0x%08x\n", i, img->image_count, firmwares[i]->magic, firmwares[i]->image_size); if (firmwares[i]->magic != VSC_FW_MAGIC) { dev_err(dev->dev, "FW (%d/%d) magic error! magic number 0x%08x, image size 0x%08x\n", i, img->image_count, firmwares[i]->magic, firmwares[i]->image_size); return -EINVAL; } } arc_sem = firmwares[0]; if (img->image_count >= IMG_BOOT_ARC_EM7D) em7d = firmwares[img->image_count - 2]; if (img->image_count >= IMG_BOOT_ARC_ACER_EM7D) ace_run = firmwares[1]; if (img->image_count >= IMG_BOOT_ARC_ACER_ACEV_EM7D) ace_vis = firmwares[2]; if (img->image_count >= IMG_BOOT_ARC_ACER_ACEV_ACECNF_EM7D) ace_conf = firmwares[3]; bootl_frag->data = bootloader->image; bootl_frag->size = bootloader->image_size; bootl_frag->location = img->image_loc[0]; if (!bootl_frag->location) return -EINVAL; if (!arc_sem) return -EINVAL; arcsem_frag->data = arc_sem->image; arcsem_frag->size = arc_sem->image_size; arcsem_frag->location = img->image_loc[1]; if (!arcsem_frag->location) return -EINVAL; if (ace_run) { acer_frag->data = ace_run->image; acer_frag->size = ace_run->image_size; acer_frag->location = img->image_loc[2]; if (!acer_frag->location) return -EINVAL; if (ace_vis) { acev_frag->data = ace_vis->image; acev_frag->size = ace_vis->image_size; /* Align to 4K boundary */ acev_frag->location = ((acer_frag->location + acer_frag->size + 0xFFF) & ~(0xFFF)); if (img->image_loc[3] && acer_frag->location != img->image_loc[3]) { dev_err(dev->dev, "ACE vision image location error. img->image_loc[3] = 0x%x, calculated is 0x%x\n", img->image_loc[3], acev_frag->location); /* when location mismatch, use the one from image file. */ acev_frag->location = img->image_loc[3]; } } if (ace_conf) { acec_frag->data = ace_conf->image; acec_frag->size = ace_conf->image_size; /* Align to 4K boundary */ acec_frag->location = ((acev_frag->location + acev_frag->size + 0xFFF) & ~(0xFFF)); if (img->image_loc[4] && acec_frag->location != img->image_loc[4]) { dev_err(dev->dev, "ACE vision image location error. img->image_loc[4] = 0x%x, calculated is 0x%x\n", img->image_loc[4], acec_frag->location); /* when location mismatch, use the one from image file. */ acec_frag->location = img->image_loc[4]; } } } em7d_frag->data = em7d->image; em7d_frag->size = em7d->image_size; /* em7d is the last firmware */ em7d_frag->location = img->image_loc[img->image_count - 1]; if (!em7d_frag->location) return -EINVAL; return 0; } static int parse_sensor_fw(struct mei_device *dev, const struct firmware *fw) { struct firmware_sign *ace_vis = NULL; struct firmware_sign *ace_conf = NULL; struct vsc_boot_img *img = (struct vsc_boot_img *)fw->data; struct mei_vsc_hw *hw = to_vsc_hw(dev); struct fragment *acer_frag = &hw->fw.frags[ACER_IMG_TYPE]; struct fragment *acev_frag = &hw->fw.frags[ACEV_IMG_TYPE]; struct fragment *acec_frag = &hw->fw.frags[ACEC_IMG_TYPE]; if (!img || img->magic != VSC_FILE_MAGIC || img->image_count < IMG_ACEV_ACECNF || img->image_count > IMG_CNT_MAX) return -EINVAL; dev_dbg(dev->dev, "%s: img->image_count=%d\n", __func__, img->image_count); hw->fw.fw_cnt += img->image_count; if (hw->fw.fw_cnt > IMG_CNT_MAX) return -EINVAL; ace_vis = (struct firmware_sign *)(img->image_loc + img->image_count); ace_conf = (struct firmware_sign *)(ace_vis->image + ace_vis->image_size); dev_dbg(dev->dev, "ACE vision signed magic number 0x%08x, image size 0x%08x\n", ace_vis->magic, ace_vis->image_size); if (ace_vis->magic != VSC_FW_MAGIC) { dev_err(dev->dev, "ACE vision signed magic error! magic number 0x%08x, image size 0x%08x\n", ace_vis->magic, ace_vis->image_size); return -EINVAL; } acev_frag->data = ace_vis->image; acev_frag->size = ace_vis->image_size; /* Align to 4K boundary */ acev_frag->location = ((acer_frag->location + acer_frag->size + 0xFFF) & ~(0xFFF)); if (img->image_loc[0] && acer_frag->location != img->image_loc[0]) { dev_err(dev->dev, "ACE vision image location error. img->image_loc[0] = 0x%x, calculated is 0x%x\n", img->image_loc[0], acev_frag->location); /* when location mismatch, use the one from image file. */ acev_frag->location = img->image_loc[0]; } dev_dbg(dev->dev, "ACE config signed magic number 0x%08x, image size 0x%08x\n", ace_conf->magic, ace_conf->image_size); if (ace_conf->magic != VSC_FW_MAGIC) { dev_err(dev->dev, "ACE config signed magic error! magic number 0x%08x, image size 0x%08x\n", ace_conf->magic, ace_conf->image_size); return -EINVAL; } acec_frag->data = ace_conf->image; acec_frag->size = ace_conf->image_size; /* Align to 4K boundary */ acec_frag->location = ((acev_frag->location + acev_frag->size + 0xFFF) & ~(0xFFF)); if (img->image_loc[1] && acec_frag->location != img->image_loc[1]) { dev_err(dev->dev, "ACE vision image location error. img->image_loc[1] = 0x%x, calculated is 0x%x\n", img->image_loc[1], acec_frag->location); /* when location mismatch, use the one from image file. */ acec_frag->location = img->image_loc[1]; } return 0; } static int parse_sku_cnf_fw(struct mei_device *dev, const struct firmware *fw) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct fragment *skucnf_frag = &hw->fw.frags[SKU_CONF_TYPE]; if (fw->size <= sizeof(u32)) return -EINVAL; skucnf_frag->data = fw->data; skucnf_frag->size = *((u32 *)fw->data) + sizeof(u32); /* SKU config use fixed location */ skucnf_frag->location = SKU_CONFIG_LOC; if (fw->size != skucnf_frag->size || fw->size > SKU_MAX_SIZE) { dev_err(dev->dev, "sku config file size is not config size + 4, config size = 0x%x, file size=0x%zx\n", skucnf_frag->size, fw->size); return -EINVAL; } return 0; } static u32 sum_CRC(void *data, int size) { int i; u32 crc = 0; for (i = 0; i < size; i++) crc += *((u8 *)data + i); return crc; } static int load_boot(struct mei_device *dev, const void *data, int size) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_rom_master_frame *frame = (struct vsc_rom_master_frame *)hw->fw.tx_buf; struct vsc_rom_slave_token *token = (struct vsc_rom_slave_token *)hw->fw.rx_buf; const u8 *ptr = data; u32 remain; int ret; if (!data || !size) return -EINVAL; dev_dbg(dev->dev, "==== %s: image payload size : %d\n", __func__, size); remain = size; while (remain > 0) { u32 max_len = sizeof(frame->data.dl_cont.payload); u32 len = remain > max_len ? max_len : remain; memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DL_CONT; frame->data.dl_cont.len = (u16)len; frame->data.dl_cont.end_flag = (remain == len ? 1 : 0); memcpy(frame->data.dl_cont.payload, ptr, len); ret = spi_rom_dev_xfer(hw, frame, NULL, VSC_ROM_SPI_PKG_SIZE); if (ret) { dev_err(dev->dev, "%s: transfer failed\n", __func__); break; } ptr += len; remain -= len; } return ret; } static int load_bootloader(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_rom_master_frame *frame = (struct vsc_rom_master_frame *)hw->fw.tx_buf; struct vsc_rom_slave_token *token = (struct vsc_rom_slave_token *)hw->fw.rx_buf; struct fragment *fragment = &hw->fw.frags[BOOT_IMAGE_TYPE]; int ret; if (!fragment->size) return -EINVAL; dev_dbg(dev->dev, "verify bootloader token ...\n"); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_QUERY; ret = spi_rom_dev_xfer(hw, frame, token, VSC_ROM_SPI_PKG_SIZE); if (ret) return ret; if (token->token != VSC_TOKEN_BOOTLOADER_REQ && token->token != VSC_TOKEN_DUMP_RESP) { dev_err(dev->dev, "failed to load bootloader, invalid token 0x%x\n", token->token); return -EINVAL; } dev_dbg(dev->dev, "bootloader token has been verified\n"); dev_dbg(dev->dev, "start download, image len: %u ...\n", fragment->size); memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DL_START; frame->data.dl_start.img_type = IMG_BOOTLOADER; frame->data.dl_start.img_len = fragment->size; frame->data.dl_start.img_loc = fragment->location; frame->data.dl_start.option = (u16)hw->fw.fw_option; frame->data.dl_start.crc = sum_CRC(frame, (int)offsetof(struct vsc_rom_master_frame, data.dl_start.crc)); ret = spi_rom_dev_xfer(hw, frame, NULL, VSC_ROM_SPI_PKG_SIZE); if (ret) return ret; dev_dbg(dev->dev, "load bootloader payload ...\n"); ret = load_boot(dev, fragment->data, fragment->size); if (ret) dev_err(dev->dev, "failed to load bootloader, err : 0x%0x\n", ret); return ret; } static int load_fw_bin(struct mei_device *dev, const void *data, int size) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_master_frame_fw_cont *frame = (struct vsc_master_frame_fw_cont *)hw->fw.tx_buf; struct vsc_bol_slave_token *token = (struct vsc_bol_slave_token *)hw->fw.rx_buf; const u8 *ptr = data; int ret; u32 remain; if (!data || !size) return -EINVAL; dev_dbg(dev->dev, "==== %s: image payload size : %d\n", __func__, size); remain = size; while (remain > 0) { u32 len = remain > FW_SPI_PKG_SIZE ? FW_SPI_PKG_SIZE : remain; memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); memcpy(frame->payload, ptr, len); ret = spi_rom_dev_xfer(hw, frame, NULL, FW_SPI_PKG_SIZE); if (ret) { dev_err(dev->dev, "transfer failed\n"); break; } ptr += len; remain -= len; } return ret; } static int load_fw_frag(struct mei_device *dev, struct fragment *frag, int type) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_fw_master_frame *frame = (struct vsc_fw_master_frame *)hw->fw.tx_buf; struct vsc_bol_slave_token *token = (struct vsc_bol_slave_token *)hw->rx_buf; int ret; dev_dbg(dev->dev, "start download firmware type %d ... loc:0x%08x, size:0x%08x\n", type, frag->location, frag->size); memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DL_START; frame->data.dl_start.img_type = type; frame->data.dl_start.img_len = frag->size; frame->data.dl_start.img_loc = frag->location; frame->data.dl_start.option = (u16)hw->fw.fw_option; frame->data.dl_start.crc = sum_CRC( frame, offsetof(struct vsc_fw_master_frame, data.dl_start.crc)); ret = spi_rom_dev_xfer(hw, frame, NULL, FW_SPI_PKG_SIZE); if (ret) return ret; return load_fw_bin(dev, frag->data, frag->size); } static int load_fw(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct vsc_fw_master_frame *frame = (struct vsc_fw_master_frame *)hw->fw.tx_buf; struct vsc_bol_slave_token *token = (struct vsc_bol_slave_token *)hw->rx_buf; struct fragment *arcsem_frag = NULL; struct fragment *em7d_frag = NULL; struct fragment *acer_frag = NULL; struct fragment *acev_frag = NULL; struct fragment *acec_frag = NULL; struct fragment *skucnf_frag = NULL; int index = 0; int ret; if (hw->fw.frags[ARC_SEM_IMG_TYPE].size > 0) arcsem_frag = &hw->fw.frags[ARC_SEM_IMG_TYPE]; if (hw->fw.frags[EM7D_IMG_TYPE].size > 0) em7d_frag = &hw->fw.frags[EM7D_IMG_TYPE]; if (hw->fw.frags[ACER_IMG_TYPE].size > 0) acer_frag = &hw->fw.frags[ACER_IMG_TYPE]; if (hw->fw.frags[ACEV_IMG_TYPE].size > 0) acev_frag = &hw->fw.frags[ACEV_IMG_TYPE]; if (hw->fw.frags[ACEC_IMG_TYPE].size > 0) acec_frag = &hw->fw.frags[ACEC_IMG_TYPE]; if (hw->fw.frags[SKU_CONF_TYPE].size > 0) skucnf_frag = &hw->fw.frags[SKU_CONF_TYPE]; if (!arcsem_frag || !em7d_frag) { dev_err(dev->dev, "invalid image or signature data\n"); return -EINVAL; } /* send dl_set frame */ dev_dbg(dev->dev, "send dl_set frame ...\n"); memset(frame, 0, sizeof(*frame)); memset(token, 0, sizeof(*token)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_CMD_DL_SET; frame->data.dl_set.option = (u16)hw->fw.fw_option; frame->data.dl_set.img_cnt = (u8)hw->fw.fw_cnt; dev_dbg(dev->dev, "%s: img_cnt = %d ...\n", __func__, frame->data.dl_set.img_cnt); frame->data.dl_set.payload[index++] = arcsem_frag->location; frame->data.dl_set.payload[index++] = arcsem_frag->size; if (acer_frag) { frame->data.dl_set.payload[index++] = acer_frag->location; frame->data.dl_set.payload[index++] = acer_frag->size; if (acev_frag) { frame->data.dl_set.payload[index++] = acev_frag->location; frame->data.dl_set.payload[index++] = acev_frag->size; } if (acec_frag) { frame->data.dl_set.payload[index++] = acec_frag->location; frame->data.dl_set.payload[index++] = acec_frag->size; } } frame->data.dl_set.payload[index++] = em7d_frag->location; frame->data.dl_set.payload[index++] = em7d_frag->size; frame->data.dl_set.payload[hw->fw.fw_cnt * 2] = sum_CRC( frame, (int)offsetof(struct vsc_fw_master_frame, data.dl_set.payload[hw->fw.fw_cnt * 2])); ret = spi_rom_dev_xfer(hw, frame, NULL, FW_SPI_PKG_SIZE); if (ret) return ret; /* load ARC-SEM FW image */ if (arcsem_frag) { ret = load_fw_frag(dev, arcsem_frag, IMG_ARCSEM); if (ret) return ret; } /* load ACE FW image */ if (acer_frag) { ret = load_fw_frag(dev, acer_frag, IMG_ACE_RUNTIME); if (ret) return ret; } if (acev_frag) { ret = load_fw_frag(dev, acev_frag, IMG_ACE_VISION); if (ret) return ret; } if (acec_frag) { ret = load_fw_frag(dev, acec_frag, IMG_ACE_CONFIG); if (ret) return ret; } /* load EM7D FW image */ if (em7d_frag) { ret = load_fw_frag(dev, em7d_frag, IMG_EM7D); if (ret) return ret; } /* load SKU Config */ if (skucnf_frag) { ret = load_fw_frag(dev, skucnf_frag, IMG_SKU_CONFIG); if (ret) return ret; } memset(frame, 0, sizeof(*frame)); frame->magic = VSC_MAGIC_NUM; frame->cmd = VSC_TOKEN_CAM_BOOT; frame->data.boot.check_sum = sum_CRC( frame, offsetof(struct vsc_fw_master_frame, data.dl_start.crc)); ret = spi_rom_dev_xfer(hw, frame, NULL, FW_SPI_PKG_SIZE); if (ret) dev_err(dev->dev, "failed to boot fw, err : 0x%x\n", ret); return ret; } static int init_hw(struct mei_device *dev) { int ret; const struct firmware *fw = NULL; const struct firmware *sensor_fw = NULL; const struct firmware *sku_cnf_fw = NULL; struct mei_vsc_hw *hw = to_vsc_hw(dev); ret = check_silicon(dev); if (ret) return ret; dev_dbg(dev->dev, "%s: FW files. Firmware Boot File: %s, Sensor FW File: %s, Sku Config File: %s\n", __func__, hw->fw.fw_file_name, hw->fw.sensor_file_name, hw->fw.sku_cnf_file_name); ret = request_firmware(&fw, hw->fw.fw_file_name, dev->dev); if (ret < 0 || !fw) { dev_err(&hw->spi->dev, "file not found %s\n", hw->fw.fw_file_name); return ret; } ret = parse_main_fw(dev, fw); if (ret || !fw) { dev_err(&hw->spi->dev, "parse fw %s failed\n", hw->fw.fw_file_name); goto release; } if (hw->fw.fw_cnt < IMG_ARC_ACER_ACEV_ACECNF_EM7D) { ret = request_firmware(&sensor_fw, hw->fw.sensor_file_name, dev->dev); if (ret < 0 || !sensor_fw) { dev_err(&hw->spi->dev, "file not found %s\n", hw->fw.sensor_file_name); goto release; } ret = parse_sensor_fw(dev, sensor_fw); if (ret) { dev_err(&hw->spi->dev, "parse fw %s failed\n", hw->fw.sensor_file_name); goto release_sensor; } } ret = request_firmware(&sku_cnf_fw, hw->fw.sku_cnf_file_name, dev->dev); if (ret < 0 || !sku_cnf_fw) { dev_err(&hw->spi->dev, "file not found %s\n", hw->fw.sku_cnf_file_name); goto release_sensor; } ret = parse_sku_cnf_fw(dev, sku_cnf_fw); if (ret) { dev_err(&hw->spi->dev, "parse fw %s failed\n", hw->fw.sensor_file_name); goto release_cnf; } ret = load_bootloader(dev); if (ret) goto release_cnf; ret = load_fw(dev); if (ret) goto release_cnf; return 0; release_cnf: release_firmware(sku_cnf_fw); release_sensor: release_firmware(sensor_fw); release: release_firmware(fw); return ret; } /** * mei_vsc_fw_status - read fw status register from pci config space * * @dev: mei device * @fw_status: fw status * * Return: 0 on success, error otherwise */ static int mei_vsc_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) { if (!fw_status) return -EINVAL; fw_status->count = 0; return 0; } /** * mei_vsc_pg_state - translate internal pg state * to the mei power gating state * * @dev: mei device * * Return: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise */ static inline enum mei_pg_state mei_vsc_pg_state(struct mei_device *dev) { return MEI_PG_OFF; } /** * mei_vsc_intr_enable - enables mei device interrupts * * @dev: the device structure */ static void mei_vsc_intr_enable(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); enable_irq(hw->wakeuphostint); } /** * mei_vsc_intr_disable - disables mei device interrupts * * @dev: the device structure */ static void mei_vsc_intr_disable(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); disable_irq(hw->wakeuphostint); } /** * mei_vsc_intr_clear - clear and stop interrupts * * @dev: the device structure */ static void mei_vsc_intr_clear(struct mei_device *dev) { ; } /** * mei_vsc_synchronize_irq - wait for pending IRQ handlers * * @dev: the device structure */ static void mei_vsc_synchronize_irq(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); synchronize_irq(hw->wakeuphostint); } /** * mei_vsc_hw_config - configure hw dependent settings * * @dev: mei device * * Return: * * -EINVAL when read_fws is not set * * 0 on success * */ static int mei_vsc_hw_config(struct mei_device *dev) { return 0; } /** * mei_vsc_host_set_ready - enable device * * @dev: mei device */ static void mei_vsc_host_set_ready(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); hw->host_ready = true; } /** * mei_vsc_host_is_ready - check whether the host has turned ready * * @dev: mei device * Return: bool */ static bool mei_vsc_host_is_ready(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); return hw->host_ready; } /** * mei_vsc_hw_is_ready - check whether the me(hw) has turned ready * * @dev: mei device * Return: bool */ static bool mei_vsc_hw_is_ready(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); return hw->fw_ready; } /** * mei_vsc_hw_start - hw start routine * * @dev: mei device * Return: 0 on success, error otherwise */ #define MEI_SPI_START_TIMEOUT 200 static int mei_vsc_hw_start(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); u8 buf; int len; int ret; int timeout = MEI_SPI_START_TIMEOUT; mei_vsc_host_set_ready(dev); atomic_set(&hw->lock_cnt, 0); mei_vsc_intr_enable(dev); /* wait for FW ready */ while (timeout > 0) { msleep(50); timeout -= 50; ret = mei_vsc_read_raw(hw, &buf, sizeof(buf), &len); if (!ret && ret != -EAGAIN) break; } if (timeout <= 0) return -ENODEV; dev_dbg(dev->dev, "hw is ready\n"); hw->fw_ready = true; return 0; } /** * mei_vsc_hbuf_is_ready - checks if host buf is empty. * * @dev: the device structure * * Return: true if empty, false - otherwise. */ static bool mei_vsc_hbuf_is_ready(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); return hw->write_lock_cnt == 0; } /** * mei_vsc_hbuf_empty_slots - counts write empty slots. * * @dev: the device structure * * Return: empty slots count */ static int mei_vsc_hbuf_empty_slots(struct mei_device *dev) { return MAX_MEI_MSG_SIZE / MEI_SLOT_SIZE; } /** * mei_vsc_hbuf_depth - returns depth of the hw buf. * * @dev: the device structure * * Return: size of hw buf in slots */ static u32 mei_vsc_hbuf_depth(const struct mei_device *dev) { return MAX_MEI_MSG_SIZE / MEI_SLOT_SIZE; } /** * mei_vsc_write - writes a message to FW. * * @dev: the device structure * @hdr: header of message * @hdr_len: header length in bytes: must be multiplication of a slot (4bytes) * @data: payload * @data_len: payload length in bytes * * Return: 0 if success, < 0 - otherwise. */ static int mei_vsc_write(struct mei_device *dev, const void *hdr, size_t hdr_len, const void *data, size_t data_len) { struct mei_vsc_hw *hw = to_vsc_hw(dev); int ret; char *buf = hw->tx_buf; if (WARN_ON(!hdr || !data || hdr_len & 0x3 || data_len > MAX_SPI_MSG_SIZE)) { dev_err(dev->dev, "%s error write msg hdr_len %zu data_len %zu\n", __func__, hdr_len, data_len); return -EINVAL; } hw->write_lock_cnt++; memcpy(buf, hdr, hdr_len); memcpy(buf + hdr_len, data, data_len); dev_dbg(dev->dev, "%s %d" MEI_HDR_FMT, __func__, hw->write_lock_cnt, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); ret = mei_vsc_write_raw(hw, buf, hdr_len + data_len); if (ret) dev_err(dev->dev, MEI_HDR_FMT "hdr_len %zu data len %zu\n", MEI_HDR_PRM((struct mei_msg_hdr *)hdr), hdr_len, data_len); hw->write_lock_cnt--; return ret; } /** * mei_vsc_read * read spi message * * @dev: the device structure * * Return: mei hdr value (u32) */ static inline u32 mei_vsc_read(const struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); int ret; ret = mei_vsc_read_raw(hw, hw->rx_buf, sizeof(hw->rx_buf), &hw->rx_len); if (ret || hw->rx_len < sizeof(u32)) return 0; return *(u32 *)hw->rx_buf; } /** * mei_vsc_count_full_read_slots - counts read full slots. * * @dev: the device structure * * Return: -EOVERFLOW if overflow, otherwise filled slots count */ static int mei_vsc_count_full_read_slots(struct mei_device *dev) { return MAX_MEI_MSG_SIZE / MEI_SLOT_SIZE; } /** * mei_vsc_read_slots - reads a message from mei device. * * @dev: the device structure * @buf: message buf will be written * @len: message size will be read * * Return: always 0 */ static int mei_vsc_read_slots(struct mei_device *dev, unsigned char *buf, unsigned long len) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct mei_msg_hdr *hdr; hdr = (struct mei_msg_hdr *)hw->rx_buf; WARN_ON(len != hdr->length || hdr->length + sizeof(*hdr) != hw->rx_len); memcpy(buf, hw->rx_buf + sizeof(*hdr), len); return 0; } /** * mei_vsc_pg_in_transition - is device now in pg transition * * @dev: the device structure * * Return: true if in pg transition, false otherwise */ static bool mei_vsc_pg_in_transition(struct mei_device *dev) { return dev->pg_event >= MEI_PG_EVENT_WAIT && dev->pg_event <= MEI_PG_EVENT_INTR_WAIT; } /** * mei_vsc_pg_is_enabled - detect if PG is supported by HW * * @dev: the device structure * * Return: true is pg supported, false otherwise */ static bool mei_vsc_pg_is_enabled(struct mei_device *dev) { return false; } /** * mei_vsc_hw_reset - resets fw. * * @dev: the device structure * @intr_enable: if interrupt should be enabled after reset. * * Return: 0 on success an error code otherwise */ static int mei_vsc_hw_reset(struct mei_device *dev, bool intr_enable) { struct mei_vsc_hw *hw = to_vsc_hw(dev); int ret; mei_vsc_intr_disable(dev); ret = vsc_reset(dev); if (ret) return ret; if (hw->disconnect) return 0; ret = init_hw(dev); if (ret) return -ENODEV; hw->seq = 0; return 0; } /** * mei_vsc_irq_quick_handler - The ISR of the MEI device * * @irq: The irq number * @dev_id: pointer to the device structure * * Return: irqreturn_t */ irqreturn_t mei_vsc_irq_quick_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *)dev_id; struct mei_vsc_hw *hw = to_vsc_hw(dev); dev_dbg(dev->dev, "interrupt top half lock_cnt %d state %d\n", atomic_read(&hw->lock_cnt), dev->dev_state); atomic_inc(&hw->lock_cnt); wake_up(&hw->xfer_wait); if (dev->dev_state == MEI_DEV_INITIALIZING || dev->dev_state == MEI_DEV_RESETTING) return IRQ_HANDLED; return IRQ_WAKE_THREAD; } /** * mei_vsc_irq_thread_handler - function called after ISR to handle the interrupt * processing. * * @irq: The irq number * @dev_id: pointer to the device structure * * Return: irqreturn_t * */ irqreturn_t mei_vsc_irq_thread_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *)dev_id; struct mei_vsc_hw *hw = to_vsc_hw(dev); struct list_head cmpl_list; s32 slots; int rets = 0; dev_dbg(dev->dev, "function called after ISR to handle the interrupt processing dev->dev_state=%d.\n", dev->dev_state); /* initialize our complete list */ mutex_lock(&dev->device_lock); INIT_LIST_HEAD(&cmpl_list); /* check slots available for reading */ slots = mei_count_full_read_slots(dev); dev_dbg(dev->dev, "slots to read = %08x\n", slots); reread: while (spi_need_read(hw)) { dev_dbg(dev->dev, "slots to read in = %08x\n", slots); rets = mei_irq_read_handler(dev, &cmpl_list, &slots); /* There is a race between ME write and interrupt delivery: * Not all data is always available immediately after the * interrupt, so try to read again on the next interrupt. */ if (rets == -ENODATA) break; if (rets && (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_POWER_DOWN)) { dev_err(dev->dev, "mei_irq_read_handler ret = %d.\n", rets); schedule_work(&dev->reset_work); goto end; } } dev_dbg(dev->dev, "slots to read out = %08x\n", slots); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); rets = mei_irq_write_handler(dev, &cmpl_list); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); mei_irq_compl_handler(dev, &cmpl_list); if (spi_need_read(hw)) goto reread; end: dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } static const struct mei_hw_ops mei_vsc_hw_ops = { .fw_status = mei_vsc_fw_status, .pg_state = mei_vsc_pg_state, .host_is_ready = mei_vsc_host_is_ready, .hw_is_ready = mei_vsc_hw_is_ready, .hw_reset = mei_vsc_hw_reset, .hw_config = mei_vsc_hw_config, .hw_start = mei_vsc_hw_start, .pg_in_transition = mei_vsc_pg_in_transition, .pg_is_enabled = mei_vsc_pg_is_enabled, .intr_clear = mei_vsc_intr_clear, .intr_enable = mei_vsc_intr_enable, .intr_disable = mei_vsc_intr_disable, .synchronize_irq = mei_vsc_synchronize_irq, .hbuf_free_slots = mei_vsc_hbuf_empty_slots, .hbuf_is_ready = mei_vsc_hbuf_is_ready, .hbuf_depth = mei_vsc_hbuf_depth, .write = mei_vsc_write, .rdbuf_full_slots = mei_vsc_count_full_read_slots, .read_hdr = mei_vsc_read, .read = mei_vsc_read_slots }; /** * mei_vsc_dev_init - allocates and initializes the mei device structure * * @parent: device associated with physical device (spi/platform) * * Return: The mei_device pointer on success, NULL on failure. */ struct mei_device *mei_vsc_dev_init(struct device *parent) { struct mei_device *dev; struct mei_vsc_hw *hw; dev = devm_kzalloc(parent, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) mei_device_init(dev, parent, &mei_vsc_hw_ops); #else mei_device_init(dev, parent, false, &mei_vsc_hw_ops); #endif dev->fw_f_fw_ver_supported = 0; dev->kind = 0; return dev; } ivsc-driver-0~git202311021215.73a044d9/drivers/misc/mei/hw-vsc.h000066400000000000000000000172021452306460500233260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2021, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_HW_SPI_H_ #define _MEI_HW_SPI_H_ #include #include #include #include #include "mei_dev.h" struct mei_cfg { const struct mei_fw_status fw_status; const char *kind; u32 fw_ver_supported : 1; u32 hw_trc_supported : 1; }; enum FRAG_TYPE { BOOT_IMAGE_TYPE, ARC_SEM_IMG_TYPE, EM7D_IMG_TYPE, ACER_IMG_TYPE, ACEV_IMG_TYPE, ACEC_IMG_TYPE, SKU_CONF_TYPE, FRAGMENT_TYPE_MAX, }; struct fragment { enum FRAG_TYPE type; u32 location; const u8 *data; u32 size; }; irqreturn_t mei_vsc_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_vsc_irq_thread_handler(int irq, void *dev_id); struct mei_device *mei_vsc_dev_init(struct device *parent); #define VSC_MAGIC_NUM 0x49505343 #define VSC_FILE_MAGIC 0x46564353 #define VSC_FW_MAGIC 0x49574653 #define VSC_ROM_SPI_PKG_SIZE 256 #define FW_SPI_PKG_SIZE 512 #define IMG_MAX_LOC (0x50FFFFFF) #define FW_MAX_SIZE (0x200000) #define SKU_CONFIG_LOC (0x5001A000) #define SKU_MAX_SIZE (4100) #define IMG_DMA_ENABLE_OPTION (1 << 0) #define SIG_SIZE 384 #define PUBKEY_SIZE 384 #define CSSHEADER_SIZE 128 #define VSC_CMD_QUERY 0 #define VSC_CMD_DL_SET 1 #define VSC_CMD_DL_START 2 #define VSC_CMD_DL_CONT 3 #define VSC_CMD_DUMP_MEM 4 #define VSC_CMD_SET_REG 5 #define VSC_CMD_PRINT_ROM_VERSION 6 #define VSC_CMD_WRITE_FLASH 7 #define VSC_CMD_RESERVED 8 enum IMAGE_TYPE { IMG_DEBUG, IMG_BOOTLOADER, IMG_EM7D, IMG_ARCSEM, IMG_ACE_RUNTIME, IMG_ACE_VISION, IMG_ACE_CONFIG, IMG_SKU_CONFIG }; /*image count define, refer to Clover Fall Boot ROM HLD 1.0*/ #define IMG_ACEV_ACECNF 2 #define IMG_BOOT_ARC_EM7D 3 #define IMG_BOOT_ARC_ACER_EM7D 4 #define IMG_BOOT_ARC_ACER_ACEV_EM7D 5 #define IMG_BOOT_ARC_ACER_ACEV_ACECNF_EM7D 6 #define IMG_ARC_ACER_ACEV_ACECNF_EM7D (IMG_BOOT_ARC_ACER_ACEV_ACECNF_EM7D - 1) #define IMG_CNT_MAX IMG_BOOT_ARC_ACER_ACEV_ACECNF_EM7D #define VSC_TOKEN_BOOTLOADER_REQ 1 #define VSC_TOKEN_FIRMWARE_REQ 2 #define VSC_TOKEN_DOWNLOAD_CONT 3 #define VSC_TOKEN_DUMP_RESP 4 #define VSC_TOKEN_DUMP_CONT 5 #define VSC_TOKEN_SKU_CONFIG_REQ 6 #define VSC_TOKEN_ERROR 7 #define VSC_TOKEN_DUMMY 8 #define VSC_TOKEN_CAM_STATUS_RESP 9 #define VSC_TOKEN_CAM_BOOT 10 #define MAX_SVN_VALUE (0xFFFFFFFE) #define EFUSE1_ADDR (0xE0030000 + 0x38) #define STRAP_ADDR (0xE0030000 + 0x100) #define SI_MAINSTEPPING_VERSION_OFFSET (4) #define SI_MAINSTEPPING_VERSION_MASK (0xF) #define SI_MAINSTEPPING_VERSION_A (0x0) #define SI_MAINSTEPPING_VERSION_B (0x1) #define SI_MAINSTEPPING_VERSION_C (0x2) #define SI_SUBSTEPPING_VERSION_OFFSET (0x0) #define SI_SUBSTEPPING_VERSION_MASK (0xF) #define SI_SUBSTEPPING_VERSION_0 (0x0) #define SI_SUBSTEPPING_VERSION_0_PRIME (0x1) #define SI_SUBSTEPPING_VERSION_1 (0x2) #define SI_SUBSTEPPING_VERSION_1_PRIME (0x3) #define SI_STRAP_KEY_SRC_OFFSET (16) #define SI_STRAP_KEY_SRC_MASK (0x1) #define SI_STRAP_KEY_SRC_DEBUG (0x0) #define SI_STRAP_KEY_SRC_PRODUCT (0x1) struct vsc_rom_master_frame { u32 magic; u8 cmd; union { struct { u8 img_type; u16 option; u32 img_len; u32 img_loc; u32 crc; u8 res[0]; } __packed dl_start; struct { u8 option; u16 img_cnt; u32 payload[(VSC_ROM_SPI_PKG_SIZE - 8) / 4]; } __packed dl_set; struct { u8 end_flag; u16 len; u8 payload[VSC_ROM_SPI_PKG_SIZE - 8]; } __packed dl_cont; struct { u8 res; u16 len; u32 addr; #define ROM_DUMP_MEM_RESERVE_SIZE 12 u8 payload[VSC_ROM_SPI_PKG_SIZE - ROM_DUMP_MEM_RESERVE_SIZE]; } __packed dump_mem; struct { u8 res[3]; u32 addr; u32 val; #define ROM_SET_REG_RESERVE_SIZE 16 u8 payload[VSC_ROM_SPI_PKG_SIZE - ROM_SET_REG_RESERVE_SIZE]; } __packed set_reg; struct { u8 ins[0]; } __packed undoc_f1; struct { u32 addr; u32 len; u8 payload[0]; } __packed os_dump_mem; u8 reserve[VSC_ROM_SPI_PKG_SIZE - 5]; } data; } __packed; struct vsc_fw_master_frame { u32 magic; u8 cmd; union { struct { u16 option; u8 img_type; u32 img_len; u32 img_loc; u32 crc; u8 res[0]; } __packed dl_start; struct { u16 option; u8 img_cnt; u32 payload[(FW_SPI_PKG_SIZE - 8) / 4]; } __packed dl_set; struct { u8 end_flag; u16 len; u8 payload[FW_SPI_PKG_SIZE - 8]; } __packed dl_cont; struct { u32 addr; u8 len; u8 payload[0]; } __packed dump_mem; struct { u32 addr; u32 val; u8 payload[0]; } __packed set_reg; struct { u8 ins[0]; } __packed undoc_f1; struct { u32 addr; u32 len; u8 payload[0]; } __packed os_dump_mem; struct { u8 resv[3]; u32 check_sum; #define LOADER_BOOT_RESERVE_SIZE 12 u8 payload[FW_SPI_PKG_SIZE - LOADER_BOOT_RESERVE_SIZE]; } __packed boot; u8 reserve[FW_SPI_PKG_SIZE - 5]; } data; } __packed; struct vsc_master_frame_fw_cont { u8 payload[FW_SPI_PKG_SIZE]; } __packed; struct vsc_rom_slave_token { u32 magic; u8 token; u8 type; u8 res[2]; u8 payload[VSC_ROM_SPI_PKG_SIZE - 8]; } __packed; struct vsc_bol_slave_token { u32 magic; u8 token; u8 type; u8 res[2]; u8 payload[FW_SPI_PKG_SIZE - 8]; } __packed; struct vsc_boot_img { u32 magic; u32 option; u32 image_count; u32 image_loc[IMG_CNT_MAX]; } __packed; struct vsc_sensor_img_t { u32 magic; u32 option; u32 image_count; u32 image_loc[IMG_ACEV_ACECNF]; } __packed; struct bootloader_sign { u32 magic; u32 image_size; u8 image[0]; } __packed; struct manifest { u32 svn; u32 header_ver; u32 comp_flags; u32 comp_name; u32 comp_vendor_name; u32 module_size; u32 module_addr; } __packed; struct firmware_sign { u32 magic; u32 image_size; u8 image[1]; } __packed; /* spi transport layer */ #define PACKET_SYNC 0x31 #define MAX_SPI_MSG_SIZE 2048 #define MAX_MEI_MSG_SIZE 512 #define CRC_SIZE sizeof(u32) #define PACKET_SIZE(pkt) (sizeof(pkt->hdr) + (pkt->hdr.len) + (CRC_SIZE)) #define MAX_PACKET_SIZE \ (sizeof(struct spi_xfer_hdr) + MAX_SPI_MSG_SIZE + (CRC_SIZE)) /* SPI xfer timeout size definition */ #define XFER_TIMEOUT_BYTES 700 #define MAX_XFER_BUFFER_SIZE ((MAX_PACKET_SIZE) + (XFER_TIMEOUT_BYTES)) struct spi_xfer_hdr { u8 sync; u8 cmd; u16 len; u32 seq; } __packed; struct spi_xfer_packet { struct spi_xfer_hdr hdr; u8 buf[MAX_XFER_BUFFER_SIZE - sizeof(struct spi_xfer_hdr)]; } __packed; #define CMD_SPI_WRITE 0x01 #define CMD_SPI_READ 0x02 #define CMD_SPI_RESET_NOTIFY 0x04 #define CMD_SPI_ACK 0x10 #define CMD_SPI_NACK 0x11 #define CMD_SPI_BUSY 0x12 #define CMD_SPI_FATAL_ERR 0x13 struct host_timestamp { u64 realtime; u64 boottime; } __packed; struct vsc_boot_fw { u32 main_ver; u32 sub_ver; u32 key_src; u32 svn; u8 tx_buf[FW_SPI_PKG_SIZE]; u8 rx_buf[FW_SPI_PKG_SIZE]; /* FirmwareBootFile */ char fw_file_name[256]; /* PkgBootFile */ char sensor_file_name[256]; /* SkuConfigBootFile */ char sku_cnf_file_name[256]; u32 fw_option; u32 fw_cnt; struct fragment frags[FRAGMENT_TYPE_MAX]; }; struct mei_vsc_hw { struct spi_device *spi; struct spi_transfer xfer; struct spi_message msg; u8 rx_buf[MAX_SPI_MSG_SIZE]; u8 tx_buf[MAX_SPI_MSG_SIZE]; u32 rx_len; int wakeuphostint; struct gpio_desc *wakeuphost; struct gpio_desc *resetfw; struct gpio_desc *wakeupfw; struct vsc_boot_fw fw; bool host_ready; bool fw_ready; /* mei transport layer */ u32 seq; u8 tx_buf1[MAX_XFER_BUFFER_SIZE]; u8 rx_buf1[MAX_XFER_BUFFER_SIZE]; struct work_struct probe_work; struct mutex mutex; bool disconnect; atomic_t lock_cnt; int write_lock_cnt; wait_queue_head_t xfer_wait; char cam_sensor_name[32]; }; #define to_vsc_hw(dev) ((struct mei_vsc_hw *)((dev)->hw)) #endif ivsc-driver-0~git202311021215.73a044d9/drivers/misc/mei/spi-vsc.c000066400000000000000000000207671452306460500235100ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include #include #include #include #include #include #include #include #include #include #include "hw-vsc.h" #define LINK_NUMBER (1) #define METHOD_NAME_SID "SID" /* 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 mei_vsc_acpi_gpios[] = { { "wakeuphost-gpios", &wakeuphost_gpio, 1 }, { "wakeuphostint-gpios", &wakeuphostint_gpio, 1 }, { "resetfw-gpios", &resetfw_gpio, 1 }, { "wakeupfw-gpios", &wakeupfw, 1 }, {} }; static const struct acpi_device_id cvfd_ids[] = { { "INTC1059", 0 }, { "INTC1095", 0 }, { "INTC100A", 0 }, { "INTC10CF", 0 }, {} }; struct match_ids_walk_data { struct acpi_device *adev; }; static int match_device_ids(struct acpi_device *adev, void *data) { struct match_ids_walk_data *wd = data; if (!acpi_match_device_ids(adev, cvfd_ids)) { wd->adev = adev; return 1; } return 0; } static struct acpi_device *find_cvfd_child_adev(struct acpi_device *parent) { struct match_ids_walk_data wd = { 0 }; if (!parent) return NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) acpi_dev_for_each_child(parent, match_device_ids, &wd); return wd.adev; #else list_for_each_entry(wd.adev, &parent->children, node) { if (match_device_ids(wd.adev, &wd)) return wd.adev; } return NULL; #endif } static int get_sensor_name(struct mei_device *dev) { struct mei_vsc_hw *hw = to_vsc_hw(dev); struct spi_device *spi = hw->spi; struct acpi_device *adev; union acpi_object obj = { .type = ACPI_TYPE_INTEGER }; union acpi_object *ret_obj; struct acpi_object_list arg_list = { .count = 1, .pointer = &obj, }; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_status status; char *c; adev = find_cvfd_child_adev(ACPI_COMPANION(&spi->dev)); if (!adev) { dev_err(&spi->dev, "ACPI not found CVFD device\n"); return -ENODEV; } obj.integer.value = LINK_NUMBER; status = acpi_evaluate_object(adev->handle, METHOD_NAME_SID, &arg_list, &buffer); if (ACPI_FAILURE(status)) { dev_err(&spi->dev, "can't evaluate SID method: %d\n", status); return -ENODEV; } ret_obj = buffer.pointer; dev_dbg(&spi->dev, "SID status %d %lld %d - %d %s %d\n", status, buffer.length, ret_obj->type, ret_obj->string.length, ret_obj->string.pointer, acpi_has_method(adev->handle, METHOD_NAME_SID)); if (ret_obj->string.length > sizeof(hw->cam_sensor_name)) { ACPI_FREE(buffer.pointer); return -EINVAL; } memcpy(hw->cam_sensor_name, ret_obj->string.pointer, ret_obj->string.length); /* camera sensor name are all in lower case */ for (c = hw->cam_sensor_name; *c != '\0'; c++) *c = tolower(*c); ACPI_FREE(buffer.pointer); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) acpi_dev_clear_dependencies(adev); #endif return 0; } static void mei_vsc_probe_work(struct work_struct *work) { struct mei_vsc_hw *hw = container_of(work, struct mei_vsc_hw, probe_work); struct spi_device *spi = hw->spi; struct mei_device *dev = spi_get_drvdata(spi); int ret; if (mei_start(dev)) { dev_err(&spi->dev, "init hw failure.\n"); goto release_irq; } ret = mei_register(dev, &spi->dev); if (ret) { dev_err(&spi->dev, "mei_register failure.\n"); goto stop; } pm_runtime_enable(dev->dev); dev_dbg(&spi->dev, "initialization successful.\n"); return; stop: mei_stop(dev); release_irq: mei_cancel_work(dev); mei_disable_interrupts(dev); free_irq(hw->wakeuphostint, dev); } static int mei_vsc_probe(struct spi_device *spi) { struct mei_vsc_hw *hw; struct mei_device *dev; int ret; dev = mei_vsc_dev_init(&spi->dev); if (!dev) return -ENOMEM; hw = to_vsc_hw(dev); INIT_WORK(&hw->probe_work, mei_vsc_probe_work); mutex_init(&hw->mutex); init_waitqueue_head(&hw->xfer_wait); hw->spi = spi; spi_set_drvdata(spi, dev); ret = get_sensor_name(dev); if (ret) return ret; ret = devm_acpi_dev_add_driver_gpios(&spi->dev, mei_vsc_acpi_gpios); if (ret) { dev_err(&spi->dev, "%s: fail to add gpio\n", __func__); return -EBUSY; } hw->wakeuphost = devm_gpiod_get(&spi->dev, "wakeuphost", GPIOD_IN); if (IS_ERR(hw->wakeuphost)) { dev_err(&spi->dev, "gpio get irq failed\n"); return PTR_ERR(hw->wakeuphost); } hw->resetfw = devm_gpiod_get(&spi->dev, "resetfw", GPIOD_OUT_HIGH); if (IS_ERR(hw->resetfw)) { dev_err(&spi->dev, "gpio get resetfw failed\n"); return PTR_ERR(hw->resetfw); } hw->wakeupfw = devm_gpiod_get(&spi->dev, "wakeupfw", GPIOD_OUT_HIGH); if (IS_ERR(hw->wakeupfw)) { dev_err(&spi->dev, "gpio get wakeupfw failed\n"); return PTR_ERR(hw->wakeupfw); } ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(&spi->dev), "wakeuphostint-gpios", 0); if (ret < 0) return ret; hw->wakeuphostint = ret; irq_set_status_flags(hw->wakeuphostint, IRQ_DISABLE_UNLAZY); ret = request_threaded_irq(hw->wakeuphostint, mei_vsc_irq_quick_handler, mei_vsc_irq_thread_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, KBUILD_MODNAME, dev); if (ret) return ret; schedule_work(&hw->probe_work); return 0; } static int __maybe_unused mei_vsc_suspend(struct device *device) { struct spi_device *spi = to_spi_device(device); struct mei_device *dev = spi_get_drvdata(spi); struct mei_vsc_hw *hw = to_vsc_hw(dev); if (!dev) return -ENODEV; dev_dbg(dev->dev, "%s\n", __func__); hw->disconnect = true; mei_stop(dev); mei_disable_interrupts(dev); free_irq(hw->wakeuphostint, dev); return 0; } static int __maybe_unused mei_vsc_resume(struct device *device) { struct spi_device *spi = to_spi_device(device); struct mei_device *dev = spi_get_drvdata(spi); struct mei_vsc_hw *hw = to_vsc_hw(dev); int ret; dev_dbg(dev->dev, "%s\n", __func__); irq_set_status_flags(hw->wakeuphostint, IRQ_DISABLE_UNLAZY); ret = request_threaded_irq(hw->wakeuphostint, mei_vsc_irq_quick_handler, mei_vsc_irq_thread_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, KBUILD_MODNAME, dev); if (ret) { dev_err(device, "request_threaded_irq failed: irq = %d.\n", hw->wakeuphostint); return ret; } hw->disconnect = false; ret = mei_restart(dev); if (ret) return ret; /* Start timer if stopped in suspend */ schedule_delayed_work(&dev->timer_work, HZ); return 0; } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0) static int mei_vsc_remove(struct spi_device *spi) #else static void mei_vsc_remove(struct spi_device *spi) #endif { struct mei_device *dev = spi_get_drvdata(spi); struct mei_vsc_hw *hw = to_vsc_hw(dev); dev_info(&spi->dev, "%s %d", __func__, hw->wakeuphostint); cancel_work_sync(&hw->probe_work); pm_runtime_disable(dev->dev); hw->disconnect = true; mei_stop(dev); mei_disable_interrupts(dev); free_irq(hw->wakeuphostint, dev); mei_deregister(dev); mutex_destroy(&hw->mutex); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0) return 0; #endif } /** * mei_vsc_shutdown - Device Removal Routine * * @spi: SPI device structure * * mei_vsc_shutdown is called from the reboot notifier * it's a simplified version of remove so we go down * faster. */ static void mei_vsc_shutdown(struct spi_device *spi) { struct mei_device *dev = spi_get_drvdata(spi); struct mei_vsc_hw *hw = to_vsc_hw(dev); dev_dbg(dev->dev, "shutdown\n"); cancel_work_sync(&hw->probe_work); hw->disconnect = true; mei_stop(dev); mei_disable_interrupts(dev); free_irq(hw->wakeuphostint, dev); } static const struct dev_pm_ops mei_vsc_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mei_vsc_suspend, mei_vsc_resume) }; static const struct acpi_device_id mei_vsc_acpi_ids[] = { { "INTC1058"}, { "INTC1094"}, { "INTC1009"}, /* RPL */ { "INTC10D0"}, /* MTL */ {}, }; MODULE_DEVICE_TABLE(acpi, mei_vsc_acpi_ids); static struct spi_driver mei_vsc_driver = { .driver = { .name = KBUILD_MODNAME, .acpi_match_table = ACPI_PTR(mei_vsc_acpi_ids), .pm = &mei_vsc_pm_ops, }, .probe = mei_vsc_probe, .remove = mei_vsc_remove, .shutdown = mei_vsc_shutdown, .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, }; module_spi_driver(mei_vsc_driver); MODULE_AUTHOR("Ye Xiang "); MODULE_DESCRIPTION("Intel MEI VSC driver"); MODULE_LICENSE("GPL v2"); ivsc-driver-0~git202311021215.73a044d9/drivers/spi/000077500000000000000000000000001452306460500210325ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/drivers/spi/spi-ljca.c000066400000000000000000000170241452306460500227040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-only /* * Intel La Jolla Cove Adapter USB-SPI driver * * Copyright (c) 2021, Intel Corporation. */ #include #include #include #include #include /* SPI commands */ enum ljca_spi_cmd { LJCA_SPI_INIT = 1, LJCA_SPI_READ, LJCA_SPI_WRITE, LJCA_SPI_WRITEREAD, LJCA_SPI_DEINIT, }; #define LJCA_SPI_BUS_MAX_HZ 48000000 enum { LJCA_SPI_BUS_SPEED_24M, LJCA_SPI_BUS_SPEED_12M, LJCA_SPI_BUS_SPEED_8M, LJCA_SPI_BUS_SPEED_6M, LJCA_SPI_BUS_SPEED_4_8M, /*4.8MHz*/ LJCA_SPI_BUS_SPEED_MIN = LJCA_SPI_BUS_SPEED_4_8M, }; enum { LJCA_SPI_CLOCK_LOW_POLARITY, LJCA_SPI_CLOCK_HIGH_POLARITY, }; enum { LJCA_SPI_CLOCK_FIRST_PHASE, LJCA_SPI_CLOCK_SECOND_PHASE, }; #define LJCA_SPI_BUF_SIZE 60 #define LJCA_SPI_MAX_XFER_SIZE \ (LJCA_SPI_BUF_SIZE - sizeof(struct spi_xfer_packet)) union spi_clock_mode { struct { u8 polarity : 1; u8 phrase : 1; u8 reserved : 6; } u; u8 mode; } __packed; struct spi_init_packet { u8 index; u8 speed; union spi_clock_mode mode; } __packed; struct spi_xfer_indicator { u8 id : 6; u8 cmpl : 1; u8 index : 1; }; struct spi_xfer_packet { struct spi_xfer_indicator indicator; s8 len; u8 data[]; } __packed; struct ljca_spi_dev { struct platform_device *pdev; struct ljca_spi_info *ctr_info; struct spi_master *master; u8 speed; u8 mode; u8 obuf[LJCA_SPI_BUF_SIZE]; u8 ibuf[LJCA_SPI_BUF_SIZE]; }; static int ljca_spi_read_write(struct ljca_spi_dev *ljca_spi, const u8 *w_data, u8 *r_data, int len, int id, int complete, int cmd) { struct spi_xfer_packet *w_packet = (struct spi_xfer_packet *)ljca_spi->obuf; struct spi_xfer_packet *r_packet = (struct spi_xfer_packet *)ljca_spi->ibuf; int ret; int ibuf_len; w_packet->indicator.index = ljca_spi->ctr_info->id; w_packet->indicator.id = id; w_packet->indicator.cmpl = complete; if (cmd == LJCA_SPI_READ) { w_packet->len = sizeof(u16); *(u16 *)&w_packet->data[0] = len; } else { w_packet->len = len; memcpy(w_packet->data, w_data, len); } ret = ljca_transfer(ljca_spi->pdev, cmd, w_packet, sizeof(*w_packet) + w_packet->len, r_packet, &ibuf_len); if (ret) return ret; if (ibuf_len < sizeof(*r_packet) || r_packet->len <= 0) { dev_err(&ljca_spi->pdev->dev, "receive patcket error len %d\n", r_packet->len); return -EIO; } if (r_data) memcpy(r_data, r_packet->data, r_packet->len); return 0; } static int ljca_spi_init(struct ljca_spi_dev *ljca_spi, int div, int mode) { struct spi_init_packet w_packet = { 0 }; int ret; if (ljca_spi->mode == mode && ljca_spi->speed == div) return 0; if (mode & SPI_CPOL) w_packet.mode.u.polarity = LJCA_SPI_CLOCK_HIGH_POLARITY; else w_packet.mode.u.polarity = LJCA_SPI_CLOCK_LOW_POLARITY; if (mode & SPI_CPHA) w_packet.mode.u.phrase = LJCA_SPI_CLOCK_SECOND_PHASE; else w_packet.mode.u.phrase = LJCA_SPI_CLOCK_FIRST_PHASE; w_packet.index = ljca_spi->ctr_info->id; w_packet.speed = div; ret = ljca_transfer(ljca_spi->pdev, LJCA_SPI_INIT, &w_packet, sizeof(w_packet), NULL, NULL); if (ret) return ret; ljca_spi->mode = mode; ljca_spi->speed = div; return 0; } static int ljca_spi_deinit(struct ljca_spi_dev *ljca_spi) { struct spi_init_packet w_packet = { 0 }; w_packet.index = ljca_spi->ctr_info->id; return ljca_transfer(ljca_spi->pdev, LJCA_SPI_DEINIT, &w_packet, sizeof(w_packet), NULL, NULL); } static int ljca_spi_transfer(struct ljca_spi_dev *ljca_spi, const u8 *tx_data, u8 *rx_data, u16 len) { int ret; int remaining = len; int offset = 0; int cur_len; int complete = 0; int i; for (i = 0; remaining > 0; offset += cur_len, remaining -= cur_len, i++) { dev_dbg(&ljca_spi->pdev->dev, "fragment %d offset %d remaining %d ret %d\n", i, offset, remaining, ret); if (remaining > LJCA_SPI_MAX_XFER_SIZE) { cur_len = LJCA_SPI_MAX_XFER_SIZE; } else { cur_len = remaining; complete = 1; } if (tx_data && rx_data) ret = ljca_spi_read_write(ljca_spi, tx_data + offset, rx_data + offset, cur_len, i, complete, LJCA_SPI_WRITEREAD); else if (tx_data) ret = ljca_spi_read_write(ljca_spi, tx_data + offset, NULL, cur_len, i, complete, LJCA_SPI_WRITE); else if (rx_data) ret = ljca_spi_read_write(ljca_spi, NULL, rx_data + offset, cur_len, i, complete, LJCA_SPI_READ); else return -EINVAL; if (ret) return ret; } return 0; } static int ljca_spi_prepare_message(struct spi_master *master, struct spi_message *message) { struct ljca_spi_dev *ljca_spi = spi_master_get_devdata(master); struct spi_device *spi = message->spi; dev_dbg(&ljca_spi->pdev->dev, "cs %d\n", spi->chip_select); return 0; } static int ljca_spi_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *xfer) { struct ljca_spi_dev *ljca_spi = spi_master_get_devdata(master); int ret; int div; div = DIV_ROUND_UP(master->max_speed_hz, xfer->speed_hz) / 2 - 1; if (div > LJCA_SPI_BUS_SPEED_MIN) div = LJCA_SPI_BUS_SPEED_MIN; ret = ljca_spi_init(ljca_spi, div, spi->mode); if (ret < 0) { dev_err(&ljca_spi->pdev->dev, "cannot initialize transfer ret %d\n", ret); return ret; } ret = ljca_spi_transfer(ljca_spi, xfer->tx_buf, xfer->rx_buf, xfer->len); if (ret < 0) dev_err(&ljca_spi->pdev->dev, "ljca spi transfer failed!\n"); return ret; } static int ljca_spi_probe(struct platform_device *pdev) { struct spi_master *master; struct ljca_spi_dev *ljca_spi; struct ljca_platform_data *pdata = dev_get_platdata(&pdev->dev); int ret; master = spi_alloc_master(&pdev->dev, sizeof(*ljca_spi)); if (!master) return -ENOMEM; platform_set_drvdata(pdev, master); ljca_spi = spi_master_get_devdata(master); ljca_spi->ctr_info = &pdata->spi_info; ljca_spi->master = master; ljca_spi->master->dev.of_node = pdev->dev.of_node; ljca_spi->pdev = pdev; ACPI_COMPANION_SET(&ljca_spi->master->dev, ACPI_COMPANION(&pdev->dev)); master->bus_num = -1; master->mode_bits = SPI_CPHA | SPI_CPOL; master->prepare_message = ljca_spi_prepare_message; master->transfer_one = ljca_spi_transfer_one; master->auto_runtime_pm = false; master->max_speed_hz = LJCA_SPI_BUS_MAX_HZ; ret = devm_spi_register_master(&pdev->dev, master); if (ret < 0) { dev_err(&pdev->dev, "Failed to register master\n"); goto exit_free_master; } return ret; exit_free_master: spi_master_put(master); return ret; } static int ljca_spi_dev_remove(struct platform_device *pdev) { struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); struct ljca_spi_dev *ljca_spi = spi_master_get_devdata(master); ljca_spi_deinit(ljca_spi); return 0; } #ifdef CONFIG_PM_SLEEP static int ljca_spi_dev_suspend(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); return spi_master_suspend(master); } static int ljca_spi_dev_resume(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); return spi_master_resume(master); } #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops ljca_spi_pm = { SET_SYSTEM_SLEEP_PM_OPS(ljca_spi_dev_suspend, ljca_spi_dev_resume) }; static struct platform_driver spi_ljca_driver = { .driver = { .name = "ljca-spi", .pm = &ljca_spi_pm, }, .probe = ljca_spi_probe, .remove = ljca_spi_dev_remove, }; module_platform_driver(spi_ljca_driver); MODULE_AUTHOR("Ye Xiang "); MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-SPI driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ljca-spi"); ivsc-driver-0~git202311021215.73a044d9/include/000077500000000000000000000000001452306460500202045ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/include/linux/000077500000000000000000000000001452306460500213435ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/include/linux/mfd/000077500000000000000000000000001452306460500221115ustar00rootroot00000000000000ivsc-driver-0~git202311021215.73a044d9/include/linux/mfd/ljca.h000066400000000000000000000020231452306460500231700ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __LINUX_USB_LJCA_H #define __LINUX_USB_LJCA_H #include #include #include #define MAX_GPIO_NUM 64 struct ljca_gpio_info { int num; DECLARE_BITMAP(valid_pin_map, MAX_GPIO_NUM); }; struct ljca_i2c_info { u8 id; u8 capacity; u8 intr_pin; }; struct ljca_spi_info { u8 id; u8 capacity; }; struct ljca_platform_data { int type; union { struct ljca_gpio_info gpio_info; struct ljca_i2c_info i2c_info; struct ljca_spi_info spi_info; }; }; typedef void (*ljca_event_cb_t)(struct platform_device *pdev, u8 cmd, const void *evt_data, int len); int ljca_register_event_cb(struct platform_device *pdev, ljca_event_cb_t event_cb); void ljca_unregister_event_cb(struct platform_device *pdev); int ljca_transfer(struct platform_device *pdev, u8 cmd, const void *obuf, int obuf_len, void *ibuf, int *ibuf_len); int ljca_transfer_noack(struct platform_device *pdev, u8 cmd, const void *obuf, int obuf_len); #endif ivsc-driver-0~git202311021215.73a044d9/include/linux/vsc.h000066400000000000000000000035771452306460500223230ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_VSC_H_ #define _LINUX_VSC_H_ #include /** * @brief VSC camera ownership definition */ enum vsc_camera_owner { VSC_CAMERA_NONE = 0, VSC_CAMERA_CVF, VSC_CAMERA_IPU, }; /** * @brief VSC privacy status definition */ enum vsc_privacy_status { VSC_PRIVACY_ON = 0, VSC_PRIVACY_OFF, }; /** * @brief VSC MIPI configuration definition */ struct vsc_mipi_config { uint32_t freq; uint32_t lane_num; }; /** * @brief VSC camera status definition */ struct vsc_camera_status { enum vsc_camera_owner owner; enum vsc_privacy_status status; uint32_t exposure_level; }; /** * @brief VSC privacy callback type definition * * @param context Privacy callback handle * @param status Current privacy status */ typedef void (*vsc_privacy_callback_t)(void *handle, enum vsc_privacy_status status); /** * @brief Acquire camera sensor ownership to IPU * * @param config[IN] The pointer of MIPI configuration going to set * @param callback[IN] The pointer of privacy callback function * @param handle[IN] Privacy callback function runtime handle from IPU driver * @param status[OUT] The pointer of camera status after the acquire * * @retval 0 If success * @retval -EIO IO error * @retval -EINVAL Invalid argument * @retval -EAGAIN VSC device not ready * @retval negative values for other errors */ int vsc_acquire_camera_sensor(struct vsc_mipi_config *config, vsc_privacy_callback_t callback, void *handle, struct vsc_camera_status *status); /** * @brief Release camera sensor ownership * * @param status[OUT] Camera status after the release * * @retval 0 If success * @retval -EIO IO error * @retval -EINVAL Invalid argument * @retval -EAGAIN VSC device not ready * @retval negative values for other errors */ int vsc_release_camera_sensor(struct vsc_camera_status *status); #endif