| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2023, Intel Corporation. | |
| * Intel Visual Sensing Controller Transport Layer Linux driver | |
| */ | |
| #include <linux/acpi.h> | |
| #include <linux/align.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/firmware.h> | |
| #include <linux/sizes.h> | |
| #include <linux/slab.h> | |
| #include <linux/string_helpers.h> | |
| #include <linux/types.h> | |
| #include <linux/unaligned.h> | |
| #include "vsc-tp.h" | |
| #define VSC_MAGIC_NUM 0x49505343 /* IPSC */ | |
| #define VSC_MAGIC_FW 0x49574653 /* IWFS */ | |
| #define VSC_MAGIC_FILE 0x46564353 /* FVCS */ | |
| #define VSC_ADDR_BASE 0xE0030000 | |
| #define VSC_EFUSE_ADDR (VSC_ADDR_BASE + 0x038) | |
| #define VSC_STRAP_ADDR (VSC_ADDR_BASE + 0x100) | |
| #define VSC_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_obj(*fw_loader); | |
| 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"); |