Commit 634f3b4e authored by Simon Trimmer's avatar Simon Trimmer Committed by Takashi Iwai
Browse files

ALSA: hda: cs35l56: Perform firmware download in the background



It is possible that during system boot when there multiple devices
attempting simultaneous initialization on a slow control bus the
download of firmware and tuning data may take a user perceivable amount
of time (a slow I2C bus with 4 amps this work could take over 2
seconds).

Adopt a pattern used in the ASoC driver and perform this activity in a
background thread so that interactive performance is not impaired. The
system_long_wq is a parallel workqueue and driver instances will perform
their firmware downloads in parallel to make best use of available bus
bandwidth.

Signed-off-by: default avatarSimon Trimmer <simont@opensource.cirrus.com>
Link: https://lore.kernel.org/20240618130011.62860-1-simont@opensource.cirrus.com


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent f900a055
Loading
Loading
Loading
Loading
+73 −17
Original line number Diff line number Diff line
@@ -50,11 +50,19 @@ static const struct reg_sequence cs35l56_hda_dai_config[] = {

};

static void cs35l56_hda_wait_dsp_ready(struct cs35l56_hda *cs35l56)
{
	/* Wait for patching to complete */
	flush_work(&cs35l56->dsp_work);
}

static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
{
	unsigned int val;
	int ret;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	pm_runtime_get_sync(cs35l56->base.dev);
	ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY);
	if (ret == 0) {
@@ -180,6 +188,8 @@ static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol,
	unsigned int reg_val;
	int i;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	regmap_read(cs35l56->base.regmap, kcontrol->private_value, &reg_val);
	reg_val &= CS35L56_ASP_TXn_SRC_MASK;

@@ -203,6 +213,8 @@ static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol,
	if (item >= CS35L56_NUM_INPUT_SRC)
		return -EINVAL;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value,
				 CS35L56_INPUT_MASK, cs35l56_tx_input_values[item],
				 &changed);
@@ -227,6 +239,8 @@ static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol,
	unsigned int pos;
	int ret;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &pos);
	if (ret)
		return ret;
@@ -248,6 +262,8 @@ static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol,
	    (pos > CS35L56_MAIN_POSTURE_MAX))
		return -EINVAL;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	ret = regmap_update_bits_check(cs35l56->base.regmap,
				       CS35L56_MAIN_POSTURE_NUMBER,
				       CS35L56_MAIN_POSTURE_MASK,
@@ -291,6 +307,8 @@ static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol,
	int vol;
	int ret;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, &raw_vol);

	if (ret)
@@ -323,6 +341,8 @@ static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol,
	raw_vol = (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) <<
		  CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;

	cs35l56_hda_wait_dsp_ready(cs35l56);

	ret = regmap_update_bits_check(cs35l56->base.regmap,
				       CS35L56_MAIN_RENDER_USER_VOLUME,
				       CS35L56_MAIN_RENDER_USER_VOLUME_MASK,
@@ -539,8 +559,9 @@ static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmw
	kfree(coeff_filename);
}

static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56)
static void cs35l56_hda_create_dsp_controls_work(struct work_struct *work)
{
	struct cs35l56_hda *cs35l56 = container_of(work, struct cs35l56_hda, control_work);
	struct hda_cs_dsp_ctl_info info;

	info.device_name = cs35l56->amp_name;
@@ -566,7 +587,7 @@ static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56)
		dev_info(cs35l56->base.dev, "Calibration applied\n");
}

static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
{
	const struct firmware *coeff_firmware = NULL;
	const struct firmware *wmfw_firmware = NULL;
@@ -574,15 +595,34 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
	char *wmfw_filename = NULL;
	unsigned int preloaded_fw_ver;
	bool firmware_missing;
	int ret = 0;
	bool add_dsp_controls_required = false;
	int ret;

	/*
	 * control_work must be flushed before proceeding, but we can't do that
	 * here as it would create a deadlock on controls_rwsem so it must be
	 * performed before queuing dsp_work.
	 */
	WARN_ON_ONCE(work_busy(&cs35l56->control_work));

	/* Prepare for a new DSP power-up */
	/*
	 * Prepare for a new DSP power-up. If the DSP has had firmware
	 * downloaded previously then it needs to be powered down so that it
	 * can be updated and if hadn't been patched before then the controls
	 * will need to be added once firmware download succeeds.
	 */
	if (cs35l56->base.fw_patched)
		cs_dsp_power_down(&cs35l56->cs_dsp);
	else
		add_dsp_controls_required = true;

	cs35l56->base.fw_patched = false;

	pm_runtime_get_sync(cs35l56->base.dev);
	ret = pm_runtime_resume_and_get(cs35l56->base.dev);
	if (ret < 0) {
		dev_err(cs35l56->base.dev, "Failed to resume and get %d\n", ret);
		return;
	}

	/*
	 * The firmware can only be upgraded if it is currently running
@@ -606,7 +646,6 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
	 */
	if (!coeff_firmware && firmware_missing) {
		dev_err(cs35l56->base.dev, ".bin file required but not found\n");
		ret = -ENOENT;
		goto err_fw_release;
	}

@@ -659,6 +698,15 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
			  CS35L56_FIRMWARE_MISSING);
	cs35l56->base.fw_patched = true;

	/*
	 * Adding controls is deferred to prevent a lock inversion - ALSA takes
	 * the controls_rwsem when adding a control, the get() / put()
	 * functions of a control are called holding controls_rwsem and those
	 * that depend on running firmware wait for dsp_work() to complete.
	 */
	if (add_dsp_controls_required)
		queue_work(system_long_wq, &cs35l56->control_work);

	ret = cs_dsp_run(&cs35l56->cs_dsp);
	if (ret)
		dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
@@ -678,8 +726,13 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
					   coeff_firmware, coeff_filename);
err_pm_put:
	pm_runtime_put(cs35l56->base.dev);
}

	return ret;
static void cs35l56_hda_dsp_work(struct work_struct *work)
{
	struct cs35l56_hda *cs35l56 = container_of(work, struct cs35l56_hda, dsp_work);

	cs35l56_hda_fw_load(cs35l56);
}

static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data)
@@ -687,7 +740,6 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas
	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
	struct hda_component_parent *parent = master_data;
	struct hda_component *comp;
	int ret;

	comp = hda_component_from_index(parent, cs35l56->index);
	if (!comp)
@@ -701,12 +753,10 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas
	strscpy(comp->name, dev_name(dev), sizeof(comp->name));
	comp->playback_hook = cs35l56_hda_playback_hook;

	ret = cs35l56_hda_fw_load(cs35l56);
	if (ret)
		return ret;
	flush_work(&cs35l56->control_work);
	queue_work(system_long_wq, &cs35l56->dsp_work);

	cs35l56_hda_create_controls(cs35l56);
	cs35l56_hda_add_dsp_controls(cs35l56);

#if IS_ENABLED(CONFIG_SND_DEBUG)
	cs35l56->debugfs_root = debugfs_create_dir(dev_name(cs35l56->base.dev), sound_debugfs_root);
@@ -724,6 +774,9 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void *
	struct hda_component_parent *parent = master_data;
	struct hda_component *comp;

	cancel_work_sync(&cs35l56->dsp_work);
	cancel_work_sync(&cs35l56->control_work);

	cs35l56_hda_remove_controls(cs35l56);

#if IS_ENABLED(CONFIG_SND_DEBUG)
@@ -752,6 +805,9 @@ static int cs35l56_hda_system_suspend(struct device *dev)
{
	struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);

	cs35l56_hda_wait_dsp_ready(cs35l56);
	flush_work(&cs35l56->control_work);

	if (cs35l56->playing)
		cs35l56_hda_pause(cs35l56);

@@ -850,11 +906,8 @@ static int cs35l56_hda_system_resume(struct device *dev)

	ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
	dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
	if (ret > 0) {
		ret = cs35l56_hda_fw_load(cs35l56);
		if (ret)
			return ret;
	}
	if (ret > 0)
		queue_work(system_long_wq, &cs35l56->dsp_work);

	if (cs35l56->playing)
		cs35l56_hda_play(cs35l56);
@@ -972,6 +1025,9 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int hid, int id)
	mutex_init(&cs35l56->base.irq_lock);
	dev_set_drvdata(cs35l56->base.dev, cs35l56);

	INIT_WORK(&cs35l56->dsp_work, cs35l56_hda_dsp_work);
	INIT_WORK(&cs35l56->control_work, cs35l56_hda_create_dsp_controls_work);

	ret = cs35l56_hda_read_acpi(cs35l56, hid, id);
	if (ret)
		goto err;
+3 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/firmware/cirrus/cs_dsp.h>
#include <linux/firmware/cirrus/wmfw.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include <sound/cs35l56.h>

struct dentry;
@@ -21,6 +22,8 @@ struct dentry;
struct cs35l56_hda {
	struct cs35l56_base base;
	struct hda_codec *codec;
	struct work_struct dsp_work;
	struct work_struct control_work;

	int index;
	const char *system_name;