> 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");