| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // TAS2563/TAS2781 Common functions for HDA and ASoC Audio drivers |
| // |
| // Copyright 2023 - 2025 Texas Instruments, Inc. |
| // |
| // Author: Shenghao Ding <shenghao-ding@ti.com> |
| |
| #include <linux/crc8.h> |
| #include <linux/dev_printk.h> |
| #include <linux/firmware.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <sound/tas2781.h> |
| |
| int tasdevice_dev_read(struct tasdevice_priv *tas_priv, |
| unsigned short chn, unsigned int reg, unsigned int *val) |
| { |
| int ret = 0; |
| |
| if (chn < tas_priv->ndev) { |
| struct regmap *map = tas_priv->regmap; |
| |
| ret = tas_priv->change_chn_book(tas_priv, chn, |
| TASDEVICE_BOOK_ID(reg)); |
| if (ret < 0) |
| goto out; |
| |
| ret = regmap_read(map, TASDEVICE_PGRG(reg), val); |
| if (ret < 0) |
| dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); |
| } else { |
| ret = -EINVAL; |
| dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, |
| chn); |
| } |
| |
| out: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_dev_read); |
| |
| int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, |
| unsigned short chn, unsigned int reg, unsigned char *data, |
| unsigned int len) |
| { |
| int ret = 0; |
| |
| if (chn < tas_priv->ndev) { |
| struct regmap *map = tas_priv->regmap; |
| |
| ret = tas_priv->change_chn_book(tas_priv, chn, |
| TASDEVICE_BOOK_ID(reg)); |
| if (ret < 0) |
| goto out; |
| |
| ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); |
| if (ret < 0) |
| dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); |
| } else |
| dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, |
| chn); |
| |
| out: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_read); |
| |
| int tasdevice_dev_write(struct tasdevice_priv *tas_priv, |
| unsigned short chn, unsigned int reg, unsigned int value) |
| { |
| int ret = 0; |
| |
| if (chn < tas_priv->ndev) { |
| struct regmap *map = tas_priv->regmap; |
| |
| ret = tas_priv->change_chn_book(tas_priv, chn, |
| TASDEVICE_BOOK_ID(reg)); |
| if (ret < 0) |
| goto out; |
| |
| ret = regmap_write(map, TASDEVICE_PGRG(reg), |
| value); |
| if (ret < 0) |
| dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); |
| } else { |
| ret = -EINVAL; |
| dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, |
| chn); |
| } |
| |
| out: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_dev_write); |
| |
| int tasdevice_dev_bulk_write( |
| struct tasdevice_priv *tas_priv, unsigned short chn, |
| unsigned int reg, unsigned char *data, |
| unsigned int len) |
| { |
| int ret = 0; |
| |
| if (chn < tas_priv->ndev) { |
| struct regmap *map = tas_priv->regmap; |
| |
| ret = tas_priv->change_chn_book(tas_priv, chn, |
| TASDEVICE_BOOK_ID(reg)); |
| if (ret < 0) |
| goto out; |
| |
| ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), |
| data, len); |
| if (ret < 0) |
| dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); |
| } else { |
| ret = -EINVAL; |
| dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, |
| chn); |
| } |
| |
| out: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_write); |
| |
| static void tasdev_dsp_prog_blk_remove(struct tasdevice_prog *prog) |
| { |
| struct tasdevice_data *tas_dt; |
| struct tasdev_blk *blk; |
| unsigned int i; |
| |
| if (!prog) |
| return; |
| |
| tas_dt = &(prog->dev_data); |
| |
| if (!tas_dt->dev_blks) |
| return; |
| |
| for (i = 0; i < tas_dt->nr_blk; i++) { |
| blk = &(tas_dt->dev_blks[i]); |
| kfree(blk->data); |
| } |
| kfree(tas_dt->dev_blks); |
| } |
| |
| static void tasdev_dsp_prog_remove(struct tasdevice_prog *prog, |
| unsigned short nr) |
| { |
| int i; |
| |
| for (i = 0; i < nr; i++) |
| tasdev_dsp_prog_blk_remove(&prog[i]); |
| kfree(prog); |
| } |
| |
| static void tasdev_dsp_cfg_blk_remove(struct tasdevice_config *cfg) |
| { |
| struct tasdevice_data *tas_dt; |
| struct tasdev_blk *blk; |
| unsigned int i; |
| |
| if (cfg) { |
| tas_dt = &(cfg->dev_data); |
| |
| if (!tas_dt->dev_blks) |
| return; |
| |
| for (i = 0; i < tas_dt->nr_blk; i++) { |
| blk = &(tas_dt->dev_blks[i]); |
| kfree(blk->data); |
| } |
| kfree(tas_dt->dev_blks); |
| } |
| } |
| |
| static void tasdev_dsp_cfg_remove(struct tasdevice_config *config, |
| unsigned short nr) |
| { |
| int i; |
| |
| for (i = 0; i < nr; i++) |
| tasdev_dsp_cfg_blk_remove(&config[i]); |
| kfree(config); |
| } |
| |
| void tasdevice_dsp_remove(void *context) |
| { |
| struct tasdevice_priv *tas_dev = (struct tasdevice_priv *) context; |
| struct tasdevice_fw *tas_fmw = tas_dev->fmw; |
| |
| if (!tas_dev->fmw) |
| return; |
| |
| if (tas_fmw->programs) |
| tasdev_dsp_prog_remove(tas_fmw->programs, |
| tas_fmw->nr_programs); |
| if (tas_fmw->configs) |
| tasdev_dsp_cfg_remove(tas_fmw->configs, |
| tas_fmw->nr_configurations); |
| kfree(tas_fmw); |
| tas_dev->fmw = NULL; |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_dsp_remove); |
| |
| void tasdevice_remove(struct tasdevice_priv *tas_priv) |
| { |
| mutex_destroy(&tas_priv->codec_lock); |
| } |
| EXPORT_SYMBOL_GPL(tasdevice_remove); |
| |
| MODULE_DESCRIPTION("TAS2781 common library"); |
| MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding@ti.com>"); |
| MODULE_LICENSE("GPL"); |