Re: [PATCH 1/2] ASoc: tas2563: DSP Firmware loading support
From: Pierre-Louis Bossart
Date: Mon Dec 04 2023 - 19:23:26 EST
> Firmware loading is done dymacally and the program and configuration
dynamically
> selection is done by the user.
>
> The binary itself contains a list of instructions for either a single
> mode write or a burst write. The single mode write is list of register
> writes to different books and pages within the register map.
> The burst writes is a block of data that is written to a specific
> location in memory.
>
> The firmware loader must parse and load the blocks in real time as the
> binary may contain different audio profiles.
>
> If the DSP is not needed to do audio preocessing then the DSP program
preprocessing
> can be turned off and the device will effectively turn off the DSP.
> ---
> {sound/soc/codecs => include/sound}/tas2562.h | 3 +
> include/sound/tas25xx-dsp.h | 100 +++++++
> sound/soc/codecs/Kconfig | 7 +
> sound/soc/codecs/Makefile | 2 +
> sound/soc/codecs/tas2562.c | 2 +-
are tas2562 and tas2563 (from commit subject) the same?
> sound/soc/codecs/tas25xx-dsp.c | 282 ++++++++++++++++++
> 6 files changed, 395 insertions(+), 1 deletion(-)
> rename {sound/soc/codecs => include/sound}/tas2562.h (97%)
> create mode 100644 include/sound/tas25xx-dsp.h
> create mode 100644 sound/soc/codecs/tas25xx-dsp.c
>
> diff --git a/sound/soc/codecs/tas2562.h b/include/sound/tas2562.h
> similarity index 97%
> rename from sound/soc/codecs/tas2562.h
> rename to include/sound/tas2562.h
> diff --git a/sound/soc/codecs/tas25xx-dsp.c b/sound/soc/codecs/tas25xx-dsp.c
> new file mode 100644
> index 000000000000..d5081fa01441
> --- /dev/null
> +++ b/sound/soc/codecs/tas25xx-dsp.c
> @@ -0,0 +1,282 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Firmware loader for the Texas Instruments TAS25XX DSP
> +// Copyright (C) 2020 Texas Instruments Inc.
> +
> +#include <linux/module.h>
> +#include <linux/errno.h>
> +#include <linux/device.h>
> +#include <linux/firmware.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
nit-pick: alphabetical order?
> +
> +#include <sound/tas2562.h>
> +#include <sound/tas25xx-dsp.h>
> +
> +
> +static void tas25xx_process_fw_delay(struct tas25xx_cmd *cmd)
> +{
> + mdelay(cpu_to_be16(cmd->hdr.length));
is this the length of the header, or the duration of the delay?
Someone will get it wrong with this naming...
> +}
> +
> +static int tas25xx_process_fw_single(struct regmap *regmap,
> + struct tas25xx_cmd *cmd)
> +{
> + int ret;
> + int num_writes = cpu_to_be16(cmd->hdr.length);
> + struct tas25xx_cmd_reg *reg = &cmd->reg;
reverse x-mas style recommended, e.g. move 'int ret;' last
> +
> + for (int i = 0; i < num_writes; i++, reg++) {
> + /* Reset Page to 0 to access BOOK_CTRL */
> + ret = regmap_write(regmap, TAS2562_PAGE_CTRL, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(regmap, TAS2562_BOOK_CTRL, reg->book);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(regmap, TAS2562_REG(reg->page, reg->offset),
> + reg->data);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int tas25xx_process_fw_burst(struct regmap *regmap,
> + struct tas25xx_cmd *cmd)
> +{
> + int ret;
> +
> + /* Reset Page to 0 to access BOOK_CTRL */
> + ret = regmap_write(regmap, TAS2562_PAGE_CTRL, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(regmap, TAS2562_BOOK_CTRL, cmd->reg.book);
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(regmap, TAS2562_REG(cmd->reg.page, cmd->reg.offset), &cmd[1],
> + cpu_to_be16(cmd->hdr.length));
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int tas25xx_process_block(struct device *dev, struct regmap *regmap,
> + struct tas25xx_cmd *cmd, int max_block_size)
> +{
> + int ret;
> + int block_read;
> +
> + const int hdr_size = sizeof(struct tas25xx_cmd_hdr);
> + const int reg_size = sizeof(struct tas25xx_cmd_reg);
> + const int cmd_size = sizeof(struct tas25xx_cmd);
> +
> + switch (cpu_to_be16(cmd->hdr.cmd_type)) {
> + case TAS25XX_CMD_SING_W:
> + block_read = cpu_to_be16(cmd->hdr.length) * reg_size + hdr_size;
> + break;
> + case TAS25XX_CMD_BURST:
> + block_read = cpu_to_be16(cmd->hdr.length) + cmd_size;
> + break;
> + case TAS25XX_CMD_DELAY:
> + block_read = 4;
> + break;
> + default:
> + block_read = 0;
> + }
> +
> + if (block_read > max_block_size) {
> + dev_err(dev,
> + "Corrupt firmware: block_read > max_block_size %d %d\n",
> + block_read, max_block_size);
> + return -EINVAL;
> + }
> +
> + switch (cpu_to_be16(cmd->hdr.cmd_type)) {
> + case TAS25XX_CMD_SING_W:
> + ret = tas25xx_process_fw_single(regmap, cmd);
> + if (ret) {
> + dev_err(dev, "Failed to process single write %d\n",
> + ret);
> + return ret;
> + }
> + break;
> + case TAS25XX_CMD_BURST:
> + ret = tas25xx_process_fw_burst(regmap, cmd);
> + if (ret) {
> + dev_err(dev, "Failed to process burst write %d\n", ret);
> + return ret;
> + }
> + break;
> + case TAS25XX_CMD_DELAY:
> + tas25xx_process_fw_delay(cmd);
> + break;
> + default:
> + dev_warn(dev, "Unknown cmd type %d\n",
> + cpu_to_be16(cmd->hdr.cmd_type));
> + break;
> + };
> +
> + return block_read;
> +}
> +
> +
> +int tas25xx_write_program(struct device *dev, struct regmap *regmap,
> + struct tas25xx_fw_data *fw_data, int prog_num)
> +{
> + int offset = 0;
> + int length = 0;
useless init
> + struct tas25xx_program_info *prog_info;
> + struct tas25xx_fw_hdr *hdr = fw_data->hdr;
> + struct tas25xx_cmd *cmd;
again reverse x-mas style would look much better
> +
> + if (prog_num > cpu_to_be32(hdr->num_programs))
> + return -EINVAL;
> +
> + for (int i = 0; i < prog_num; i++)
> + offset += cpu_to_be32(hdr->prog_size[i]);
> +
> + prog_info = (struct tas25xx_program_info *)&fw_data->prog_data[offset];
> + dev_info(dev, "Write program %d: %s\n", prog_num, prog_info->name);
> +
> + int max_offset = offset + cpu_to_be32(hdr->prog_size[prog_num]);
> + int num_subblocks = cpu_to_be32(prog_info->blk_data.num_subblocks);
It's not illegal but not consistent to declare variables in the middle
of code for no good reason.
> + offset += sizeof(struct tas25xx_program_info);
> +
> + for (int i = 0; i < num_subblocks; i++) {
> + cmd = (struct tas25xx_cmd *)&fw_data->prog_data[offset];
> + length = tas25xx_process_block(dev, regmap, cmd,
> + max_offset - offset);
> + if (length < 0)
> + return length;
> +
> + offset += length;
> + }
> +
> + /* Reset Book to 0 */
> + regmap_write(regmap, TAS2562_PAGE_CTRL, 0);
> + regmap_write(regmap, TAS2562_BOOK_CTRL, 0);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(tas25xx_write_program);
> +
> +int tas25xx_write_config(struct device *dev, struct regmap *regmap,
> + struct tas25xx_fw_data *fw_data, int config_num)
> +{
> + int offset = 0;
> + int length = 0;
useless init again
> + struct tas25xx_config_info *cfg_info;
> + struct tas25xx_fw_hdr *hdr = fw_data->hdr;
> + struct tas25xx_cmd *cmd;
> +
> + if (config_num > cpu_to_be32(hdr->num_configs))
> + return -EINVAL;
> +
> + for (int i = 0; i < config_num; i++)
> + offset += cpu_to_be32(hdr->config_size[i]);
> +
> + cfg_info = (struct tas25xx_config_info *)&fw_data->config_data[offset];
> + dev_info(dev, "Write config %d: %s\n", config_num, cfg_info->name);
> +
> + int max_offset = offset + cpu_to_be32(hdr->config_size[config_num]);
> + int num_subblocks = cpu_to_be32(cfg_info->blk_data.num_subblocks);
> +
> + offset += sizeof(struct tas25xx_config_info);
> +
> + for (int i = 0; i < num_subblocks; i++) {
> + cmd = (struct tas25xx_cmd *)&fw_data->config_data[offset];
> + length = tas25xx_process_block(dev, regmap, cmd,
> + max_offset - offset);
> + if (length < 0)
> + return length;
> +
> + offset += length;
> + }
> +
> + /* Reset Book to 0 */
> + regmap_write(regmap, TAS2562_PAGE_CTRL, 0);
> + regmap_write(regmap, TAS2562_BOOK_CTRL, 0);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(tas25xx_write_config);
> +
> +
> +struct tas25xx_fw_data *tas25xx_parse_fw(struct device *dev,
> + const struct firmware *fw)
> +{
> + u32 total_prog_sz = 0;
> + u32 total_config_sz = 0;
> + u32 prog_num = 0;
> + u32 config_num = 0;
last two inits are useless
> + int hdr_size = sizeof(struct tas25xx_fw_hdr);
> + struct tas25xx_fw_data *fw_data = NULL;
> +
> + fw_data = devm_kzalloc(dev, sizeof(struct tas25xx_fw_data), GFP_KERNEL);
> + if (!fw_data)
> + goto err_fw;
> +
> + if (fw->size < hdr_size)
> + goto err_data;
> +
> + fw_data->hdr = devm_kzalloc(dev, hdr_size, GFP_KERNEL);
> + if (!fw_data->hdr)
> + goto err_data;
> +
> + memcpy(fw_data->hdr, &fw->data[0], hdr_size);
> +
> + for (int i = 0; i < cpu_to_be32(fw_data->hdr->num_programs); i++)
> + total_prog_sz += cpu_to_be32(fw_data->hdr->prog_size[i]);
> +
> + for (int i = 0; i < cpu_to_be32(fw_data->hdr->num_configs); i++)
> + total_config_sz += cpu_to_be32(fw_data->hdr->config_size[i]);
> +
> + if (fw->size < hdr_size + total_prog_sz + total_config_sz)
> + goto err_hdr;
> +
> + fw_data->prog_data = devm_kzalloc(dev, total_prog_sz, GFP_KERNEL);
> + if (!fw_data->prog_data)
> + goto err_hdr;
> +
> + memcpy(fw_data->prog_data, &fw->data[hdr_size], total_prog_sz);
> +
> + fw_data->config_data = devm_kzalloc(dev, total_config_sz, GFP_KERNEL);
> + if (!fw_data->config_data)
> + goto err_prog;
> +
> + memcpy(fw_data->config_data, &fw->data[hdr_size + total_prog_sz],
> + total_config_sz);
> +
> + prog_num = cpu_to_be32(fw_data->hdr->num_programs);
> + config_num = cpu_to_be32(fw_data->hdr->num_configs);
> + dev_info(dev, "Firmware loaded: programs %d, configs %d\n",
> + prog_num, config_num);
> +
> + return fw_data;
> +
> +err_prog:
> + devm_kfree(dev, fw_data->prog_data);
> +err_hdr:
> + devm_kfree(dev, fw_data->hdr);
> +err_data:
> + devm_kfree(dev, fw_data);
> +err_fw:
> + release_firmware(fw);
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(tas25xx_parse_fw);
> +
> +MODULE_DESCRIPTION("TAS25xx DSP library");
> +MODULE_AUTHOR("Dan Murphy <dmurphy@xxxxxx>");
> +MODULE_LICENSE("GPL");