diff options
Diffstat (limited to 'drivers/dpll/zl3073x/fw.c')
-rw-r--r-- | drivers/dpll/zl3073x/fw.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/fw.c b/drivers/dpll/zl3073x/fw.c new file mode 100644 index 000000000000..d5418ff74886 --- /dev/null +++ b/drivers/dpll/zl3073x/fw.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/array_size.h> +#include <linux/build_bug.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/netlink.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include "core.h" +#include "flash.h" +#include "fw.h" + +#define ZL3073X_FW_ERR_PFX "FW load failed: " +#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg, \ + ## __VA_ARGS__) + +enum zl3073x_flash_type { + ZL3073X_FLASH_TYPE_NONE = 0, + ZL3073X_FLASH_TYPE_SECTORS, + ZL3073X_FLASH_TYPE_PAGE, + ZL3073X_FLASH_TYPE_PAGE_AND_COPY, +}; + +struct zl3073x_fw_component_info { + const char *name; + size_t max_size; + enum zl3073x_flash_type flash_type; + u32 load_addr; + u32 dest_page; + u32 copy_page; +}; + +static const struct zl3073x_fw_component_info component_info[] = { + [ZL_FW_COMPONENT_UTIL] = { + .name = "utility", + .max_size = 0x2300, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_NONE, + }, + [ZL_FW_COMPONENT_FW1] = { + .name = "firmware1", + .max_size = 0x35000, + .load_addr = 0x20002000, + .flash_type = ZL3073X_FLASH_TYPE_SECTORS, + .dest_page = 0x020, + }, + [ZL_FW_COMPONENT_FW2] = { + .name = "firmware2", + .max_size = 0x0040, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e0, + .copy_page = 0x000, + }, + [ZL_FW_COMPONENT_FW3] = { + .name = "firmware3", + .max_size = 0x0248, + .load_addr = 0x20000400, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e4, + .copy_page = 0x004, + }, + [ZL_FW_COMPONENT_CFG0] = { + .name = "config0", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3d0, + }, + [ZL_FW_COMPONENT_CFG1] = { + .name = "config1", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3c0, + }, + [ZL_FW_COMPONENT_CFG2] = { + .name = "config2", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3b0, + }, + [ZL_FW_COMPONENT_CFG3] = { + .name = "config3", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3a0, + }, + [ZL_FW_COMPONENT_CFG4] = { + .name = "config4", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x390, + }, + [ZL_FW_COMPONENT_CFG5] = { + .name = "config5", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x380, + }, + [ZL_FW_COMPONENT_CFG6] = { + .name = "config6", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x370, + }, +}; + +/* Sanity check */ +static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS); + +/** + * zl3073x_fw_component_alloc - Alloc structure to hold firmware component + * @size: size of buffer to store data + * + * Return: pointer to allocated component structure or NULL on error. + */ +static struct zl3073x_fw_component * +zl3073x_fw_component_alloc(size_t size) +{ + struct zl3073x_fw_component *comp; + + comp = kzalloc(sizeof(*comp), GFP_KERNEL); + if (!comp) + return NULL; + + comp->size = size; + comp->data = kzalloc(size, GFP_KERNEL); + if (!comp->data) { + kfree(comp); + return NULL; + } + + return comp; +} + +/** + * zl3073x_fw_component_free - Free allocated component structure + * @comp: pointer to allocated component + */ +static void +zl3073x_fw_component_free(struct zl3073x_fw_component *comp) +{ + if (comp) + kfree(comp->data); + + kfree(comp); +} + +/** + * zl3073x_fw_component_id_get - Get ID for firmware component name + * @name: input firmware component name + * + * Return: + * - ZL3073X_FW_COMPONENT_* ID for known component name + * - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown + */ +static enum zl3073x_fw_component_id +zl3073x_fw_component_id_get(const char *name) +{ + enum zl3073x_fw_component_id id; + + for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++) + if (!strcasecmp(name, component_info[id].name)) + return id; + + return ZL_FW_COMPONENT_INVALID; +} + +/** + * zl3073x_fw_component_load - Load component from firmware source + * @zldev: zl3073x device structure + * @pcomp: pointer to loaded component + * @psrc: data pointer to load component from + * @psize: remaining bytes in buffer + * @extack: netlink extack pointer to report errors + * + * The function allocates single firmware component and loads the data from + * the buffer specified by @psrc and @psize. Pointer to allocated component + * is stored in output @pcomp. Source data pointer @psrc and remaining bytes + * @psize are updated accordingly. + * + * Return: + * * 1 when component was allocated and loaded + * * 0 when there is no component to load + * * <0 on error + */ +static ssize_t +zl3073x_fw_component_load(struct zl3073x_dev *zldev, + struct zl3073x_fw_component **pcomp, + const char **psrc, size_t *psize, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + struct zl3073x_fw_component *comp = NULL; + struct device *dev = zldev->dev; + enum zl3073x_fw_component_id id; + char buf[32], name[16]; + u32 count, size, *dest; + int pos, rc; + + /* Fetch image name and size from input */ + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%15s %u %n", name, &count, &pos); + if (!rc) { + /* No more data */ + return 0; + } else if (rc == 1 || count > U32_MAX / sizeof(u32)) { + ZL3073X_FW_ERR_MSG(extack, "invalid component size"); + return -EINVAL; + } + *psrc += pos; + *psize -= pos; + + dev_dbg(dev, "Firmware component '%s' found\n", name); + + id = zl3073x_fw_component_id_get(name); + if (id == ZL_FW_COMPONENT_INVALID) { + ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name); + return -EINVAL; + } + + info = &component_info[id]; + size = count * sizeof(u32); /* get size in bytes */ + + /* Check image size validity */ + if (size > component_info[id].max_size) { + ZL3073X_FW_ERR_MSG(extack, + "[%s] component is too big (%u bytes)\n", + info->name, size); + return -EINVAL; + } + + dev_dbg(dev, "Indicated component image size: %u bytes\n", size); + + /* Alloc component */ + comp = zl3073x_fw_component_alloc(size); + if (!comp) { + ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory"); + return -ENOMEM; + } + comp->id = id; + + /* Load component data from firmware source */ + for (dest = comp->data; count; count--, dest++) { + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%x %n", dest, &pos); + if (!rc) + goto err_data; + + *psrc += pos; + *psize -= pos; + } + + *pcomp = comp; + + return 1; + +err_data: + ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name); + + zl3073x_fw_component_free(comp); + + return -ENODATA; +} + +/** + * zl3073x_fw_free - Free allocated firmware + * @fw: firmware pointer + * + * The function frees existing firmware allocated by @zl3073x_fw_load. + */ +void zl3073x_fw_free(struct zl3073x_fw *fw) +{ + size_t i; + + if (!fw) + return; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) + zl3073x_fw_component_free(fw->component[i]); + + kfree(fw); +} + +/** + * zl3073x_fw_load - Load all components from source + * @zldev: zl3073x device structure + * @data: source buffer pointer + * @size: size of source buffer + * @extack: netlink extack pointer to report errors + * + * The functions allocate firmware structure and loads all components from + * the given buffer specified by @data and @size. + * + * Return: pointer to firmware on success, error pointer on error + */ +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *comp; + enum zl3073x_fw_component_id id; + struct zl3073x_fw *fw; + ssize_t rc; + + /* Allocate firmware structure */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); + if (!fw) + return ERR_PTR(-ENOMEM); + + do { + /* Load single component */ + rc = zl3073x_fw_component_load(zldev, &comp, &data, &size, + extack); + if (rc <= 0) + /* Everything was read or error occurred */ + break; + + id = comp->id; + + /* Report error if the given component is present twice + * or more. + */ + if (fw->component[id]) { + ZL3073X_FW_ERR_MSG(extack, + "duplicate component '%s' detected", + component_info[id].name); + zl3073x_fw_component_free(comp); + rc = -EINVAL; + break; + } + + fw->component[id] = comp; + } while (true); + + if (rc) { + /* Free allocated firmware in case of error */ + zl3073x_fw_free(fw); + return ERR_PTR(rc); + } + + return fw; +} + +/** + * zl3073x_flash_bundle_flash - Flash all components + * @zldev: zl3073x device structure + * @components: pointer to components array + * @extack: netlink extack pointer to report errors + * + * Returns 0 in case of success or negative number otherwise. + */ +static int +zl3073x_fw_component_flash(struct zl3073x_dev *zldev, + struct zl3073x_fw_component *comp, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + int rc; + + info = &component_info[comp->id]; + + switch (info->flash_type) { + case ZL3073X_FLASH_TYPE_NONE: + /* Non-flashable component - used for utility */ + return 0; + case ZL3073X_FLASH_TYPE_SECTORS: + rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page, + info->load_addr, comp->data, + comp->size, extack); + break; + case ZL3073X_FLASH_TYPE_PAGE: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + break; + case ZL3073X_FLASH_TYPE_PAGE_AND_COPY: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + if (!rc) + rc = zl3073x_flash_page_copy(zldev, info->name, + info->dest_page, + info->copy_page, extack); + break; + } + if (rc) + ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'", + info->name); + + return rc; +} + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + int i, rc = 0; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) { + if (!zlfw->component[i]) + continue; /* Component is not present */ + + rc = zl3073x_fw_component_flash(zldev, zlfw->component[i], + extack); + if (rc) + break; + } + + return rc; +} |