root/drivers/misc/mei/vsc-fw-loader.c
// 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");