+config SND_SOC_TAS2783
tristate "Texas Instruments TAS2783 speaker amplifier (sdw)"
depends on SOUNDWIRE
select REGMAP
select CRC32_SARWATE
help
Enable support for Texas Instruments TAS2783 Smart Amplifier
Digital input mono Class-D and DSP-inside audio power amplifiers.
Note the TAS2783 driver implements a flexible and configurable
algo coff setting, for one, two, even multiple TAS2783 chips.
Algorithm coefficient
+#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/efi.h>
is this really needed?
+#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/pcm_params.h> +#include <sound/sdw.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/tas2781-tlv.h>
+#include "tas2783.h"
+static const unsigned int tas2783_calibration_reg[] = {
- TAS2783_CALIBRATION_RE,
- TAS2783_CALIBRATION_RE_LOW,
- TAS2783_CALIBRATION_INV_RE,
- TAS2783_CALIBRATION_POW,
- TAS2783_CALIBRATION_TLIMIT,
- 0,
what is the purpose of this zero, presumably you can use ARRAY_SIZE and don't need a zero-terminated value?
+static bool tas2783_volatile_register(struct device *dev,
- unsigned int reg)
+{
- switch (reg) {
- case 0x8001:
// Only reset register was volatiled.
return true;
can you explain the concept of a volatile reset register?
- default:
return false;
- }
+}
+static int tas2783_digital_getvol(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *codec
= snd_soc_kcontrol_component(kcontrol);
- struct tasdevice_priv *tas_dev =
snd_soc_component_get_drvdata(codec);
- struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- struct regmap *map = tas_dev->regmap;
- int val = 0, ret;
different line when a variable is initialized?
- if (!map) {
ret = -EINVAL;
dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
__func__);
goto out;
- }
- /* Read the primary device as the whole */
I can't figure out what this comment means
- ret = regmap_read(map, mc->reg, &val);
- dev_dbg(tas_dev->dev, "%s, get digital vol %d from %x with %d\n",
__func__, val, mc->reg, ret);
- if (ret) {
dev_err(tas_dev->dev, "%s, get digital vol error %x.\n",
__func__, ret);
goto out;
- }
- ucontrol->value.integer.value[0] =
tasdevice_clamp(val, mc->max, mc->invert);
+out:
- return ret;
+}
+static int tas2783_amp_getvol(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_component *codec
= snd_soc_kcontrol_component(kcontrol);
- struct tasdevice_priv *tas_dev =
snd_soc_component_get_drvdata(codec);
- struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- struct regmap *map = tas_dev->regmap;
- unsigned char mask;
- int ret, val;
- if (!map) {
dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
__func__);
return -EINVAL;
- }
- /* Read the primary device */
What is a primary device?
- ret = regmap_read(map, mc->reg, &val);
- dev_dbg(tas_dev->dev, "%s, get AMP vol %d from %x with %d\n",
__func__, val, mc->reg, ret);
- mask = (1 << fls(mc->max)) - 1;
- mask <<= mc->shift;
- val = (val & mask) >> mc->shift;
- ucontrol->value.integer.value[0] = tasdevice_clamp(val, mc->max,
mc->invert);
- return ret;
+}
+static int tas2783_calibration(struct tasdevice_priv *tas_priv) +{
- efi_guid_t efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc,
0x09, 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92);
- static efi_char16_t efi_name[] = TAS2783_CALIDATA_NAME;
- struct tm *tm = &tas_priv->tm;
- unsigned int attr, crc;
- unsigned int *tmp_val;
- efi_status_t status;
- tas_priv->cali_data.total_sz = TAS2783_MAX_CALIDATA_SIZE;
- /* Get real size of UEFI variable */
- status = efi.get_variable(efi_name, &efi_guid, &attr,
&tas_priv->cali_data.total_sz, tas_priv->cali_data.data);
- dev_dbg(tas_priv->dev, "cali get %lx bytes with result : %ld\n",
tas_priv->cali_data.total_sz, status);
- if (status == EFI_BUFFER_TOO_SMALL) {
status = efi.get_variable(efi_name, &efi_guid, &attr,
&tas_priv->cali_data.total_sz,
tas_priv->cali_data.data);
dev_dbg(tas_priv->dev, "cali get %lx bytes result:%ld\n",
tas_priv->cali_data.total_sz, status);
- }
- /* Failed got calibration data from EFI. */
I don't get what the dependency on EFI is. First time I see a codec needing this.
Please describe in details what you are trying to accomplish. Edit: there's also a dependency on firmware but the firmware name SWFT will hint at ACPI uses for anyone that has read the SDCA draft. This is beyond confusing.
- if (status != 0) {
dev_dbg(tas_priv->dev, "cali get %lx error with:%ld\n",
tas_priv->cali_data.total_sz, status);
return 0;
- }
- /* Print all content of calibration data for debug. */
- for (int i = 0; i < tas_priv->cali_data.total_sz; i += 4) {
dev_dbg(tas_priv->dev, "cali get %02x %02x %02x %02x",
tas_priv->cali_data.data[i],
tas_priv->cali_data.data[i+1],
tas_priv->cali_data.data[i+2],
tas_priv->cali_data.data[i+3]);
- }
- tmp_val = (unsigned int *)tas_priv->cali_data.data;
- crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0;
- dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n",
crc, tmp_val[21]);
- if (crc == tmp_val[21]) {
time64_to_tm(tmp_val[20], 0, tm);
dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n",
tm->tm_year, tm->tm_mon, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
What is this about? Why would a codec care about time?
tas2783_apply_calib(tas_priv, tmp_val);
- } else {
dev_dbg(tas_priv->dev, "CRC error!\n");
tas_priv->cali_data.total_sz = 0;
- }
- return 0;
+}
+static void tasdevice_rca_ready(const struct firmware *fmw, void *context) +{
- struct tasdevice_priv *tas_dev =
(struct tasdevice_priv *) context;
- struct regmap *map = tas_dev->regmap;
- struct tas2783_firmware_node *p;
- int offset = 0, num_nodes = 0, img_sz, ret;
- unsigned char *buf;
- mutex_lock(&tas_dev->codec_lock);
- if (!fmw || !fmw->data) {
dev_err(tas_dev->dev,
"Failed to read %s, no side - effect on driver running\n",
side-effect
tas_dev->rca_binaryname);
ret = -EINVAL;
goto out;
- }
- if (!map) {
dev_err(tas_dev->dev, "Failed to load regmap.\n");
ret = -EINVAL;
goto out;
- }
- buf = (unsigned char *)fmw->data;
- img_sz = le32_to_cpup((__le32 *)&buf[offset]);
- dev_dbg(tas_dev->dev, "Got %x:%lx.\n", img_sz, fmw->size);
- offset += sizeof(img_sz);
- if (img_sz != fmw->size) {
dev_err(tas_dev->dev,
"Size not match, %d %u", (int)fmw->size, img_sz);
Size does not match or size not matching
ret = -EINVAL;
goto out;
- }
- while ((offset < img_sz) && (num_nodes < TAS2783_MAX_NODES)) {
/* Store firmware into context of driver. */
p = (struct tas2783_firmware_node *)(buf + offset);
tas_dev->firmware_node[num_nodes].vendor_id =
p->vendor_id;
tas_dev->firmware_node[num_nodes].file_id = p->file_id;
tas_dev->firmware_node[num_nodes].version_id =
p->version_id;
tas_dev->firmware_node[num_nodes].length = p->length;
tas_dev->firmware_node[num_nodes].download_addr =
p->download_addr;
tas_dev->firmware_node[num_nodes].start_addr =
((char *)p) + sizeof(unsigned int)*5;
ret = regmap_bulk_write(map, p->download_addr,
p->start_addr, p->length);
dev_dbg(tas_dev->dev, "Wr %d :%x:%x:%x:%x:%x:%x %d.\n",
num_nodes, p->vendor_id, p->file_id,
p->version_id, p->length, p->download_addr,
p->start_addr[0], ret);
offset += sizeof(unsigned int)*5 + p->length;
num_nodes++;
- }
- tas2783_calibration(tas_dev);
+out:
- mutex_unlock(&tas_dev->codec_lock);
- if (fmw)
release_firmware(fmw);
+}
+static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = {
- SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM,
0, 0),
- SND_SOC_DAPM_OUTPUT("OUT"),
- SND_SOC_DAPM_INPUT("DMIC")
Can you clarify what "ASI" is? Also what is the plan for DMIC - this is an amplifier, no?
+};
+static const struct snd_soc_dapm_route tasdevice_audio_map[] = {
- {"OUT", NULL, "ASI"},
- {"ASI OUT", NULL, "DMIC"}
+};
+static int tasdevice_set_sdw_stream(
- struct snd_soc_dai *dai, void *sdw_stream, int direction)
+{
- struct sdw_stream_data *stream;
- if (!sdw_stream)
return 0;
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
return -ENOMEM;
- stream->sdw_stream = sdw_stream;
- /* Use tx_mask or rx_mask to set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
- return 0;
+}
this can be simplied, just look at all other existing codecs and implement the same, e.g.
static int cs42l42_sdw_dai_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, int direction) { snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0; }
+static void tasdevice_sdw_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct sdw_stream_data *stream;
- stream = snd_soc_dai_get_dma_data(dai, substream);
- snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
same, this can be simplified
+}
+static struct snd_soc_dai_driver tasdevice_dai_driver[] = {
- {
.name = "tas2783-codec",
.id = 0,
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 4,
.rates = TAS2783_DEVICE_RATES,
.formats = TAS2783_DEVICE_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 4,
.rates = TAS2783_DEVICE_RATES,
.formats = TAS2783_DEVICE_FORMATS,
},
what is the capture part? Feedback or DMIC?
.ops = &tasdevice_dai_ops,
.symmetric_rate = 1,
- },
+};
+static void tas2783_reset(struct tasdevice_priv *tas_dev) +{
- struct regmap *map = tas_dev->regmap;
- unsigned char value_sdw;
- int ret;
- if (!map) {
dev_err(tas_dev->dev, "Failed to load regmap.\n");
return;
- }
- value_sdw = TAS2873_REG_SWRESET_RESET;
- ret = regmap_write(map, TAS2873_REG_SWRESET, value_sdw);
- dev_dbg(tas_dev->dev, "%s TAS2783 was reseted %d.\n",
__func__, ret);
- usleep_range(1000, 1050);
don't we need some sort of regmap_sync here?
+}
+static int tasdevice_codec_probe(struct snd_soc_component *codec)
the naming is poor, this is the component probe, not the codec driver probe.
+{
- struct tasdevice_priv *tas_dev =
snd_soc_component_get_drvdata(codec);
- int ret;
- dev_dbg(tas_dev->dev, "%s called for TAS2783 start.\n",
__func__);
- /* Codec Lock Hold */
- mutex_lock(&tas_dev->codec_lock);> +
- tas2783_reset(tas_dev);
Isn't this something you would do in a driver probe?
- tas_dev->codec = codec;
- scnprintf(tas_dev->rca_binaryname, 64, "MY_SWFT_x%01x.bin",
The naming is rather controversial...
You need to have some sort of identifier that is TI specific, and/or a prefix to find this file in /lib/firmware.
It's really unclear to me why this is part of a component probe and not a driver probe.
tas_dev->sdw_peripheral->id.unique_id);
- ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
tas_dev->rca_binaryname, tas_dev->dev, GFP_KERNEL,
tas_dev, tasdevice_rca_ready);
- dev_dbg(tas_dev->dev,
"%s: request_firmware %x open status: 0x%08x\n",
__func__, tas_dev->sdw_peripheral->id.unique_id, ret);
- /* Codec Lock Release*/
- mutex_unlock(&tas_dev->codec_lock);
- dev_dbg(tas_dev->dev, "%s was called end.\n", __func__);
- return ret;
+}
+static int tasdevice_io_init(struct device *dev, struct sdw_slave *slave) +{
- struct tasdevice_priv *tasdevice = dev_get_drvdata(dev);
- if (tasdevice->hw_init)
return 0;
- /* PM runtime is only enabled when
* a Slave reports as Attached
* set autosuspend parameters
*/
- pm_runtime_set_autosuspend_delay(&slave->dev, 3000);
- pm_runtime_use_autosuspend(&slave->dev);
- /* update count of parent 'active' children */
- pm_runtime_set_active(&slave->dev);
- /* make sure the device does not suspend immediately */
- pm_runtime_mark_last_busy(&slave->dev);
- pm_runtime_enable(&slave->dev);
- pm_runtime_get_noresume(&slave->dev);
- /* Mark Slave initialization complete */
- tasdevice->hw_init = true;
- pm_runtime_mark_last_busy(&slave->dev);
- pm_runtime_put_autosuspend(&slave->dev);
- dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
- return 0;
This is really not aligned with the latest upstream code. Intel (and specifically me myself and I) contributed a change where the pm_runtime is enabled in the driver probe, but the device status changes to active in the io_init.
In addition, it's rather surprising that on attachment there is not a single regmap access?
+static void tasdevice_remove(struct tasdevice_priv *tas_dev) +{
- snd_soc_unregister_component(tas_dev->dev);
- mutex_destroy(&tas_dev->dev_lock);
- mutex_destroy(&tas_dev->codec_lock);
I didn't really look into the locking parts but you will need a lot more explanations on what you are trying to protect and the concurrency issues.
+}
+static int tasdevice_sdw_probe(struct sdw_slave *peripheral,
- const struct sdw_device_id *id)
+{
- struct device *dev = &peripheral->dev;
- struct tasdevice_priv *tas_dev;
- int ret;
- dev_dbg(dev, "%s was called.\n", __func__);
- tas_dev = devm_kzalloc(dev, sizeof(*tas_dev), GFP_KERNEL);
- if (!tas_dev) {
ret = -ENOMEM;
goto out;
- }
- tas_dev->dev = &peripheral->dev;
- tas_dev->chip_id = id->driver_data;
- tas_dev->sdw_peripheral = peripheral;
- tas_dev->hw_init = false;
- dev_dbg(dev, "%d chip id %x for TAS2783.\n",
- peripheral->id.unique_id, tas_dev->chip_id);
- dev_set_drvdata(dev, tas_dev);
- tas_dev->regmap = devm_regmap_init_sdw(peripheral,
&tasdevice_regmap);
- if (IS_ERR(tas_dev->regmap)) {
ret = PTR_ERR(tas_dev->regmap);
dev_err(dev, "Failed devm_regmap_init: %d\n", ret);
goto out;
- }
- ret = tasdevice_init(tas_dev);
+out:
- if (ret < 0 && tas_dev != NULL)
tasdevice_remove(tas_dev);
- return ret;
like I said above, this is missing the pm_runtime stuff.
+}
+static int tasdevice_sdw_remove(struct sdw_slave *peripheral) +{
- struct tasdevice_priv *tas_dev =
dev_get_drvdata(&peripheral->dev);
- if (tas_dev)
tasdevice_remove(tas_dev);
- return 0;
+}
+static const struct sdw_device_id tasdevice_sdw_id[] = {
- SDW_SLAVE_ENTRY(0x0102, 0x0, 0),
0x0102 is the legit TI manufacturer ID, that's good.
What's not so good is that the part ID is *ZERO*? Is this really intentional?
+#define TASDEVICE_REG(book, page, reg) ((book * 256 * 256) + 0x8000 +\
(page * 128) + reg)
+/*Software Reset */
/* Software