blob: ffa4ccd96a10430a5e477869e9d093be59579729 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023, Intel Corporation.
* Intel Visual Sensing Controller Transport Layer Linux driver
*/
#include <linux/acpi.h>
#include <linux/align.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/firmware.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
#include <linux/types.h>
#include <asm-generic/unaligned.h>
#include "vsc-tp.h"
#define VSC_MAGIC_NUM 0x49505343 /* IPSC */
#define VSC_MAGIC_FW 0x49574653 /* IWFS */
#define VSC_MAGIC_FILE 0x46564353 /* FVCS */
#define VSC_ADDR_BASE 0xE0030000
#define VSC_EFUSE_ADDR (VSC_ADDR_BASE + 0x038)
#define VSC_STRAP_ADDR (VSC_ADDR_BASE + 0x100)
#define VSC_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 fimware
* @tp: transport layer used with the firmware loader
* @csi: CSI image
* @ace: ACE image
* @cfg: config image
* @tx_buf: tx buffer
* @rx_buf: rx buffer
* @option: command option
* @count: total image count
* @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 = {
.type = ACPI_TYPE_INTEGER,
.integer.value = 1,
};
struct acpi_object_list arg_list = {
.count = 1,
.pointer = &obj,
};
union acpi_object *ret_obj;
acpi_handle handle;
acpi_status status;
int ret = 0;
handle = ACPI_HANDLE(dev);
if (!handle)
return -EINVAL;
status = acpi_evaluate_object(handle, "SID", &arg_list, &buffer);
if (ACPI_FAILURE(status)) {
dev_err(dev, "can't evaluate SID method: %d\n", status);
return -ENODEV;
}
ret_obj = buffer.pointer;
if (!ret_obj) {
dev_err(dev, "can't locate ACPI buffer\n");
return -ENODEV;
}
if (ret_obj->type != ACPI_TYPE_STRING) {
dev_err(dev, "found non-string entry\n");
ret = -ENODEV;
goto out_free_buff;
}
/* string length excludes trailing NUL */
if (ret_obj->string.length >= sizeof(fw_loader->sensor_name)) {
dev_err(dev, "sensor name buffer too small\n");
ret = -EINVAL;
goto out_free_buff;
}
memcpy(fw_loader->sensor_name, ret_obj->string.pointer,
ret_obj->string.length);
string_lower(fw_loader->sensor_name, fw_loader->sensor_name);
out_free_buff:
ACPI_FREE(buffer.pointer);
return ret;
}
static int vsc_identify_silicon(struct vsc_fw_loader *fw_loader)
{
struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf;
struct vsc_rom_cmd *cmd = fw_loader->tx_buf;
u8 version, sub_version;
int ret;
/* identify stepping information */
cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
cmd->cmd_id = VSC_CMD_DUMP_MEM;
cmd->data.dump_mem.addr = cpu_to_le32(VSC_EFUSE_ADDR);
cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32));
ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE);
if (ret)
return ret;
if (ack->token == VSC_TOKEN_ERROR)
return -EINVAL;
cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
cmd->cmd_id = VSC_CMD_GET_CONT;
ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE);
if (ret)
return ret;
if (ack->token != VSC_TOKEN_DUMP_RESP)
return -EINVAL;
version = FIELD_GET(VSC_MAINSTEPPING_VERSION_MASK, ack->payload[0]);
sub_version = FIELD_GET(VSC_SUBSTEPPING_VERSION_MASK, ack->payload[0]);
if (version != VSC_MAINSTEPPING_VERSION_A)
return -EINVAL;
if (sub_version != VSC_SUBSTEPPING_VERSION_0 &&
sub_version != VSC_SUBSTEPPING_VERSION_1)
return -EINVAL;
dev_info(fw_loader->dev, "silicon stepping version is %u:%u\n",
version, sub_version);
/* identify strap information */
cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
cmd->cmd_id = VSC_CMD_DUMP_MEM;
cmd->data.dump_mem.addr = cpu_to_le32(VSC_STRAP_ADDR);
cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32));
ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE);
if (ret)
return ret;
if (ack->token == VSC_TOKEN_ERROR)
return -EINVAL;
cmd->magic = cpu_to_le32(VSC_MAGIC_NUM);
cmd->cmd_id = VSC_CMD_GET_CONT;
ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE);
if (ret)
return ret;
if (ack->token != VSC_TOKEN_DUMP_RESP)
return -EINVAL;
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);