diff mbox series

[v2] ASoC: tas2783: Add source files for tas2783 soundwire driver

Message ID 20230814121158.4668-1-baojun.xu@ti.com (mailing list archive)
State New, archived
Headers show
Series [v2] ASoC: tas2783: Add source files for tas2783 soundwire driver | expand

Commit Message

Baojun Xu Aug. 14, 2023, 12:11 p.m. UTC
Add source file and header file for tas2783 driver.
Update Kconfig and Makefile for tas2783 driver.

Signed-off-by: Baojun Xu <baojun.xu@ti.com>

---
Change in v2:
 - change all registers access to regmap.
 - change header file include order.
 - remove tas2783_sdw_read().
 - remove regmap_bus and relative function.
 - change custom access to regmap.
 - return if get calibration data from EFI failed.
 - remove mutex protect for null function.
 - remove tasdevice_startup() as no actions needed.
 - remove sysclk function as no action needed.
 - remove tasdevice_mute().
 - remove crc8 look up table generate.
 - remove first_hw_init flag.
 - change some define name to TAS2783_XXX.
 - remove SMS_HTONL(), use le_to_cpup().
 - remove enum channel define.
 - change struct and element name for FW.
 - remove global address elements.
 - remove irq and gpio in structure.
 - remove tasdevice structure.
---
 sound/soc/codecs/Kconfig       |  12 +
 sound/soc/codecs/Makefile      |   2 +
 sound/soc/codecs/tas2783-sdw.c | 872 +++++++++++++++++++++++++++++++++
 sound/soc/codecs/tas2783.h     | 113 +++++
 4 files changed, 999 insertions(+)
 create mode 100644 sound/soc/codecs/tas2783-sdw.c
 create mode 100644 sound/soc/codecs/tas2783.h

Comments

Pierre-Louis Bossart Aug. 17, 2023, 2:17 p.m. UTC | #1
> +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
Mark Brown Aug. 17, 2023, 3:12 p.m. UTC | #2
On Thu, Aug 17, 2023 at 09:17:50AM -0500, Pierre-Louis Bossart wrote:

> > +		goto out;
> > +	}
> > +	/* Read the primary device as the whole */
> 
> I can't figure out what this comment means

It's part of...

> > +		dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
> > +			__func__);
> > +		return -EINVAL;
> > +	}
> > +	/* Read the primary device */
> 
> What is a primary device?

...a thing where they're trying to present multiple devices as a unified
device with grouped control, it looks like there's some hardware support
for this.

> > +	/* 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.

It seems fairly normal to store calibration details in the device
firmware?

> > +	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?

I can see someone finding it helpful to confirm when the calibration data
that got applied was generated to make sure they're testing/using what
they thought they were.

> In addition, it's rather surprising that on attachment there is not a
> single regmap access?

Don't know if there's something different with Soundwire but for I2C/SPI
devices it's a reasonable pattern to only actually start initialising
the registers when the device actually becomes active.  Not checked that
this is actually happening.
Pierre-Louis Bossart Aug. 17, 2023, 4:28 p.m. UTC | #3
On 8/17/23 10:12, Mark Brown wrote:
> On Thu, Aug 17, 2023 at 09:17:50AM -0500, Pierre-Louis Bossart wrote:
> 
>>> +		goto out;
>>> +	}
>>> +	/* Read the primary device as the whole */
>>
>> I can't figure out what this comment means
> 
> It's part of...
> 
>>> +		dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
>>> +			__func__);
>>> +		return -EINVAL;
>>> +	}
>>> +	/* Read the primary device */
>>
>> What is a primary device?
> 
> ...a thing where they're trying to present multiple devices as a unified
> device with grouped control, it looks like there's some hardware support
> for this.

Let me clarify the comment: SDCA peripheral can have multiple functions,
each with its own address space and can operate independently. So I am
just trying to have clarity on what 'device' means here.

>>> +	/* 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.
> 
> It seems fairly normal to store calibration details in the device
> firmware?

No objection on the device firmware, but why use an EFI variable?

There is on-going work to standardize with ACPI, and there's also a
request_firmware(). Not sure what the direction is to read from an EFI
variable. I've been in SDCA circles since the beginning and never heard
about this, ever. I am not saying it's bad, just surprised and curious
on a 3rd way of getting information needed for initialization.

>>> +	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?
> 
> I can see someone finding it helpful to confirm when the calibration data
> that got applied was generated to make sure they're testing/using what
> they thought they were.

Ah yes, I missed that. I wasn't sure if this was a log on when the
calibration finished, if this is a log on when the calibration data was
generated that's a different story indeed.

>> In addition, it's rather surprising that on attachment there is not a
>> single regmap access?
> 
> Don't know if there's something different with Soundwire but for I2C/SPI
> devices it's a reasonable pattern to only actually start initialising
> the registers when the device actually becomes active.  Not checked that
> this is actually happening.

that's precisely the point, there's an io_init() routine which is when
the peripheral is attached on the bus and the earliest time when the
registers can be initialized.

But there isn't a single initialization happening, which is different to
all existing SoundWire codec drivers. Maybe it's fine, I am just asking
the question if this was intentional.
diff mbox series

Patch

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c2de4ee72183..a4d334faca5e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -240,6 +240,7 @@  config SND_SOC_ALL_CODECS
 	imply SND_SOC_TAS2781_COMLIB
 	imply SND_SOC_TAS2781_FMWLIB
 	imply SND_SOC_TAS2781_I2C
+	imply SND_SOC_TAS2783
 	imply SND_SOC_TAS5086
 	imply SND_SOC_TAS571X
 	imply SND_SOC_TAS5720
@@ -1758,6 +1759,17 @@  config SND_SOC_TAS2781_I2C
 	  algo coefficient setting, for one, two or even multiple TAS2781
 	  chips.
 
+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.
+
 config SND_SOC_TAS5086
 	tristate "Texas Instruments TAS5086 speaker amplifier"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index b48a9a323b84..3298dfa3b9d5 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -276,6 +276,7 @@  snd-soc-tas2770-objs := tas2770.o
 snd-soc-tas2781-comlib-objs := tas2781-comlib.o
 snd-soc-tas2781-fmwlib-objs := tas2781-fmwlib.o
 snd-soc-tas2781-i2c-objs := tas2781-i2c.o
+snd-soc-tas2783-objs := tas2783-sdw.o
 snd-soc-tfa9879-objs := tfa9879.o
 snd-soc-tfa989x-objs := tfa989x.o
 snd-soc-tlv320adc3xxx-objs := tlv320adc3xxx.o
@@ -648,6 +649,7 @@  obj-$(CONFIG_SND_SOC_TAS2780)	+= snd-soc-tas2780.o
 obj-$(CONFIG_SND_SOC_TAS2781_COMLIB)	+= snd-soc-tas2781-comlib.o
 obj-$(CONFIG_SND_SOC_TAS2781_FMWLIB)	+= snd-soc-tas2781-fmwlib.o
 obj-$(CONFIG_SND_SOC_TAS2781_I2C)	+= snd-soc-tas2781-i2c.o
+obj-$(CONFIG_SND_SOC_TAS2783)	+= snd-soc-tas2783.o
 obj-$(CONFIG_SND_SOC_TAS5086)	+= snd-soc-tas5086.o
 obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o
 obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o
diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c
new file mode 100644
index 000000000000..f916ac9c5520
--- /dev/null
+++ b/sound/soc/codecs/tas2783-sdw.c
@@ -0,0 +1,871 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments TAS2783 Audio Smart Amplifier.
+//
+// Copyright (C) 2023 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The TAS2783 driver implements a flexible and configurable
+// algo coefficient setting for single TAS2783 chips.
+//
+// Author: Baojun Xu <baojun.xu@ti.com>
+// Author: Kevin Lu <kevin-lu@ti.com>
+//
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/efi.h>
+#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,
+};
+
+static const struct reg_default tas2783_reg_defaults[] = {
+	// Default values for ROM mode. Activated.
+	{ 0x8002, 0x1a},	// Put AMP in power down mode, B0P0R02.
+	{ 0x800e, 0x44},	// VSense from slot 4, B0P0R0E.
+	{ 0x800f, 0x40},	// ISense from slot 0, B0P0R0E.
+	{ 0x8097, 0xc8},	// SARBurstMask = 0, B0P1R17.
+	{ 0x80b5, 0x74},
+	{ 0x8099, 0x20},
+	{ 0xfe8d, 0x0d},
+	{ 0xfebe, 0x4a},
+	{ 0x8230, 0x00},
+	{ 0x8231, 0x00},
+	{ 0x8232, 0x00},
+	{ 0x8233, 0x01},	// YMEM[70] = 1. B0P4R30.
+	{ 0x8418, 0x00},	// 0 dB in B0P8R18.
+	{ 0x8419, 0x00},
+	{ 0x841a, 0x00},
+	{ 0x841b, 0x00},
+	{ 0x8428, 0x40},	// Unmute channel, B0P8R28.
+	{ 0x8429, 0x00},
+	{ 0x842a, 0x00},
+	{ 0x842b, 0x00},
+	{ 0x8548, 0x00},	// 0dB in B0PAR48.
+	{ 0x8549, 0x00},
+	{ 0x854a, 0x00},
+	{ 0x854b, 0x00},
+	{ 0x8558, 0x40},	// unmute channel, B0PAR58.
+	{ 0x8559, 0x00},
+	{ 0x855a, 0x00},
+	{ 0x855b, 0x00},
+	{ 0x800a, 0x3a},	// Enable both channel in B0P0RA.
+	{ 0x805c, 0xd9},	// Enable clock detected in B0P0R5C.
+	{ 0x8002, 0x00},	// Put AMP in active mode, B0P0R02.
+	/* Below register was used to select function, entity, CS, CN. */
+	{ SDW_SDCA_CTL(1, 1, 2, 0), 0},	// 0x40400088.
+	{ SDW_SDCA_CTL(1, 1, 1, 0), 0},	// 0x40400090.
+	{ SDW_SDCA_CTL(1, 2, 1, 0), 0},	// 0x40400108.
+};
+
+static bool tas2783_readable_register(struct device *dev,
+	unsigned int reg)
+{
+	switch (reg) {
+	case 0x8000 ... 0xc000:	// Page 0 ~ 127.
+	case 0xfe80 ... 0xfeff:	// Page 253.
+	case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU21,
+		TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0):
+	case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21,
+		TAS2783_SDCA_CTL_FU_MUTE, TAS2783_DEVICE_CHANNEL_LEFT):
+	case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21,
+		TAS2783_SDCA_CTL_FU_MUTE, TAS2783_DEVICE_CHANNEL_RIGHT):
+	case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23,
+		TAS2783_SDCA_CTL_REQ_POWER_STATE, 0):
+	case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE22,
+		TAS2783_SDCA_CTL_REQ_POWER_STATE, 0):
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool tas2783_volatile_register(struct device *dev,
+	unsigned int reg)
+{
+	switch (reg) {
+	case 0x8001:
+		// Only reset register was volatiled.
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config tasdevice_regmap = {
+	.reg_bits = 32,
+	.val_bits = 8,
+	.readable_reg = tas2783_readable_register,
+	.volatile_reg = tas2783_volatile_register,
+	.max_register = 0x41008000 + TASDEVICE_REG(0xa1, 0x60, 0x7f),
+	.reg_defaults = tas2783_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(tas2783_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static int tasdevice_clamp(int val, int max, unsigned int invert)
+{
+	/* Keep in valid area, out of range value don't care. */
+	if (val > max)
+		val = max;
+	if (invert)
+		val = max - val;
+	if (val < 0)
+		val = 0;
+	return val;
+}
+
+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;
+
+	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 */
+	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_digital_putvol(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, ret;
+
+	if (!map) {
+		dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+			__func__);
+		return -EINVAL;
+	}
+	val = tasdevice_clamp(ucontrol->value.integer.value[0],
+		mc->max, mc->invert);
+
+	ret = regmap_write(map, mc->reg, val);
+	if (ret != 0) {
+		dev_err(tas_dev->dev, "Write @%#x..%#x:%d\n",
+			mc->reg, val, ret);
+		return ret;
+	}
+	dev_dbg(tas_dev->dev, "%s, Put digital vol %d into %x.\n",
+		__func__, val, mc->reg);
+
+	return 1;
+}
+
+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 */
+	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_amp_putvol(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 val, ret;
+
+	if (!map) {
+		dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+			__func__);
+		return -EINVAL;
+	}
+	mask = (1 << fls(mc->max)) - 1;
+	mask <<= mc->shift;
+	val = tasdevice_clamp(ucontrol->value.integer.value[0], mc->max,
+		mc->invert);
+	ret = regmap_update_bits(map, mc->reg, mask, val << mc->shift);
+	if (ret != 0) {
+		dev_err(tas_dev->dev, "Write @%#x..%#x:%d\n",
+			mc->reg, val, ret);
+		return ret;
+	}
+
+	dev_dbg(tas_dev->dev, "wr amp %x into 0x%08x by %x shift %d.\n",
+		val, mc->reg, mask, mc->shift);
+
+	return ret;
+}
+
+static const struct snd_kcontrol_new tas2783_snd_controls[] = {
+	SOC_SINGLE_RANGE_EXT_TLV("Amp Gain Volume", TAS2783_AMP_LEVEL,
+		1, 0, 20, 0, tas2783_amp_getvol,
+		tas2783_amp_putvol, amp_vol_tlv),
+	SOC_SINGLE_RANGE_EXT_TLV("Digital Volume", TAS2783_DVC_LVL,
+		0, 0, 200, 1, tas2783_digital_getvol,
+		tas2783_digital_putvol, dvc_tlv),
+};
+
+static void tas2783_apply_calib(
+	struct tasdevice_priv *tas_priv, unsigned int *cali_data)
+{
+	struct regmap *map = tas_priv->regmap;
+	int i = 0, ret;
+	u8 *reg_start;
+
+	if (!map) {
+		dev_err(tas_priv->dev, "%s, regmap doesn't exist.\n",
+			__func__);
+		return;
+	}
+	if (!tas_priv->sdw_peripheral) {
+		dev_err(tas_priv->dev, "%s, slaver doesn't exist.\n",
+			__func__);
+		return;
+	}
+	if ((tas_priv->sdw_peripheral->id.unique_id <
+		TAS2783_DEVICE_ID_START) ||
+		(tas_priv->sdw_peripheral->id.unique_id >
+		TAS2783_DEVICE_ID_END)) {
+		dev_err(tas_priv->dev, "%s, wrong unique id: %x.\n",
+			__func__, tas_priv->sdw_peripheral->id.unique_id);
+		return;
+	}
+	reg_start =
+		(u8 *)(cali_data+(tas_priv->sdw_peripheral->id.unique_id -
+		TAS2783_DEVICE_ID_START)*TAS2783_CALIBRATION_SIZE);
+	while (tas2783_calibration_reg[i] != 0) {
+		ret = regmap_bulk_write(map, tas2783_calibration_reg[i],
+			reg_start + i, 4);
+		if (ret != 0) {
+			dev_err(tas_priv->dev, "Cali failed %x:%d\n",
+			tas2783_calibration_reg[i], ret);
+			break;
+		}
+		i++;
+	}
+}
+
+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. */
+	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);
+		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",
+		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);
+		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")
+};
+
+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;
+}
+
+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);
+}
+
+static int tasdevice_sdw_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tasdevice_priv *tasdevice =
+		snd_soc_component_get_drvdata(component);
+	struct sdw_stream_config stream_config = {0};
+	struct sdw_port_config port_config = {0};
+	struct sdw_stream_data *stream;
+	int retval;
+
+	dev_dbg(dai->dev, "%s %s", __func__, dai->name);
+	stream = snd_soc_dai_get_dma_data(dai, substream);
+
+	if (!stream)
+		return -EINVAL;
+
+	if (!tasdevice->sdw_peripheral)
+		return -EINVAL;
+
+	/* SoundWire specific configuration */
+	snd_sdw_params_to_config(substream, params,
+		&stream_config, &port_config);
+
+	/* port 1 for playback */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		port_config.num = 1;
+	else
+		port_config.num = 2;
+
+	retval = sdw_stream_add_slave(tasdevice->sdw_peripheral,
+		&stream_config,	&port_config, 1, stream->sdw_stream);
+	if (retval) {
+		dev_err(dai->dev, "Unable to configure port\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static int tasdevice_sdw_pcm_hw_free(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tasdevice_priv *tasdevice =
+		snd_soc_component_get_drvdata(component);
+	struct sdw_stream_data *stream =
+		snd_soc_dai_get_dma_data(dai, substream);
+
+	if (!tasdevice->sdw_peripheral)
+		return -EINVAL;
+
+	sdw_stream_remove_slave(tasdevice->sdw_peripheral, stream->sdw_stream);
+	return 0;
+}
+
+static const struct snd_soc_dai_ops tasdevice_dai_ops = {
+	.hw_params	= tasdevice_sdw_hw_params,
+	.hw_free	= tasdevice_sdw_pcm_hw_free,
+	.set_stream	= tasdevice_set_sdw_stream,
+	.shutdown	= tasdevice_sdw_shutdown,
+};
+
+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,
+		},
+		.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);
+}
+
+static int tasdevice_codec_probe(struct snd_soc_component *codec)
+{
+	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);
+
+	tas_dev->codec = codec;
+
+	scnprintf(tas_dev->rca_binaryname, 64, "MY_SWFT_x%01x.bin",
+		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 const struct snd_soc_component_driver
+	soc_codec_driver_tasdevice = {
+	.probe		= tasdevice_codec_probe,
+	.controls	= tas2783_snd_controls,
+	.num_controls	= ARRAY_SIZE(tas2783_snd_controls),
+	.dapm_widgets	= tasdevice_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(tasdevice_dapm_widgets),
+	.dapm_routes		= tasdevice_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(tasdevice_audio_map),
+	.idle_bias_on	= 1,
+	.endianness	= 1,
+};
+
+static int tasdevice_init(struct tasdevice_priv *tas_dev)
+{
+	int ret;
+
+	mutex_init(&tas_dev->dev_lock);
+
+	dev_set_drvdata(tas_dev->dev, tas_dev);
+
+	mutex_init(&tas_dev->codec_lock);
+	ret = devm_snd_soc_register_component(tas_dev->dev,
+		&soc_codec_driver_tasdevice,
+		tasdevice_dai_driver, ARRAY_SIZE(tasdevice_dai_driver));
+	if (ret) {
+		dev_err(tas_dev->dev, "%s: codec register error:0x%x\n",
+			__func__, ret);
+	}
+	dev_dbg(tas_dev->dev, "%s was called for TAS2783.\n",  __func__);
+
+	return ret;
+}
+
+static int tasdevice_read_prop(struct sdw_slave *slave)
+{
+	struct sdw_slave_prop *prop = &slave->prop;
+	struct sdw_dpn_prop *dpn;
+	unsigned long addr;
+	int nval, i, j;
+	u32 bit;
+
+	prop->scp_int1_mask =
+		SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+	prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+
+	prop->paging_support = true;
+
+	/* first we need to allocate memory for set bits in port lists */
+	prop->source_ports = 0x04; /* BITMAP: 00000100 */
+	prop->sink_ports = 0x2; /* BITMAP:  00000010 */
+
+	nval = hweight32(prop->source_ports);
+	prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+		sizeof(*prop->src_dpn_prop), GFP_KERNEL);
+	if (!prop->src_dpn_prop)
+		return -ENOMEM;
+
+	i = 0;
+	dpn = prop->src_dpn_prop;
+	addr = prop->source_ports;
+	for_each_set_bit(bit, &addr, 32) {
+		dpn[i].num = bit;
+		dpn[i].type = SDW_DPN_FULL;
+		dpn[i].simple_ch_prep_sm = true;
+		dpn[i].ch_prep_timeout = 10;
+		i++;
+	}
+
+	/* do this again for sink now */
+	nval = hweight32(prop->sink_ports);
+	prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+		sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
+	if (!prop->sink_dpn_prop)
+		return -ENOMEM;
+
+	j = 0;
+	dpn = prop->sink_dpn_prop;
+	addr = prop->sink_ports;
+	for_each_set_bit(bit, &addr, 32) {
+		dpn[j].num = bit;
+		dpn[j].type = SDW_DPN_FULL;
+		dpn[j].simple_ch_prep_sm = true;
+		dpn[j].ch_prep_timeout = 10;
+		j++;
+	}
+
+	/* set the timeout values */
+	prop->clk_stop_timeout = 20;
+
+	dev_dbg(&slave->dev, "%s was performed.\n", __func__);
+
+	return 0;
+}
+
+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;
+}
+
+static int tasdevice_update_status(struct sdw_slave *slave,
+	enum sdw_slave_status status)
+{
+	struct  tasdevice_priv *tasdevice = dev_get_drvdata(&slave->dev);
+
+	/* Update the status */
+	tasdevice->status = status;
+
+	if (status == SDW_SLAVE_UNATTACHED)
+		tasdevice->hw_init = false;
+
+	/* Perform initialization only if slave status
+	 * is present and hw_init flag is false
+	 */
+	if (tasdevice->hw_init || tasdevice->status != SDW_SLAVE_ATTACHED)
+		return 0;
+
+	/* perform I/O transfers required for Slave initialization */
+	return tasdevice_io_init(&slave->dev, slave);
+}
+
+/*
+ * slave_ops: callbacks for get_clock_stop_mode, clock_stop and
+ * port_prep are not defined for now
+ */
+static const struct sdw_slave_ops tasdevice_sdw_ops = {
+	.read_prop	= tasdevice_read_prop,
+	.update_status	= tasdevice_update_status,
+};
+
+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);
+}
+
+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;
+
+}
+
+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),
+	{},
+};
+MODULE_DEVICE_TABLE(sdw, tasdevice_sdw_id);
+
+static struct sdw_driver tasdevice_sdw_driver = {
+	.driver = {
+		.name = "slave-tas2783",
+	},
+	.probe = tasdevice_sdw_probe,
+	.remove = tasdevice_sdw_remove,
+	.ops = &tasdevice_sdw_ops,
+	.id_table = tasdevice_sdw_id,
+};
+
+module_sdw_driver(tasdevice_sdw_driver);
+
+MODULE_AUTHOR("Baojun Xu <baojun.xu@ti.com>");
+MODULE_DESCRIPTION("ASoC TAS2783 SoundWire Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas2783.h b/sound/soc/codecs/tas2783.h
new file mode 100644
index 000000000000..09575e741400
--- /dev/null
+++ b/sound/soc/codecs/tas2783.h
@@ -0,0 +1,113 @@ 
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * ALSA SoC Texas Instruments TAS2783 Audio Smart Amplifier.
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated
+ * https://www.ti.com
+ *
+ * The TAS2783 driver implements a flexible and configurable
+ * algo coff setting for single TAS2783 chips.
+ *
+ * Author: Baojun Xu <baojun.xu@ti.com>
+ */
+
+#ifndef __TAS2783_H__
+#define __TAS2783_H__
+
+#define TAS2783_DEVICE_RATES		(SNDRV_PCM_RATE_44100 | \
+					SNDRV_PCM_RATE_48000 | \
+					SNDRV_PCM_RATE_96000 | \
+					SNDRV_PCM_RATE_88200)
+
+#define TAS2783_DEVICE_FORMATS		(SNDRV_PCM_FMTBIT_S16_LE | \
+					SNDRV_PCM_FMTBIT_S24_LE | \
+					SNDRV_PCM_FMTBIT_S32_LE)
+
+/* BOOK, PAGE Control Register */
+#define TASDEVICE_REG(book, page, reg)	((book * 256 * 256) + 0x8000 +\
+					(page * 128) + reg)
+
+/*Software Reset */
+#define TAS2873_REG_SWRESET		TASDEVICE_REG(0x0, 0X0, 0x01)
+#define TAS2873_REG_SWRESET_RESET	BIT(0)
+
+/* Volume control */
+#define TAS2783_DVC_LVL			TASDEVICE_REG(0x0, 0x00, 0x1A)
+#define TAS2783_AMP_LEVEL		TASDEVICE_REG(0x0, 0x00, 0x03)
+#define TAS2783_AMP_LEVEL_MASK		GENMASK(5, 1)
+
+/* Calibration data */
+#define TAS2783_CALIBRATION_RE		TASDEVICE_REG(0x0, 0x17, 0x74)
+#define TAS2783_CALIBRATION_RE_LOW	TASDEVICE_REG(0x0, 0x18, 0x14)
+#define TAS2783_CALIBRATION_INV_RE	TASDEVICE_REG(0x0, 0x18, 0x0c)
+#define TAS2783_CALIBRATION_POW		TASDEVICE_REG(0x0, 0x0d, 0x3c)
+#define TAS2783_CALIBRATION_TLIMIT	TASDEVICE_REG(0x0, 0x18, 0x7c)
+
+#define TAS2783_CALIBRATION_SIZE	5	// int data number.
+
+#define TAS2783_DEVICE_ID_START		0x08	// Unique id start
+#define TAS2783_DEVICE_ID_END		0x0F	// Unique id end
+
+/* TAS2783 SDCA Control - function number */
+#define FUNC_NUM_SMART_AMP		0x01
+
+/* TAS2783 SDCA entity */
+#define TAS2783_SDCA_ENT_PDE23		0x0C
+#define TAS2783_SDCA_ENT_PDE22		0x0B
+#define TAS2783_SDCA_ENT_FU21		0x01
+#define TAS2783_SDCA_ENT_UDMPU21	0x10
+
+/* TAS2783 SDCA control */
+#define TAS2783_SDCA_CTL_REQ_POWER_STATE	0x01
+#define TAS2783_SDCA_CTL_FU_MUTE		0x01
+#define TAS2783_SDCA_CTL_UDMPU_CLUSTER		0x10
+
+#define TAS2783_DEVICE_CHANNEL_LEFT	1
+#define TAS2783_DEVICE_CHANNEL_RIGHT	2
+
+#define TAS2783_MAX_NODES		100
+#define TAS2783_MAX_CALIDATA_SIZE	252
+#define TAS2783_CALIDATA_NAME		L"CALI_DATA"
+
+struct tas2783_firmware_node {
+	unsigned int vendor_id;
+	unsigned int file_id;
+	unsigned int version_id;
+	unsigned int length;
+	unsigned int download_addr;
+	unsigned char *start_addr;
+};
+
+struct calibration_data {
+	unsigned long total_sz;
+	unsigned char data[TAS2783_MAX_CALIDATA_SIZE];
+};
+
+struct tasdevice_priv {
+	struct tas2783_firmware_node firmware_node[TAS2783_MAX_NODES];
+	struct snd_soc_component *component;
+	struct calibration_data cali_data;
+	struct sdw_slave *sdw_peripheral;
+	struct sdw_bus_params params;
+	struct mutex codec_lock;
+	struct regmap *regmap;
+	struct mutex dev_lock;
+	struct device *dev;
+	struct tm tm;
+
+	enum sdw_slave_status status;
+
+	bool hw_init;
+
+	unsigned int chip_id;
+	unsigned char rca_binaryname[64];
+	unsigned char dev_name[32];
+
+	void *codec;
+};
+
+struct sdw_stream_data {
+	struct sdw_stream_runtime *sdw_stream;
+};
+
+#endif /*__TAS2783_H__ */