// SPDX-License-Identifier: GPL-2.0
|
/*
|
* Support for Intel Camera Imaging ISP subsystem.
|
* Copyright (c) 2015, Intel Corporation.
|
*
|
* This program is free software; you can redistribute it and/or modify it
|
* under the terms and conditions of the GNU General Public License,
|
* version 2, as published by the Free Software Foundation.
|
*
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
* more details.
|
*/
|
|
#include <linux/string.h> /* for memcpy() */
|
#include <linux/slab.h>
|
#include <linux/vmalloc.h>
|
|
#include "hmm.h"
|
|
#include <math_support.h>
|
#include "platform_support.h"
|
#include "sh_css_firmware.h"
|
|
#include "sh_css_defs.h"
|
#include "ia_css_debug.h"
|
#include "sh_css_internal.h"
|
#include "ia_css_isp_param.h"
|
|
#include "assert_support.h"
|
|
#include "isp.h" /* PMEM_WIDTH_LOG2 */
|
|
#include "ia_css_isp_params.h"
|
#include "ia_css_isp_configs.h"
|
#include "ia_css_isp_states.h"
|
|
#define _STR(x) #x
|
#define STR(x) _STR(x)
|
|
struct firmware_header {
|
struct sh_css_fw_bi_file_h file_header;
|
struct ia_css_fw_info binary_header;
|
};
|
|
struct fw_param {
|
const char *name;
|
const void *buffer;
|
};
|
|
static struct firmware_header *firmware_header;
|
|
/*
|
* The string STR is a place holder
|
* which will be replaced with the actual RELEASE_VERSION
|
* during package generation. Please do not modify
|
*/
|
static const char *isp2400_release_version = STR(irci_stable_candrpv_0415_20150521_0458);
|
static const char *isp2401_release_version = STR(irci_ecr - master_20150911_0724);
|
|
#define MAX_FW_REL_VER_NAME 300
|
static char FW_rel_ver_name[MAX_FW_REL_VER_NAME] = "---";
|
|
struct ia_css_fw_info sh_css_sp_fw;
|
struct ia_css_blob_descr *sh_css_blob_info; /* Only ISP blob info (no SP) */
|
unsigned int sh_css_num_binaries; /* This includes 1 SP binary */
|
|
static struct fw_param *fw_minibuffer;
|
|
char *sh_css_get_fw_version(void)
|
{
|
return FW_rel_ver_name;
|
}
|
|
/*
|
* Split the loaded firmware into blobs
|
*/
|
|
/* Setup sp/sp1 binary */
|
static int
|
setup_binary(struct ia_css_fw_info *fw, const char *fw_data,
|
struct ia_css_fw_info *sh_css_fw, unsigned int binary_id)
|
{
|
const char *blob_data;
|
|
if ((!fw) || (!fw_data))
|
return -EINVAL;
|
|
blob_data = fw_data + fw->blob.offset;
|
|
*sh_css_fw = *fw;
|
|
sh_css_fw->blob.code = vmalloc(fw->blob.size);
|
if (!sh_css_fw->blob.code)
|
return -ENOMEM;
|
|
memcpy((void *)sh_css_fw->blob.code, blob_data, fw->blob.size);
|
sh_css_fw->blob.data = (char *)sh_css_fw->blob.code + fw->blob.data_source;
|
fw_minibuffer[binary_id].buffer = sh_css_fw->blob.code;
|
|
return 0;
|
}
|
|
int
|
sh_css_load_blob_info(const char *fw, const struct ia_css_fw_info *bi,
|
struct ia_css_blob_descr *bd,
|
unsigned int index)
|
{
|
const char *name;
|
const unsigned char *blob;
|
|
if ((!fw) || (!bd))
|
return -EINVAL;
|
|
/* Special case: only one binary in fw */
|
if (!bi)
|
bi = (const struct ia_css_fw_info *)fw;
|
|
name = fw + bi->blob.prog_name_offset;
|
blob = (const unsigned char *)fw + bi->blob.offset;
|
|
/* sanity check */
|
if (bi->blob.size !=
|
bi->blob.text_size + bi->blob.icache_size +
|
bi->blob.data_size + bi->blob.padding_size) {
|
/* sanity check, note the padding bytes added for section to DDR alignment */
|
return -EINVAL;
|
}
|
|
if ((bi->blob.offset % (1UL << (ISP_PMEM_WIDTH_LOG2 - 3))) != 0)
|
return -EINVAL;
|
|
bd->blob = blob;
|
bd->header = *bi;
|
|
if (bi->type == ia_css_isp_firmware || bi->type == ia_css_sp_firmware) {
|
char *namebuffer;
|
|
namebuffer = kstrdup(name, GFP_KERNEL);
|
if (!namebuffer)
|
return -ENOMEM;
|
bd->name = fw_minibuffer[index].name = namebuffer;
|
} else {
|
bd->name = name;
|
}
|
|
if (bi->type == ia_css_isp_firmware) {
|
size_t paramstruct_size = sizeof(struct ia_css_memory_offsets);
|
size_t configstruct_size = sizeof(struct ia_css_config_memory_offsets);
|
size_t statestruct_size = sizeof(struct ia_css_state_memory_offsets);
|
|
char *parambuf = kmalloc(paramstruct_size + configstruct_size +
|
statestruct_size,
|
GFP_KERNEL);
|
if (!parambuf)
|
return -ENOMEM;
|
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_PARAM].ptr = NULL;
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_CONFIG].ptr = NULL;
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_STATE].ptr = NULL;
|
|
fw_minibuffer[index].buffer = parambuf;
|
|
/* copy ia_css_memory_offsets */
|
memcpy(parambuf, (void *)(fw +
|
bi->blob.memory_offsets.offsets[IA_CSS_PARAM_CLASS_PARAM]),
|
paramstruct_size);
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_PARAM].ptr = parambuf;
|
|
/* copy ia_css_config_memory_offsets */
|
memcpy(parambuf + paramstruct_size,
|
(void *)(fw + bi->blob.memory_offsets.offsets[IA_CSS_PARAM_CLASS_CONFIG]),
|
configstruct_size);
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_CONFIG].ptr = parambuf +
|
paramstruct_size;
|
|
/* copy ia_css_state_memory_offsets */
|
memcpy(parambuf + paramstruct_size + configstruct_size,
|
(void *)(fw + bi->blob.memory_offsets.offsets[IA_CSS_PARAM_CLASS_STATE]),
|
statestruct_size);
|
bd->mem_offsets.array[IA_CSS_PARAM_CLASS_STATE].ptr = parambuf +
|
paramstruct_size + configstruct_size;
|
}
|
return 0;
|
}
|
|
bool
|
sh_css_check_firmware_version(struct device *dev, const char *fw_data)
|
{
|
struct sh_css_fw_bi_file_h *file_header;
|
|
const char *release_version;
|
|
if (!IS_ISP2401)
|
release_version = isp2400_release_version;
|
else
|
release_version = isp2401_release_version;
|
|
firmware_header = (struct firmware_header *)fw_data;
|
file_header = &firmware_header->file_header;
|
|
if (strcmp(file_header->version, release_version) != 0) {
|
dev_err(dev, "Firmware version may not be compatible with this driver\n");
|
dev_err(dev, "Expecting version '%s', but firmware is '%s'.\n",
|
release_version, file_header->version);
|
}
|
|
/* For now, let's just accept a wrong version, even if wrong */
|
return false;
|
}
|
|
static const char * const fw_type_name[] = {
|
[ia_css_sp_firmware] = "SP",
|
[ia_css_isp_firmware] = "ISP",
|
[ia_css_bootloader_firmware] = "BootLoader",
|
[ia_css_acc_firmware] = "accel",
|
};
|
|
static const char * const fw_acc_type_name[] = {
|
[IA_CSS_ACC_NONE] = "Normal",
|
[IA_CSS_ACC_OUTPUT] = "Accel for output",
|
[IA_CSS_ACC_VIEWFINDER] = "Accel for viewfinder",
|
[IA_CSS_ACC_STANDALONE] = "Stand-alone accel",
|
};
|
|
int
|
sh_css_load_firmware(struct device *dev, const char *fw_data,
|
unsigned int fw_size)
|
{
|
unsigned int i;
|
struct ia_css_fw_info *binaries;
|
struct sh_css_fw_bi_file_h *file_header;
|
int ret;
|
const char *release_version;
|
|
if (!IS_ISP2401)
|
release_version = isp2400_release_version;
|
else
|
release_version = isp2401_release_version;
|
|
firmware_header = (struct firmware_header *)fw_data;
|
file_header = &firmware_header->file_header;
|
binaries = &firmware_header->binary_header;
|
strscpy(FW_rel_ver_name, file_header->version,
|
min(sizeof(FW_rel_ver_name), sizeof(file_header->version)));
|
ret = sh_css_check_firmware_version(dev, fw_data);
|
if (ret) {
|
IA_CSS_ERROR("CSS code version (%s) and firmware version (%s) mismatch!",
|
file_header->version, release_version);
|
return -EINVAL;
|
} else {
|
IA_CSS_LOG("successfully load firmware version %s", release_version);
|
}
|
|
/* some sanity checks */
|
if (!fw_data || fw_size < sizeof(struct sh_css_fw_bi_file_h))
|
return -EINVAL;
|
|
if (file_header->h_size != sizeof(struct sh_css_fw_bi_file_h))
|
return -EINVAL;
|
|
sh_css_num_binaries = file_header->binary_nr;
|
/* Only allocate memory for ISP blob info */
|
if (sh_css_num_binaries > NUM_OF_SPS) {
|
sh_css_blob_info = kmalloc(
|
(sh_css_num_binaries - NUM_OF_SPS) *
|
sizeof(*sh_css_blob_info), GFP_KERNEL);
|
if (!sh_css_blob_info)
|
return -ENOMEM;
|
} else {
|
sh_css_blob_info = NULL;
|
}
|
|
fw_minibuffer = kcalloc(sh_css_num_binaries, sizeof(struct fw_param),
|
GFP_KERNEL);
|
if (!fw_minibuffer)
|
return -ENOMEM;
|
|
for (i = 0; i < sh_css_num_binaries; i++) {
|
struct ia_css_fw_info *bi = &binaries[i];
|
/*
|
* note: the var below is made static as it is quite large;
|
* if it is not static it ends up on the stack which could
|
* cause issues for drivers
|
*/
|
static struct ia_css_blob_descr bd;
|
int err;
|
|
err = sh_css_load_blob_info(fw_data, bi, &bd, i);
|
|
if (err)
|
return -EINVAL;
|
|
if (bi->blob.offset + bi->blob.size > fw_size)
|
return -EINVAL;
|
|
switch (bd.header.type) {
|
case ia_css_isp_firmware:
|
if (bd.header.info.isp.type > IA_CSS_ACC_STANDALONE) {
|
dev_err(dev, "binary #%2d: invalid SP type\n",
|
i);
|
return -EINVAL;
|
}
|
|
dev_dbg(dev,
|
"binary #%-2d type %s (%s), binary id is %2d: %s\n",
|
i,
|
fw_type_name[bd.header.type],
|
fw_acc_type_name[bd.header.info.isp.type],
|
bd.header.info.isp.sp.id,
|
bd.name);
|
break;
|
case ia_css_sp_firmware:
|
case ia_css_bootloader_firmware:
|
case ia_css_acc_firmware:
|
dev_dbg(dev,
|
"binary #%-2d type %s: %s\n",
|
i, fw_type_name[bd.header.type],
|
bd.name);
|
break;
|
default:
|
if (bd.header.info.isp.type > IA_CSS_ACC_STANDALONE) {
|
dev_err(dev,
|
"binary #%2d: invalid firmware type\n",
|
i);
|
return -EINVAL;
|
}
|
break;
|
}
|
|
if (bi->type == ia_css_sp_firmware) {
|
if (i != SP_FIRMWARE)
|
return -EINVAL;
|
err = setup_binary(bi, fw_data, &sh_css_sp_fw, i);
|
if (err)
|
return err;
|
|
} else {
|
/*
|
* All subsequent binaries
|
* (including bootloaders) (i>NUM_OF_SPS)
|
* are ISP firmware
|
*/
|
if (i < NUM_OF_SPS)
|
return -EINVAL;
|
|
if (bi->type != ia_css_isp_firmware)
|
return -EINVAL;
|
if (!sh_css_blob_info) /* cannot happen but KW does not see this */
|
return -EINVAL;
|
sh_css_blob_info[i - NUM_OF_SPS] = bd;
|
}
|
}
|
|
return 0;
|
}
|
|
void sh_css_unload_firmware(void)
|
{
|
/* release firmware minibuffer */
|
if (fw_minibuffer) {
|
unsigned int i = 0;
|
|
for (i = 0; i < sh_css_num_binaries; i++) {
|
if (fw_minibuffer[i].name)
|
kfree((void *)fw_minibuffer[i].name);
|
if (fw_minibuffer[i].buffer)
|
vfree((void *)fw_minibuffer[i].buffer);
|
}
|
kfree(fw_minibuffer);
|
fw_minibuffer = NULL;
|
}
|
|
memset(&sh_css_sp_fw, 0, sizeof(sh_css_sp_fw));
|
kfree(sh_css_blob_info);
|
sh_css_blob_info = NULL;
|
sh_css_num_binaries = 0;
|
}
|
|
ia_css_ptr
|
sh_css_load_blob(const unsigned char *blob, unsigned int size)
|
{
|
ia_css_ptr target_addr = hmm_alloc(size, HMM_BO_PRIVATE, 0, NULL, 0);
|
/*
|
* this will allocate memory aligned to a DDR word boundary which
|
* is required for the CSS DMA to read the instructions.
|
*/
|
|
assert(blob);
|
if (target_addr)
|
hmm_store(target_addr, blob, size);
|
return target_addr;
|
}
|