RE: [EXTERNAL] Re: [PATCH v5] ASoc: tas2783: Add tas2783 codec driver

From: Ding, Shenghao
Date: Tue Jan 16 2024 - 03:01:40 EST


Thanks for your review comment.

> -----Original Message-----
> From: Pierre-Louis Bossart <pierre-louis.bossart@xxxxxxxxxxxxxxx>
> Sent: Monday, January 15, 2024 5:34 PM
> To: Ding, Shenghao <shenghao-ding@xxxxxx>; broonie@xxxxxxxxxx
> Cc: andriy.shevchenko@xxxxxxxxxxxxxxx; lgirdwood@xxxxxxxxx;
> perex@xxxxxxxx; 13916275206@xxxxxxx; alsa-devel@xxxxxxxxxxxxxxxx;
> linux-kernel@xxxxxxxxxxxxxxx; liam.r.girdwood@xxxxxxxxx;
> mengdong.lin@xxxxxxxxx; yung-chuan.liao@xxxxxxxxxxxxxxx; Xu, Baojun
> <baojun.xu@xxxxxx>; Lu, Kevin <kevin-lu@xxxxxx>; Gupta, Peeyush
> <peeyush@xxxxxx>; Navada Kanyana, Mukund <navada@xxxxxx>;
> tiwai@xxxxxxx
> Subject: [EXTERNAL] Re: [PATCH v5] ASoc: tas2783: Add tas2783 codec
> driver
>
> This version is much better than previous ones, but can be improved further,
> specifically on the identification of firmware based on the unique_id and
> pm_runtime. See additional nit-picks and suggestions below.
>
>
> > ---
> > Change in v5:
> > - simplify tasdevice_set_sdw_stream
> > - fixed some Linux coding style
> > - fixed the spelling mistakes
> > - Select left/right channel based on unique id
> > - a longer description has been added
> > - remove unused crc8 in KCONFIG
>
> Don't you need a 'select CRC32' though? ...
>
> Maybe it's already set by other Kconfigs.
>
>
> > +/* Update the calibrate data, including speaker impedance, f0, etc, into
> algo.
> > + * Calibrate data is done by manufacturer in the factory. These data
> > +are used
> > + * by Algo for calucating the speaker temperature, speaker membrance
> > +excursion
> > + * and f0 in real time during playback.
> > + * In case of no or valid calibrated data, dsp will still works with
> > +default
> > + * calibrated data inside algo.
>
> suggested edits:
>
> Load the calibration data, including speaker impedance, f0, etc.
> Calibration is done by the manufacturer in the factory. The calibration data
> are used by the algorithm for calculating the speaker temperature, speaker
> membrance excursion and f0 in real time during playback.
> The DSP will work with default data values if calibrated data are missing or
> are invalid.
>
>
> > +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 *tas_priv =
> > + snd_soc_component_get_drvdata(component);
> > + struct sdw_stream_config stream_config = {0};
> > + struct sdw_port_config port_config = {0};
> > + struct sdw_stream_runtime *sdw_stream;
> > + int ret;
> > +
> > + dev_dbg(dai->dev, "%s %s", __func__, dai->name);
> > + sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
> > +
> > + if (!sdw_stream)
> > + return -EINVAL;
> > +
> > + if (!tas_priv->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;
> > +
> > + ret = sdw_stream_add_slave(tas_priv->sdw_peripheral,
> > + &stream_config, &port_config, 1, sdw_stream);
> > + if (ret) {
> > + dev_err(dai->dev, "Unable to configure port\n");
> > + return ret;
> > + }
> > +
> > + dev_dbg(dai->dev, "%s fomrat: %i rate: %i\n", __func__,
>
> typo: format
>
> > + params_format(params), params_rate(params));
> > +
> > + return 0;
> > +}
> > +
>
> > +static int tasdevice_mute(struct snd_soc_dai *dai, int mute,
> > + int direction)
> > +{
> > + struct snd_soc_component *component = dai->component;
> > + struct tasdevice_priv *tas_dev =
> > + snd_soc_component_get_drvdata(component);
> > + struct regmap *map = tas_dev->regmap;
> > + int ret;
> > +
> > + dev_dbg(tas_dev->dev, "Mute or unmute %d.\n", mute);
> > +
> > + if (mute) {
> > + /* Echo channel can't be shutdown while tas2783 must keep
> > + * working state while playback is on.
>
> Consider rewording that comment, two 'while' in the same sentence make it
> hard to parse.
>
> > + */
> > + if (direction == SNDRV_PCM_STREAM_CAPTURE
> > + && tas_dev->pstream == true)
> > + return 0;
> > + /* FU23 mute (0x40400108) */
> > + ret = regmap_write(map,
> > + SDW_SDCA_CTL(TAS2783_FUNC_TYPE_SMART_AMP,
> > + TAS2783_SDCA_ENT_FU23,
> TAS2783_SDCA_CTL_FU_MUTE, 0),
> > + 1);
> > + ret |= regmap_write(map, TASDEVICE_REG(0, 0, 0x02), 0x1a);
>
> ORin error codecs is not quite right
>
> > + tas_dev->pstream = false;
> > + } else {
> > + /* FU23 Unmute, 0x40400108. */
> > + ret = regmap_write(map,
> > + SDW_SDCA_CTL(TAS2783_FUNC_TYPE_SMART_AMP,
> > + TAS2783_SDCA_ENT_FU23,
> TAS2783_SDCA_CTL_FU_MUTE, 0),
> > + 0);
> > + ret |= regmap_write(map, TASDEVICE_REG(0, 0, 0x02), 0x0);
> > + if (direction == SNDRV_PCM_STREAM_PLAYBACK)
> > + tas_dev->pstream = true;
> > + }
> > +
> > + if (ret)
> > + dev_err(tas_dev->dev, "Mute or unmute %d failed %d.\n",
> > + mute, ret);
> > +
> > + return ret;
> > +}
> > +
>
> > +static int tasdevice_init(struct tasdevice_priv *tas_dev) {
> > + int ret;
> > +
> > + 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:%d.\n",
> > + __func__, ret);
> > + }
> > +
> > + tas2783_reset(tas_dev);
> > + /* tas2783-8[9,...,f].bin was copied into /lib/firmware/ */
>
> You need to add a comment to explain what those files contain, it's not clear
> at all if they are just 'firmware' or if they contain tables.
>
> > + scnprintf(tas_dev->rca_binaryname, 64, "tas2783-%01x.bin",
> > + tas_dev->sdw_peripheral->id.unique_id);
>
> And more specifically the use of the unique_id is problematic since it's only
> relevant in the context of one link. If you have two amps on separate links,
> the unique_id is irrelevant.
>
> > +
> > + ret = request_firmware_nowait(THIS_MODULE,
> FW_ACTION_UEVENT,
> > + tas_dev->rca_binaryname, tas_dev->dev, GFP_KERNEL,
> > + tas_dev, tasdevice_rca_ready);
> > + if (ret) {
> > + dev_dbg(tas_dev->dev,
> > + "%s: request_firmware %x open status: %d.\n",
> > + __func__, tas_dev->sdw_peripheral->id.unique_id, ret);
> > + }
> > +
> > + /* set autosuspend parameters */
> > + pm_runtime_set_autosuspend_delay(tas_dev->dev, 3000);
> > + pm_runtime_use_autosuspend(tas_dev->dev);
> > +
> > + /* make sure the device does not suspend immediately */
> > + pm_runtime_mark_last_busy(tas_dev->dev);
> > +
> > + pm_runtime_enable(tas_dev->dev);
>
> You are handling pm_runtime here...
>
> > +
> > + 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;
> > + int nval;
> > + int i, j;
> > + u32 bit;
> > + unsigned long addr;
> > + struct sdw_dpn_prop *dpn;
> > +
> > + 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 = BIT(2); /* BITMAP: 00000100 */
> > + prop->sink_ports = BIT(1); /* 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;
> > +
> > + return 0;
> > +}
> > +
> > +static int tasdevice_io_init(struct device *dev, struct sdw_slave
> > +*slave) {
> > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
> > +
> > + if (tas_priv->hw_init)
> > + return 0;
> > +
> > + if (tas_priv->first_hw_init) {
> > + regcache_cache_only(tas_priv->regmap, false);
> > + regcache_cache_bypass(tas_priv->regmap, true);
> > + } else {
> > + /*
> > + * 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);
>
> ... and here.
>
> This isn't quite right, you should only use set_active() here and move all other
> parts in the probe.
>
> > + }
> > +
> > + pm_runtime_get_noresume(&slave->dev);
> > +
> > + /* sw reset */
> > + regmap_write(tas_priv->regmap, 0x8001, 0x01);
> > +
> > + if (tas_priv->first_hw_init) {
> > + regcache_cache_bypass(tas_priv->regmap, false);
> > + regcache_mark_dirty(tas_priv->regmap);
> > + } else
> > + tas_priv->first_hw_init = true;
> > + /* Mark Slave initialization complete */
> > + tas_priv->hw_init = true;
>
> if you do a get_noresume() you've got to do a put_noidle() here for
> symmetry.
>
> > +
> > + return 0;
> > +}
> > +