ASoC: Yet another round of SDCA fixes

Charles Keepax <ckeepax@opensource.cirrus.com> says:

Another round of SDCA fixes a couple of fix to the IRQ cleanup
from Richard, and a minor tweak to the IRQ handling from me.
This commit is contained in:
Mark Brown
2026-04-09 19:39:32 +01:00
11 changed files with 184 additions and 46 deletions

View File

@@ -12,8 +12,8 @@ maintainers:
- Baojun Xu <baojun.xu@ti.com>
description: >
The TAS2552 can receive its reference clock via MCLK, BCLK, IVCLKIN pin or
use the internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL,
The TAS2552 can receive its reference clock via MCLK, BCLK, IVCLKIN pin or
use the internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL,
the PDM reference clock is also selectable: PLL, IVCLKIN, BCLK or MCLK.
For system integration the dt-bindings/sound/tas2552.h header file provides
@@ -34,6 +34,9 @@ properties:
maxItems: 1
description: gpio pin to enable/disable the device
'#sound-dai-cells':
const: 0
required:
- compatible
- reg
@@ -41,7 +44,10 @@ required:
- iovdd-supply
- avdd-supply
additionalProperties: false
allOf:
- $ref: dai-common.yaml#
unevaluatedProperties: false
examples:
- |
@@ -54,6 +60,7 @@ examples:
audio-codec@41 {
compatible = "ti,tas2552";
reg = <0x41>;
#sound-dai-cells = <0>;
vbat-supply = <&reg_vbat>;
iovdd-supply = <&reg_iovdd>;
avdd-supply = <&reg_avdd>;

View File

@@ -69,6 +69,8 @@ struct sdca_interrupt_info {
int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *interrupt_info,
int sdca_irq, const char *name, irq_handler_t handler,
void *data);
void sdca_irq_free(struct device *dev, struct sdca_interrupt_info *interrupt_info,
int sdca_irq, const char *name, void *data);
int sdca_irq_data_populate(struct device *dev, struct regmap *function_regmap,
struct snd_soc_component *component,
struct sdca_function_data *function,
@@ -81,6 +83,9 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *function_regmap,
int sdca_irq_populate(struct sdca_function_data *function,
struct snd_soc_component *component,
struct sdca_interrupt_info *info);
void sdca_irq_cleanup(struct device *dev,
struct sdca_function_data *function,
struct sdca_interrupt_info *info);
struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev,
struct regmap *regmap, int irq);

View File

@@ -99,17 +99,33 @@ static const struct dmi_system_id soc_sdw_quirk_table[] = {
.callback = soc_sdw_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "21YW"),
DMI_MATCH(DMI_PRODUCT_SKU, "21YW"),
},
.driver_data = (void *)(ASOC_SDW_CODEC_SPKR),
.driver_data = (void *)((ASOC_SDW_CODEC_SPKR) | (ASOC_SDW_ACP_DMIC)),
},
{
.callback = soc_sdw_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "21YX"),
DMI_MATCH(DMI_PRODUCT_SKU, "21YX"),
},
.driver_data = (void *)(ASOC_SDW_CODEC_SPKR),
.driver_data = (void *)((ASOC_SDW_CODEC_SPKR) | (ASOC_SDW_ACP_DMIC)),
},
{
.callback = soc_sdw_quirk_cb,
.matches = { /* Lenovo P16s G5 AMD */
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_SKU, "21XG"),
},
.driver_data = (void *)(ASOC_SDW_ACP_DMIC),
},
{
.callback = soc_sdw_quirk_cb,
.matches = { /* Lenovo P16s G5 AMD */
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_SKU, "21XH"),
},
.driver_data = (void *)(ASOC_SDW_ACP_DMIC),
},
{
.callback = soc_sdw_quirk_cb,

View File

@@ -142,7 +142,7 @@ static bool nau8325_readable_reg(struct device *dev, unsigned int reg)
static bool nau8325_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case NAU8325_R00_HARDWARE_RST:
case NAU8325_R00_HARDWARE_RST ... NAU8325_R01_SOFTWARE_RST:
case NAU8325_R03_CLK_CTRL ... NAU8325_R06_INT_CLR_STATUS:
case NAU8325_R09_IRQOUT ... NAU8325_R13_DAC_VOLUME:
case NAU8325_R29_DAC_CTRL1 ... NAU8325_R2A_DAC_CTRL2:
@@ -670,6 +670,12 @@ static void nau8325_reset_chip(struct regmap *regmap)
regmap_write(regmap, NAU8325_R00_HARDWARE_RST, 0x0000);
}
static void nau8325_software_reset(struct regmap *regmap)
{
regmap_write(regmap, NAU8325_R01_SOFTWARE_RST, 0x0000);
regmap_write(regmap, NAU8325_R01_SOFTWARE_RST, 0x0000);
}
static void nau8325_init_regs(struct nau8325 *nau8325)
{
struct regmap *regmap = nau8325->regmap;
@@ -856,6 +862,7 @@ static int nau8325_i2c_probe(struct i2c_client *i2c)
nau8325_print_device_properties(nau8325);
nau8325_reset_chip(nau8325->regmap);
nau8325_software_reset(nau8325->regmap);
ret = regmap_read(nau8325->regmap, NAU8325_R02_DEVICE_ID, &value);
if (ret) {
dev_dbg(dev, "Failed to read device id (%d)", ret);

View File

@@ -520,7 +520,8 @@ static int avs_register_i2s_test_boards(struct avs_dev *adev)
if (num_elems > max_ssps) {
dev_err(adev->dev, "board supports only %d SSP, %d specified\n",
max_ssps, num_elems);
return -EINVAL;
ret = -EINVAL;
goto exit;
}
for (ssp_port = 0; ssp_port < num_elems; ssp_port++) {
@@ -528,11 +529,13 @@ static int avs_register_i2s_test_boards(struct avs_dev *adev)
for_each_set_bit(tdm_slot, &tdm_slots, 16) {
ret = avs_register_i2s_test_board(adev, ssp_port, tdm_slot);
if (ret)
return ret;
goto exit;
}
}
return 0;
exit:
kfree(array);
return ret;
}
static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach *mach)

View File

@@ -137,6 +137,13 @@ static const struct regmap_config class_dev_regmap_config = {
.unlock = class_regmap_unlock,
};
static void class_remove_functions(void *data)
{
struct sdca_class_drv *drv = data;
sdca_dev_unregister_functions(drv->sdw);
}
static void class_boot_work(struct work_struct *work)
{
struct sdca_class_drv *drv = container_of(work,
@@ -157,6 +164,11 @@ static void class_boot_work(struct work_struct *work)
if (ret)
goto err;
/* Ensure function drivers are removed before the IRQ is destroyed */
ret = devm_add_action_or_reset(drv->dev, class_remove_functions, drv);
if (ret)
goto err;
dev_dbg(drv->dev, "boot work complete\n");
pm_runtime_mark_last_busy(drv->dev);
@@ -168,15 +180,6 @@ static void class_boot_work(struct work_struct *work)
pm_runtime_put_sync(drv->dev);
}
static void class_dev_remove(void *data)
{
struct sdca_class_drv *drv = data;
cancel_work_sync(&drv->boot_work);
sdca_dev_unregister_functions(drv->sdw);
}
static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
{
struct device *dev = &sdw->dev;
@@ -230,15 +233,19 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, class_dev_remove, drv);
if (ret)
return ret;
queue_work(system_long_wq, &drv->boot_work);
return 0;
}
static void class_sdw_remove(struct sdw_slave *sdw)
{
struct device *dev = &sdw->dev;
struct sdca_class_drv *drv = dev_get_drvdata(dev);
cancel_work_sync(&drv->boot_work);
}
static int class_suspend(struct device *dev)
{
struct sdca_class_drv *drv = dev_get_drvdata(dev);
@@ -330,6 +337,7 @@ static struct sdw_driver class_sdw_driver = {
},
.probe = class_sdw_probe,
.remove = class_sdw_remove,
.id_table = class_sdw_id,
.ops = &class_sdw_ops,
};

View File

@@ -198,6 +198,14 @@ static int class_function_component_probe(struct snd_soc_component *component)
return sdca_irq_populate(drv->function, component, core->irq_info);
}
static void class_function_component_remove(struct snd_soc_component *component)
{
struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
struct sdca_class_drv *core = drv->core;
sdca_irq_cleanup(component->dev, drv->function, core->irq_info);
}
static int class_function_set_jack(struct snd_soc_component *component,
struct snd_soc_jack *jack, void *d)
{
@@ -209,6 +217,7 @@ static int class_function_set_jack(struct snd_soc_component *component,
static const struct snd_soc_component_driver class_function_component_drv = {
.probe = class_function_component_probe,
.remove = class_function_component_remove,
.endianness = 1,
};
@@ -408,6 +417,13 @@ static int class_function_probe(struct auxiliary_device *auxdev,
return 0;
}
static void class_function_remove(struct auxiliary_device *auxdev)
{
struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
sdca_irq_cleanup(drv->dev, drv->function, drv->core->irq_info);
}
static int class_function_runtime_suspend(struct device *dev)
{
struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
@@ -560,6 +576,7 @@ static struct auxiliary_driver class_function_drv = {
},
.probe = class_function_probe,
.remove = class_function_remove,
.id_table = class_function_id_table
};
module_auxiliary_driver(class_function_drv);

View File

@@ -117,9 +117,7 @@ static irqreturn_t function_status_handler(int irq, void *data)
status = val;
for_each_set_bit(mask, &status, BITS_PER_BYTE) {
mask = 1 << mask;
switch (mask) {
switch (BIT(mask)) {
case SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION:
//FIXME: Add init writes
break;
@@ -140,7 +138,7 @@ static irqreturn_t function_status_handler(int irq, void *data)
}
}
ret = regmap_write(interrupt->function_regmap, reg, val);
ret = regmap_write(interrupt->function_regmap, reg, val & 0x7F);
if (ret < 0) {
dev_err(dev, "failed to clear function status: %d\n", ret);
goto error;
@@ -252,8 +250,7 @@ static int sdca_irq_request_locked(struct device *dev,
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(dev, irq, NULL, handler,
IRQF_ONESHOT, name, data);
ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name, data);
if (ret)
return ret;
@@ -264,6 +261,22 @@ static int sdca_irq_request_locked(struct device *dev,
return 0;
}
static void sdca_irq_free_locked(struct device *dev, struct sdca_interrupt_info *info,
int sdca_irq, const char *name, void *data)
{
int irq;
irq = regmap_irq_get_virq(info->irq_data, sdca_irq);
if (irq < 0)
return;
free_irq(irq, data);
info->irqs[sdca_irq].irq = 0;
dev_dbg(dev, "freed irq %d for %s\n", irq, name);
}
/**
* sdca_irq_request - request an individual SDCA interrupt
* @dev: Pointer to the struct device against which things should be allocated.
@@ -302,6 +315,30 @@ int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info,
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA");
/**
* sdca_irq_free - free an individual SDCA interrupt
* @dev: Pointer to the struct device.
* @info: Pointer to the interrupt information structure.
* @sdca_irq: SDCA interrupt position.
* @name: Name to be given to the IRQ.
* @data: Private data pointer that will be passed to the handler.
*
* Typically this is handled internally by sdca_irq_cleanup, however if
* a device requires custom IRQ handling this can be called manually before
* calling sdca_irq_cleanup, which will then skip that IRQ whilst processing.
*/
void sdca_irq_free(struct device *dev, struct sdca_interrupt_info *info,
int sdca_irq, const char *name, void *data)
{
if (sdca_irq < 0 || sdca_irq >= SDCA_MAX_INTERRUPTS)
return;
guard(mutex)(&info->irq_lock);
sdca_irq_free_locked(dev, info, sdca_irq, name, data);
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_free, "SND_SOC_SDCA");
/**
* sdca_irq_data_populate - Populate common interrupt data
* @dev: Pointer to the Function device.
@@ -328,8 +365,8 @@ int sdca_irq_data_populate(struct device *dev, struct regmap *regmap,
if (!dev)
return -ENODEV;
name = devm_kasprintf(dev, GFP_KERNEL, "%s %s %s", function->desc->name,
entity->label, control->label);
name = kasprintf(GFP_KERNEL, "%s %s %s", function->desc->name,
entity->label, control->label);
if (!name)
return -ENOMEM;
@@ -516,6 +553,35 @@ int sdca_irq_populate(struct sdca_function_data *function,
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA");
/**
* sdca_irq_cleanup - Free all the individual IRQs for an SDCA Function
* @dev: Device pointer against which the sdca_interrupt_info was allocated.
* @function: Pointer to the SDCA Function.
* @info: Pointer to the SDCA interrupt info for this device.
*
* Typically this would be called from the driver for a single SDCA Function.
*/
void sdca_irq_cleanup(struct device *dev,
struct sdca_function_data *function,
struct sdca_interrupt_info *info)
{
int i;
guard(mutex)(&info->irq_lock);
for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
struct sdca_interrupt *interrupt = &info->irqs[i];
if (interrupt->function != function || !interrupt->irq)
continue;
sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt);
kfree(interrupt->name);
}
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA");
/**
* sdca_irq_allocate - allocate an SDCA interrupt structure for a device
* @sdev: Device pointer against which things should be allocated.
@@ -564,13 +630,12 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA");
static void irq_enable_flags(struct sdca_function_data *function,
struct sdca_interrupt_info *info, bool early)
{
struct sdca_interrupt *interrupt;
int i;
for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
interrupt = &info->irqs[i];
struct sdca_interrupt *interrupt = &info->irqs[i];
if (!interrupt || interrupt->function != function)
if (!interrupt->irq || interrupt->function != function)
continue;
switch (SDCA_CTL_TYPE(interrupt->entity->type,
@@ -623,13 +688,12 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA");
void sdca_irq_disable(struct sdca_function_data *function,
struct sdca_interrupt_info *info)
{
struct sdca_interrupt *interrupt;
int i;
for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
interrupt = &info->irqs[i];
struct sdca_interrupt *interrupt = &info->irqs[i];
if (!interrupt || interrupt->function != function)
if (!interrupt->irq || interrupt->function != function)
continue;
disable_irq(interrupt->irq);

View File

@@ -219,6 +219,7 @@ EXPORT_SYMBOL_NS(hda_dsp_pcm_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_component *scomp = sdev->component;
@@ -268,8 +269,17 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
return -ENODEV;
}
/* minimum as per HDA spec */
snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
/*
* Set period size constraint to ensure BDLE buffer length and
* start address alignment requirements are met. Align to 128
* bytes for newer Intel platforms, with older ones using 4 byte alignment.
*/
if (chip_info->hw_ip_version >= SOF_INTEL_ACE_4_0)
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
else
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
/* avoid circular buffer wrap in middle of period */
snd_pcm_hw_constraint_integer(substream->runtime,

View File

@@ -1133,8 +1133,7 @@ static void hda_generic_machine_select(struct snd_sof_dev *sdev,
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
static bool is_endpoint_present(struct sdw_slave *sdw_device,
struct asoc_sdw_codec_info *dai_info, int dai_type)
static bool is_endpoint_present(struct sdw_slave *sdw_device, int dai_type)
{
int i;
@@ -1145,7 +1144,7 @@ static bool is_endpoint_present(struct sdw_slave *sdw_device,
}
for (i = 0; i < sdw_device->sdca_data.num_functions; i++) {
if (dai_type == dai_info->dais[i].dai_type)
if (dai_type == asoc_sdw_get_dai_type(sdw_device->sdca_data.function[i].type))
return true;
}
dev_dbg(&sdw_device->dev, "Endpoint DAI type %d not found\n", dai_type);
@@ -1202,11 +1201,10 @@ static struct snd_soc_acpi_adr_device *find_acpi_adr_device(struct device *dev,
}
for (j = 0; j < codec_info_list[i].dai_num; j++) {
/* Check if the endpoint is present by the SDCA DisCo table */
if (!is_endpoint_present(sdw_device, &codec_info_list[i],
codec_info_list[i].dais[j].dai_type))
if (!is_endpoint_present(sdw_device, codec_info_list[i].dais[j].dai_type))
continue;
endpoints[ep_index].num = ep_index;
endpoints[ep_index].num = j;
if (codec_info_list[i].dais[j].dai_type == SOC_SDW_DAI_TYPE_AMP) {
/* Assume all amp are aggregated */
endpoints[ep_index].aggregated = 1;

View File

@@ -802,6 +802,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
break;
/* Left justified */
case SND_SOC_DAIFMT_MSB:
cr1 |= SAI_XCR1_CKSTR;
frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
break;
/* Right justified */
@@ -809,9 +810,11 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF;
break;
case SND_SOC_DAIFMT_DSP_A:
cr1 |= SAI_XCR1_CKSTR;
frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF;
break;
case SND_SOC_DAIFMT_DSP_B:
cr1 |= SAI_XCR1_CKSTR;
frcr |= SAI_XFRCR_FSPOL;
break;
default: