// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2023, Intel Corporation. * Intel Visual Sensing Controller Transport Layer Linux driver */ #include #include #include #include #include #include #include #include #include #include #include #include "vsc-tp.h" #define VSC_MAGIC_NUM 0x49505343 /* IPSC */ #define VSC_MAGIC_FW 0x49574653 /* IWFS */ #define VSC_MAGIC_FILE 0x46564353 /* FVCS */ #define VSC_ADDR_BASE 0xE0030000 #define VSC_EFUSE_ADDR (VSC_ADDR_BASE + 0x038) #define VSC_STRAP_ADDR (VSC_ADDR_BASE + 0x100) #define VSC_MAINSTEPPING_VERSION_MASK GENMASK(7, 4) #define VSC_MAINSTEPPING_VERSION_A 0 #define VSC_SUBSTEPPING_VERSION_MASK GENMASK(3, 0) #define VSC_SUBSTEPPING_VERSION_0 0 #define VSC_SUBSTEPPING_VERSION_1 2 #define VSC_BOOT_IMG_OPTION_MASK GENMASK(15, 0) #define VSC_SKU_CFG_LOCATION 0x5001A000 #define VSC_SKU_MAX_SIZE 4100u #define VSC_ACE_IMG_CNT 2 #define VSC_CSI_IMG_CNT 4 #define VSC_IMG_CNT_MAX 6 #define VSC_ROM_PKG_SIZE 256u #define VSC_FW_PKG_SIZE 512u #define VSC_IMAGE_DIR "intel/vsc/" #define VSC_CSI_IMAGE_NAME VSC_IMAGE_DIR "ivsc_fw.bin" #define VSC_ACE_IMAGE_NAME_FMT VSC_IMAGE_DIR "ivsc_pkg_%s_0.bin" #define VSC_CFG_IMAGE_NAME_FMT VSC_IMAGE_DIR "ivsc_skucfg_%s_0_1.bin" #define VSC_IMAGE_PATH_MAX_LEN 64 #define VSC_SENSOR_NAME_MAX_LEN 16 /* command id */ enum { VSC_CMD_QUERY = 0, VSC_CMD_DL_SET = 1, VSC_CMD_DL_START = 2, VSC_CMD_DL_CONT = 3, VSC_CMD_DUMP_MEM = 4, VSC_CMD_GET_CONT = 8, VSC_CMD_CAM_BOOT = 10, }; /* command ack token */ enum { VSC_TOKEN_BOOTLOADER_REQ = 1, VSC_TOKEN_DUMP_RESP = 4, VSC_TOKEN_ERROR = 7, }; /* image type */ enum { VSC_IMG_BOOTLOADER_TYPE = 1, VSC_IMG_CSI_EM7D_TYPE, VSC_IMG_CSI_SEM_TYPE, VSC_IMG_CSI_RUNTIME_TYPE, VSC_IMG_ACE_VISION_TYPE, VSC_IMG_ACE_CFG_TYPE, VSC_IMG_SKU_CFG_TYPE, }; /* image fragments */ enum { VSC_IMG_BOOTLOADER_FRAG, VSC_IMG_CSI_SEM_FRAG, VSC_IMG_CSI_RUNTIME_FRAG, VSC_IMG_ACE_VISION_FRAG, VSC_IMG_ACE_CFG_FRAG, VSC_IMG_CSI_EM7D_FRAG, VSC_IMG_SKU_CFG_FRAG, VSC_IMG_FRAG_MAX }; struct vsc_rom_cmd { __le32 magic; __u8 cmd_id; union { /* download start */ struct { __u8 img_type; __le16 option; __le32 img_len; __le32 img_loc; __le32 crc; DECLARE_FLEX_ARRAY(__u8, res); } __packed dl_start; /* download set */ struct { __u8 option; __le16 img_cnt; DECLARE_FLEX_ARRAY(__le32, payload); } __packed dl_set; /* download continue */ struct { __u8 end_flag; __le16 len; /* 8 is the offset of payload */ __u8 payload[VSC_ROM_PKG_SIZE - 8]; } __packed dl_cont; /* dump memory */ struct { __u8 res; __le16 len; __le32 addr; DECLARE_FLEX_ARRAY(__u8, payload); } __packed dump_mem; /* 5 is the offset of padding */ __u8 padding[VSC_ROM_PKG_SIZE - 5]; } data; }; struct vsc_rom_cmd_ack { __le32 magic; __u8 token; __u8 type; __u8 res[2]; __u8 payload[]; }; struct vsc_fw_cmd { __le32 magic; __u8 cmd_id; union { struct { __le16 option; __u8 img_type; __le32 img_len; __le32 img_loc; __le32 crc; DECLARE_FLEX_ARRAY(__u8, res); } __packed dl_start; struct { __le16 option; __u8 img_cnt; DECLARE_FLEX_ARRAY(__le32, payload); } __packed dl_set; struct { __le32 addr; __u8 len; DECLARE_FLEX_ARRAY(__u8, payload); } __packed dump_mem; struct { __u8 resv[3]; __le32 crc; DECLARE_FLEX_ARRAY(__u8, payload); } __packed boot; /* 5 is the offset of padding */ __u8 padding[VSC_FW_PKG_SIZE - 5]; } data; }; struct vsc_img { __le32 magic; __le32 option; __le32 image_count; __le32 image_location[VSC_IMG_CNT_MAX]; }; struct vsc_fw_sign { __le32 magic; __le32 image_size; __u8 image[]; }; struct vsc_image_code_data { /* fragment index */ u8 frag_index; /* image type */ u8 image_type; }; struct vsc_img_frag { u8 type; u32 location; const u8 *data; u32 size; }; /** * struct vsc_fw_loader - represent vsc firmware loader * @dev: device used to request firmware * @tp: transport layer used with the firmware loader * @csi: CSI image * @ace: ACE image * @cfg: config image * @tx_buf: tx buffer * @rx_buf: rx buffer * @option: command option * @count: total image count * @sensor_name: camera sensor name * @frags: image fragments */ struct vsc_fw_loader { struct device *dev; struct vsc_tp *tp; const struct firmware *csi; const struct firmware *ace; const struct firmware *cfg; void *tx_buf; void *rx_buf; u16 option; u16 count; char sensor_name[VSC_SENSOR_NAME_MAX_LEN]; struct vsc_img_frag frags[VSC_IMG_FRAG_MAX]; }; static inline u32 vsc_sum_crc(void *data, size_t size) { u32 crc = 0; size_t i; for (i = 0; i < size; i++) crc += *((u8 *)data + i); return crc; } /* get sensor name to construct image name */ static int vsc_get_sensor_name(struct vsc_fw_loader *fw_loader, struct device *dev) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER }; union acpi_object obj = { .integer.type = ACPI_TYPE_INTEGER, .integer.value = 1, }; struct acpi_object_list arg_list = { .count = 1, .pointer = &obj, }; union acpi_object *ret_obj; acpi_handle handle; acpi_status status; int ret = 0; handle = ACPI_HANDLE(dev); if (!handle) return -EINVAL; status = acpi_evaluate_object(handle, "SID", &arg_list, &buffer); if (ACPI_FAILURE(status)) { dev_err(dev, "can't evaluate SID method: %d\n", status); return -ENODEV; } ret_obj = buffer.pointer; if (!ret_obj) { dev_err(dev, "can't locate ACPI buffer\n"); return -ENODEV; } if (ret_obj->type != ACPI_TYPE_STRING) { dev_err(dev, "found non-string entry\n"); ret = -ENODEV; goto out_free_buff; } /* string length excludes trailing NUL */ if (ret_obj->string.length >= sizeof(fw_loader->sensor_name)) { dev_err(dev, "sensor name buffer too small\n"); ret = -EINVAL; goto out_free_buff; } memcpy(fw_loader->sensor_name, ret_obj->string.pointer, ret_obj->string.length); string_lower(fw_loader->sensor_name, fw_loader->sensor_name); out_free_buff: ACPI_FREE(buffer.pointer); return ret; } static int vsc_identify_silicon(struct vsc_fw_loader *fw_loader) { struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf; struct vsc_rom_cmd *cmd = fw_loader->tx_buf; u8 version, sub_version; int ret; /* identify stepping information */ cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DUMP_MEM; cmd->data.dump_mem.addr = cpu_to_le32(VSC_EFUSE_ADDR); cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32)); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); if (ret || ack->token == VSC_TOKEN_ERROR) { dev_err(fw_loader->dev, "CMD_DUMP_MEM error %d token %d\n", ret, ack->token); return ret ?: -EINVAL; } cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_GET_CONT; ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); if (ret || ack->token != VSC_TOKEN_DUMP_RESP) { dev_err(fw_loader->dev, "CMD_GETCONT error %d token %d\n", ret, ack->token); return ret ?: -EINVAL; } version = FIELD_GET(VSC_MAINSTEPPING_VERSION_MASK, ack->payload[0]); sub_version = FIELD_GET(VSC_SUBSTEPPING_VERSION_MASK, ack->payload[0]); if (version != VSC_MAINSTEPPING_VERSION_A) { dev_err(fw_loader->dev, "mainstepping mismatch expected %d got %d\n", VSC_MAINSTEPPING_VERSION_A, version); return -EINVAL; } if (sub_version != VSC_SUBSTEPPING_VERSION_0 && sub_version != VSC_SUBSTEPPING_VERSION_1) { dev_err(fw_loader->dev, "substepping %d is out of supported range %d - %d\n", sub_version, VSC_SUBSTEPPING_VERSION_0, VSC_SUBSTEPPING_VERSION_1); return -EINVAL; } dev_info(fw_loader->dev, "silicon stepping version is %u:%u\n", version, sub_version); /* identify strap information */ cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DUMP_MEM; cmd->data.dump_mem.addr = cpu_to_le32(VSC_STRAP_ADDR); cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32)); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); if (ret) return ret; if (ack->token == VSC_TOKEN_ERROR) return -EINVAL; cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_GET_CONT; ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); if (ret) return ret; if (ack->token != VSC_TOKEN_DUMP_RESP) return -EINVAL; return 0; } static int vsc_identify_csi_image(struct vsc_fw_loader *fw_loader) { const struct firmware *image; struct vsc_fw_sign *sign; struct vsc_img *img; unsigned int i; int ret; ret = request_firmware(&image, VSC_CSI_IMAGE_NAME, fw_loader->dev); if (ret) return ret; img = (struct vsc_img *)image->data; if (!img) { ret = -ENOENT; goto err_release_image; } if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) { ret = -EINVAL; goto err_release_image; } if (le32_to_cpu(img->image_count) != VSC_CSI_IMG_CNT) { ret = -EINVAL; goto err_release_image; } fw_loader->count += le32_to_cpu(img->image_count) - 1; fw_loader->option = FIELD_GET(VSC_BOOT_IMG_OPTION_MASK, le32_to_cpu(img->option)); sign = (struct vsc_fw_sign *) (img->image_location + le32_to_cpu(img->image_count)); for (i = 0; i < VSC_CSI_IMG_CNT; i++) { /* mapping from CSI image index to image code data */ static const struct vsc_image_code_data csi_image_map[] = { { VSC_IMG_BOOTLOADER_FRAG, VSC_IMG_BOOTLOADER_TYPE }, { VSC_IMG_CSI_SEM_FRAG, VSC_IMG_CSI_SEM_TYPE }, { VSC_IMG_CSI_RUNTIME_FRAG, VSC_IMG_CSI_RUNTIME_TYPE }, { VSC_IMG_CSI_EM7D_FRAG, VSC_IMG_CSI_EM7D_TYPE }, }; struct vsc_img_frag *frag; if ((u8 *)sign + sizeof(*sign) > image->data + image->size) { ret = -EINVAL; goto err_release_image; } if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) { ret = -EINVAL; goto err_release_image; } if (!le32_to_cpu(img->image_location[i])) { ret = -EINVAL; goto err_release_image; } frag = &fw_loader->frags[csi_image_map[i].frag_index]; frag->data = sign->image; frag->size = le32_to_cpu(sign->image_size); frag->location = le32_to_cpu(img->image_location[i]); frag->type = csi_image_map[i].image_type; sign = (struct vsc_fw_sign *) (sign->image + le32_to_cpu(sign->image_size)); } fw_loader->csi = image; return 0; err_release_image: release_firmware(image); return ret; } static int vsc_identify_ace_image(struct vsc_fw_loader *fw_loader) { char path[VSC_IMAGE_PATH_MAX_LEN]; const struct firmware *image; struct vsc_fw_sign *sign; struct vsc_img *img; unsigned int i; int ret; snprintf(path, sizeof(path), VSC_ACE_IMAGE_NAME_FMT, fw_loader->sensor_name); ret = request_firmware(&image, path, fw_loader->dev); if (ret) return ret; img = (struct vsc_img *)image->data; if (!img) { ret = -ENOENT; goto err_release_image; } if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) { ret = -EINVAL; goto err_release_image; } if (le32_to_cpu(img->image_count) != VSC_ACE_IMG_CNT) { ret = -EINVAL; goto err_release_image; } fw_loader->count += le32_to_cpu(img->image_count); sign = (struct vsc_fw_sign *) (img->image_location + le32_to_cpu(img->image_count)); for (i = 0; i < VSC_ACE_IMG_CNT; i++) { /* mapping from ACE image index to image code data */ static const struct vsc_image_code_data ace_image_map[] = { { VSC_IMG_ACE_VISION_FRAG, VSC_IMG_ACE_VISION_TYPE }, { VSC_IMG_ACE_CFG_FRAG, VSC_IMG_ACE_CFG_TYPE }, }; struct vsc_img_frag *frag, *last_frag; u8 frag_index; if ((u8 *)sign + sizeof(*sign) > image->data + image->size) { ret = -EINVAL; goto err_release_image; } if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) { ret = -EINVAL; goto err_release_image; } frag_index = ace_image_map[i].frag_index; frag = &fw_loader->frags[frag_index]; frag->data = sign->image; frag->size = le32_to_cpu(sign->image_size); frag->location = le32_to_cpu(img->image_location[i]); frag->type = ace_image_map[i].image_type; if (!frag->location) { last_frag = &fw_loader->frags[frag_index - 1]; frag->location = ALIGN(last_frag->location + last_frag->size, SZ_4K); } sign = (struct vsc_fw_sign *) (sign->image + le32_to_cpu(sign->image_size)); } fw_loader->ace = image; return 0; err_release_image: release_firmware(image); return ret; } static int vsc_identify_cfg_image(struct vsc_fw_loader *fw_loader) { struct vsc_img_frag *frag = &fw_loader->frags[VSC_IMG_SKU_CFG_FRAG]; char path[VSC_IMAGE_PATH_MAX_LEN]; const struct firmware *image; u32 size; int ret; snprintf(path, sizeof(path), VSC_CFG_IMAGE_NAME_FMT, fw_loader->sensor_name); ret = request_firmware(&image, path, fw_loader->dev); if (ret) return ret; /* identify image size */ if (image->size <= sizeof(u32) || image->size > VSC_SKU_MAX_SIZE) { ret = -EINVAL; goto err_release_image; } size = le32_to_cpu(*((__le32 *)image->data)) + sizeof(u32); if (image->size != size) { ret = -EINVAL; goto err_release_image; } frag->data = image->data; frag->size = image->size; frag->type = VSC_IMG_SKU_CFG_TYPE; frag->location = VSC_SKU_CFG_LOCATION; fw_loader->cfg = image; return 0; err_release_image: release_firmware(image); return ret; } static int vsc_download_bootloader(struct vsc_fw_loader *fw_loader) { struct vsc_img_frag *frag = &fw_loader->frags[VSC_IMG_BOOTLOADER_FRAG]; struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf; struct vsc_rom_cmd *cmd = fw_loader->tx_buf; u32 len, c_len; size_t remain; const u8 *p; int ret; cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_QUERY; ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); if (ret) return ret; if (ack->token != VSC_TOKEN_DUMP_RESP && ack->token != VSC_TOKEN_BOOTLOADER_REQ) return -EINVAL; cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DL_START; cmd->data.dl_start.option = cpu_to_le16(fw_loader->option); cmd->data.dl_start.img_type = frag->type; cmd->data.dl_start.img_len = cpu_to_le32(frag->size); cmd->data.dl_start.img_loc = cpu_to_le32(frag->location); c_len = offsetof(struct vsc_rom_cmd, data.dl_start.crc); cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_ROM_PKG_SIZE); if (ret) return ret; p = frag->data; remain = frag->size; /* download image data */ while (remain > 0) { len = min(remain, sizeof(cmd->data.dl_cont.payload)); cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DL_CONT; cmd->data.dl_cont.len = cpu_to_le16(len); cmd->data.dl_cont.end_flag = remain == len; memcpy(cmd->data.dl_cont.payload, p, len); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_ROM_PKG_SIZE); if (ret) return ret; p += len; remain -= len; } return 0; } static int vsc_download_firmware(struct vsc_fw_loader *fw_loader) { struct vsc_fw_cmd *cmd = fw_loader->tx_buf; unsigned int i, index = 0; u32 c_len; int ret; cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DL_SET; cmd->data.dl_set.img_cnt = cpu_to_le16(fw_loader->count); put_unaligned_le16(fw_loader->option, &cmd->data.dl_set.option); for (i = VSC_IMG_CSI_SEM_FRAG; i <= VSC_IMG_CSI_EM7D_FRAG; i++) { struct vsc_img_frag *frag = &fw_loader->frags[i]; cmd->data.dl_set.payload[index++] = cpu_to_le32(frag->location); cmd->data.dl_set.payload[index++] = cpu_to_le32(frag->size); } c_len = offsetof(struct vsc_fw_cmd, data.dl_set.payload[index]); cmd->data.dl_set.payload[index] = cpu_to_le32(vsc_sum_crc(cmd, c_len)); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); if (ret) return ret; for (i = VSC_IMG_CSI_SEM_FRAG; i < VSC_IMG_FRAG_MAX; i++) { struct vsc_img_frag *frag = &fw_loader->frags[i]; const u8 *p; u32 remain; cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_DL_START; cmd->data.dl_start.img_type = frag->type; cmd->data.dl_start.img_len = cpu_to_le32(frag->size); cmd->data.dl_start.img_loc = cpu_to_le32(frag->location); put_unaligned_le16(fw_loader->option, &cmd->data.dl_start.option); c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc); cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); if (ret) return ret; p = frag->data; remain = frag->size; /* download image data */ while (remain > 0) { u32 len = min(remain, VSC_FW_PKG_SIZE); memcpy(fw_loader->tx_buf, p, len); memset(fw_loader->tx_buf + len, 0, VSC_FW_PKG_SIZE - len); ret = vsc_tp_rom_xfer(fw_loader->tp, fw_loader->tx_buf, NULL, VSC_FW_PKG_SIZE); if (ret) break; p += len; remain -= len; } } cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); cmd->cmd_id = VSC_CMD_CAM_BOOT; c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc); cmd->data.boot.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); return vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); } /** * vsc_tp_init - init vsc_tp * @tp: vsc_tp device handle * @dev: device node for mei vsc device * Return: 0 in case of success, negative value in case of error */ int vsc_tp_init(struct vsc_tp *tp, struct device *dev) { struct vsc_fw_loader *fw_loader __free(kfree) = NULL; void *tx_buf __free(kfree) = NULL; void *rx_buf __free(kfree) = NULL; int ret; fw_loader = kzalloc(sizeof(*fw_loader), GFP_KERNEL); if (!fw_loader) return -ENOMEM; tx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL); if (!tx_buf) return -ENOMEM; rx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL); if (!rx_buf) return -ENOMEM; fw_loader->tx_buf = tx_buf; fw_loader->rx_buf = rx_buf; fw_loader->tp = tp; fw_loader->dev = dev; ret = vsc_get_sensor_name(fw_loader, dev); if (ret) return ret; ret = vsc_identify_silicon(fw_loader); if (ret) return ret; ret = vsc_identify_csi_image(fw_loader); if (ret) return ret; ret = vsc_identify_ace_image(fw_loader); if (ret) goto err_release_csi; ret = vsc_identify_cfg_image(fw_loader); if (ret) goto err_release_ace; ret = vsc_download_bootloader(fw_loader); if (!ret) ret = vsc_download_firmware(fw_loader); release_firmware(fw_loader->cfg); err_release_ace: release_firmware(fw_loader->ace); err_release_csi: release_firmware(fw_loader->csi); return ret; } EXPORT_SYMBOL_NS_GPL(vsc_tp_init, "VSC_TP");