// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/firmware.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/soc/qcom/mdt_loader.h>
#include "iris_core.h"
#include "iris_firmware.h"
#define IRIS_PAS_ID 9
#define MAX_FIRMWARE_NAME_SIZE 128
/* Detect Gen2 firmware by scanning the blob for:
* QC_IMAGE_VERSION_STRING=<version>
* and then checking:
* - version starts with "vfw", OR
* - version matches "video-firmware.N.M" with N >= 2
*/
static bool iris_detect_gen2_from_fwdata(const u8 *data, size_t size)
{
static const char *marker = "QC_IMAGE_VERSION_STRING=";
const size_t mlen = strlen(marker);
static const char *vfw = "vfw";
const size_t vfwlen = strlen(vfw);
static const char *vf = "video-firmware.";
const size_t vflen = strlen(vf);
for (size_t i = 0; i + mlen < size; i++) {
const char *found;
if (memcmp(data + i, marker, mlen))
continue;
found = data + i + mlen;
size -= i + mlen;
/* vfw => Gen2 */
if (size > vfwlen && !memcmp(found, vfw, vfwlen))
return true;
if (size < vflen ||
memcmp(found, vf, vflen))
return false;
found += vflen;
size -= vflen;
/*
* video-firmware.1.x is Gen1.
* video-firmware.2.x and video-firmware.10.x are Gen2.
*/
return size >= 2 &&
(*found >= '2' || (*found == '1' && found[1] != '.'));
}
return false;
}
static const struct firmware *iris_detect_firmware(struct iris_core *core,
const char **fw_name)
{
const struct firmware *firmware;
bool has_both_gens;
int ret;
*fw_name = NULL;
if (core->iris_platform_data->firmware_desc_gen2)
core->iris_firmware_desc = core->iris_platform_data->firmware_desc_gen2;
else if (core->iris_platform_data->firmware_desc_gen1)
core->iris_firmware_desc = core->iris_platform_data->firmware_desc_gen1;
else
return ERR_PTR(-EINVAL);
has_both_gens = core->iris_platform_data->firmware_desc_gen2 &&
core->iris_platform_data->firmware_desc_gen1;
ret = of_property_read_string_index(dev_of_node(core->dev), "firmware-name", 0, fw_name);
if (ret) {
*fw_name = core->iris_firmware_desc->fwname;
ret = request_firmware(&firmware, *fw_name, core->dev);
if (ret && has_both_gens) {
core->iris_firmware_desc = core->iris_platform_data->firmware_desc_gen1;
*fw_name = core->iris_firmware_desc->fwname;
ret = request_firmware(&firmware, *fw_name, core->dev);
}
return ret ? ERR_PTR(ret) : firmware;
}
ret = request_firmware(&firmware, *fw_name, core->dev);
if (ret)
return ERR_PTR(ret);
if (has_both_gens &&
!iris_detect_gen2_from_fwdata((const u8 *)firmware->data, firmware->size)) {
dev_info(core->dev, "Gen1 FW detected in %s\n", *fw_name);
core->iris_firmware_desc = core->iris_platform_data->firmware_desc_gen1;
}
return firmware;
}
static int iris_load_fw_to_memory(struct iris_core *core)
{
const struct firmware *firmware = NULL;
struct device *dev = core->dev;
struct resource res;
phys_addr_t mem_phys;
const char *fw_name;
size_t res_size;
ssize_t fw_size;
void *mem_virt;
int ret;
ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
if (ret)
return ret;
mem_phys = res.start;
res_size = resource_size(&res);
firmware = iris_detect_firmware(core, &fw_name);
if (IS_ERR(firmware))
return PTR_ERR(firmware);
core->iris_firmware_data = core->iris_firmware_desc->firmware_data;
fw_size = qcom_mdt_get_size(firmware);
if (fw_size < 0 || res_size < (size_t)fw_size) {
ret = -EINVAL;
goto err_release_fw;
}
mem_virt = memremap(mem_phys, res_size, MEMREMAP_WC);
if (!mem_virt) {
ret = -ENOMEM;
goto err_release_fw;
}
ret = qcom_mdt_load(dev, firmware, fw_name,
IRIS_PAS_ID, mem_virt, mem_phys, res_size, NULL);
memunmap(mem_virt);
err_release_fw:
release_firmware(firmware);
return ret;
}
int iris_fw_load(struct iris_core *core)
{
const struct tz_cp_config *cp_config;
int i, ret;
ret = iris_load_fw_to_memory(core);
if (ret) {
dev_err(core->dev, "firmware download failed %d\n", ret);
return ret;
}
ret = qcom_pas_auth_and_reset(IRIS_PAS_ID);
if (ret) {
dev_err(core->dev, "auth and reset failed: %d\n", ret);
return ret;
}
for (i = 0; i < core->iris_platform_data->tz_cp_config_data_size; i++) {
cp_config = &core->iris_platform_data->tz_cp_config_data[i];
ret = qcom_scm_mem_protect_video_var(cp_config->cp_start,
cp_config->cp_size,
cp_config->cp_nonpixel_start,
cp_config->cp_nonpixel_size);
if (ret) {
dev_err(core->dev, "qcom_scm_mem_protect_video_var failed: %d\n", ret);
qcom_pas_shutdown(IRIS_PAS_ID);
return ret;
}
}
return 0;
}
int iris_fw_unload(struct iris_core *core)
{
return qcom_pas_shutdown(IRIS_PAS_ID);
}
int iris_set_hw_state(struct iris_core *core, bool resume)
{
return qcom_pas_set_remote_state(resume, 0);
}