ASoC/soundwire: add initial support for SDCA

Merge series from Bard Liao <yung-chuan.liao@linux.intel.com>:

We need to get rt712 version by reading SDCA version and functions.
This patch series adds initial support for SDCA and add a helper to tell
if the codec is RT712_VB.

This series may go via the ASoC tree with Vinod's Acked-by tag.

Bard Liao (1):
  soundwire: sdw_intel: include linux/acpi.h

Pierre-Louis Bossart (10):
  ASoC/soundwire: remove sdw_slave_extended_id
  ASoC: SDCA: add initial module
  soundwire: slave: lookup SDCA version and functions
  ASoC: SDCA: add quirk function for RT712_VB match
  ASoC: rt712-sdca: detect the SMART_MIC function during the probe stage
  ASoC: soc-acpi: introduce new 'machine check' callback
  ASoC: sdw_utils: add SmartMic DAI for RT712 VB
  ASoC: sdw_utils: add SmartMic DAI for RT713 VB
  ASoC: Intel: soc-acpi: add is_device_rt712_vb() helper
  ASoC: SOF: Intel: hda: use machine_check() for SoundWire

 drivers/soundwire/Kconfig                     |   1 +
 drivers/soundwire/amd_init.c                  |  12 +-
 drivers/soundwire/intel_init.c                |  13 +-
 drivers/soundwire/slave.c                     |  14 ++
 include/linux/soundwire/sdw.h                 |   9 +-
 include/linux/soundwire/sdw_amd.h             |   7 +-
 include/linux/soundwire/sdw_intel.h           |   8 +-
 include/sound/sdca.h                          |  62 +++++++
 include/sound/sdca_function.h                 |  55 ++++++
 include/sound/soc-acpi.h                      |   8 +-
 sound/soc/Kconfig                             |   1 +
 sound/soc/Makefile                            |   1 +
 sound/soc/amd/ps/pci-ps.c                     |   3 +-
 sound/soc/codecs/rt712-sdca-sdw.c             |   1 +
 sound/soc/codecs/rt712-sdca.c                 |  38 +++-
 sound/soc/codecs/rt712-sdca.h                 |   1 +
 sound/soc/intel/Kconfig                       |   5 +
 sound/soc/intel/common/Makefile               |   3 +
 .../intel/common/soc-acpi-intel-mtl-match.c   |  51 ++++++
 .../intel/common/soc-acpi-intel-sdca-quirks.c |  42 +++++
 .../intel/common/soc-acpi-intel-sdca-quirks.h |  14 ++
 sound/soc/sdca/Kconfig                        |  11 ++
 sound/soc/sdca/Makefile                       |   5 +
 sound/soc/sdca/sdca_device.c                  |  67 +++++++
 sound/soc/sdca/sdca_functions.c               | 173 ++++++++++++++++++
 sound/soc/sdw_utils/soc_sdw_utils.c           |  18 +-
 sound/soc/soc-acpi.c                          |  30 +--
 sound/soc/sof/amd/acp-common.c                |   3 +-
 sound/soc/sof/intel/hda.c                     |  19 +-
 29 files changed, 610 insertions(+), 65 deletions(-)
 create mode 100644 include/sound/sdca.h
 create mode 100644 include/sound/sdca_function.h
 create mode 100644 sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c
 create mode 100644 sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h
 create mode 100644 sound/soc/sdca/Kconfig
 create mode 100644 sound/soc/sdca/Makefile
 create mode 100644 sound/soc/sdca/sdca_device.c
 create mode 100644 sound/soc/sdca/sdca_functions.c

--
2.43.0
This commit is contained in:
Mark Brown 2024-10-18 21:01:45 +01:00
commit 478fc2f421
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
29 changed files with 611 additions and 66 deletions

View File

@ -6,6 +6,7 @@
menuconfig SOUNDWIRE
tristate "SoundWire support"
depends on ACPI || OF
depends on SND_SOC_SDCA_OPTIONAL
help
SoundWire is a 2-Pin interface with data and clock line ratified
by the MIPI Alliance. SoundWire is used for transporting data

View File

@ -177,7 +177,7 @@ EXPORT_SYMBOL_NS(sdw_amd_probe, SOUNDWIRE_AMD_INIT);
void sdw_amd_exit(struct sdw_amd_ctx *ctx)
{
sdw_amd_cleanup(ctx);
kfree(ctx->ids);
kfree(ctx->peripherals);
kfree(ctx);
}
EXPORT_SYMBOL_NS(sdw_amd_exit, SOUNDWIRE_AMD_INIT);
@ -204,10 +204,11 @@ int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
num_slaves++;
}
ctx->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL);
if (!ctx->ids)
ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves),
GFP_KERNEL);
if (!ctx->peripherals)
return -ENOMEM;
ctx->num_slaves = num_slaves;
ctx->peripherals->num_peripherals = num_slaves;
for (index = 0; index < ctx->count; index++) {
if (!(ctx->link_mask & BIT(index)))
continue;
@ -215,8 +216,7 @@ int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
if (amd_manager) {
bus = &amd_manager->bus;
list_for_each_entry(slave, &bus->slaves, node) {
ctx->ids[i].id = slave->id;
ctx->ids[i].link_id = bus->link_id;
ctx->peripherals->array[i] = slave;
i++;
}
}

View File

@ -252,17 +252,16 @@ static struct sdw_intel_ctx
num_slaves++;
}
ctx->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL);
if (!ctx->ids)
ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves),
GFP_KERNEL);
if (!ctx->peripherals)
goto err;
ctx->num_slaves = num_slaves;
ctx->peripherals->num_peripherals = num_slaves;
i = 0;
list_for_each_entry(link, &ctx->link_list, list) {
bus = &link->cdns->bus;
list_for_each_entry(slave, &bus->slaves, node) {
ctx->ids[i].id = slave->id;
ctx->ids[i].link_id = bus->link_id;
ctx->peripherals->array[i] = slave;
i++;
}
}
@ -371,7 +370,7 @@ void sdw_intel_exit(struct sdw_intel_ctx *ctx)
}
sdw_intel_cleanup(ctx);
kfree(ctx->ids);
kfree(ctx->peripherals);
kfree(ctx->ldev);
kfree(ctx);
}

View File

@ -5,6 +5,7 @@
#include <linux/of.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include <sound/sdca.h>
#include "bus.h"
#include "sysfs_local.h"
@ -70,6 +71,17 @@ int sdw_slave_add(struct sdw_bus *bus,
list_add_tail(&slave->node, &bus->slaves);
mutex_unlock(&bus->bus_lock);
/*
* The Soundwire driver probe may optionally register SDCA
* sub-devices, one per Function. This means the information
* on the SDCA revision and the number/type of Functions need
* to be extracted from platform firmware before the SoundWire
* driver probe, and as a consequence before the SoundWire
* device_register() below.
*/
sdca_lookup_interface_revision(slave);
sdca_lookup_functions(slave);
ret = device_register(&slave->dev);
if (ret) {
dev_err(bus->dev, "Failed to add slave: ret %d\n", ret);
@ -259,3 +271,5 @@ int sdw_of_find_slaves(struct sdw_bus *bus)
return 0;
}
MODULE_IMPORT_NS(SND_SOC_SDCA);

View File

@ -10,6 +10,7 @@
#include <linux/irqdomain.h>
#include <linux/mod_devicetable.h>
#include <linux/bitfield.h>
#include <sound/sdca.h>
struct sdw_bus;
struct sdw_slave;
@ -488,9 +489,9 @@ struct sdw_slave_id {
__u8 sdw_version:4;
};
struct sdw_extended_slave_id {
int link_id;
struct sdw_slave_id id;
struct sdw_peripherals {
int num_peripherals;
struct sdw_slave *array[];
};
/*
@ -663,6 +664,7 @@ struct sdw_slave_ops {
* @is_mockup_device: status flag used to squelch errors in the command/control
* protocol for SoundWire mockup devices
* @sdw_dev_lock: mutex used to protect callbacks/remove races
* @sdca_data: structure containing all device data for SDCA helpers
*/
struct sdw_slave {
struct sdw_slave_id id;
@ -686,6 +688,7 @@ struct sdw_slave {
bool first_interrupt_done;
bool is_mockup_device;
struct mutex sdw_dev_lock; /* protect callbacks/remove races */
struct sdca_device_data sdca_data;
};
#define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev)

View File

@ -115,19 +115,16 @@ struct sdw_amd_acpi_info {
* struct sdw_amd_ctx - context allocated by the controller driver probe
*
* @count: link count
* @num_slaves: total number of devices exposed across all enabled links
* @link_mask: bit-wise mask listing SoundWire links reported by the
* Controller
* @ids: array of slave_id, representing Slaves exposed across all enabled
* links
* @pdev: platform device structure
* @peripherals: array representing Peripherals exposed across all enabled links
*/
struct sdw_amd_ctx {
int count;
int num_slaves;
u32 link_mask;
struct sdw_extended_slave_id *ids;
struct platform_device *pdev[AMD_SDW_MAX_MANAGER_COUNT];
struct sdw_peripherals *peripherals;
};
/**

View File

@ -4,6 +4,7 @@
#ifndef __SDW_INTEL_H
#define __SDW_INTEL_H
#include <linux/acpi.h>
#include <linux/irqreturn.h>
#include <linux/soundwire/sdw.h>
@ -286,31 +287,28 @@ struct hdac_bus;
* hardware capabilities after all power dependencies are settled.
* @link_mask: bit-wise mask listing SoundWire links reported by the
* Controller
* @num_slaves: total number of devices exposed across all enabled links
* @handle: ACPI parent handle
* @ldev: information for each link (controller-specific and kept
* opaque here)
* @ids: array of slave_id, representing Slaves exposed across all enabled
* links
* @link_list: list to handle interrupts across all links
* @shim_lock: mutex to handle concurrent rmw access to shared SHIM registers.
* @shim_mask: flags to track initialization of SHIM shared registers
* @shim_base: sdw shim base.
* @alh_base: sdw alh base.
* @peripherals: array representing Peripherals exposed across all enabled links
*/
struct sdw_intel_ctx {
int count;
void __iomem *mmio_base;
u32 link_mask;
int num_slaves;
acpi_handle handle;
struct sdw_intel_link_dev **ldev;
struct sdw_extended_slave_id *ids;
struct list_head link_list;
struct mutex shim_lock; /* lock for access to shared SHIM registers */
u32 shim_mask;
u32 shim_base;
u32 alh_base;
struct sdw_peripherals *peripherals;
};
/**

62
include/sound/sdca.h Normal file
View File

@ -0,0 +1,62 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* The MIPI SDCA specification is available for public downloads at
* https://www.mipi.org/mipi-sdca-v1-0-download
*
* Copyright(c) 2024 Intel Corporation
*/
#ifndef __SDCA_H__
#define __SDCA_H__
struct sdw_slave;
#define SDCA_MAX_FUNCTION_COUNT 8
/**
* sdca_device_desc - short descriptor for an SDCA Function
* @adr: ACPI address (used for SDCA register access)
* @type: Function topology type
* @name: human-readable string
*/
struct sdca_function_desc {
u64 adr;
u32 type;
const char *name;
};
/**
* sdca_device_data - structure containing all SDCA related information
* @sdca_interface_revision: value read from _DSD property, mainly to check
* for changes between silicon versions
* @num_functions: total number of supported SDCA functions. Invalid/unsupported
* functions will be skipped.
* @sdca_func: array of function descriptors
*/
struct sdca_device_data {
u32 interface_revision;
int num_functions;
struct sdca_function_desc sdca_func[SDCA_MAX_FUNCTION_COUNT];
};
enum sdca_quirk {
SDCA_QUIRKS_RT712_VB,
};
#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
void sdca_lookup_functions(struct sdw_slave *slave);
void sdca_lookup_interface_revision(struct sdw_slave *slave);
bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk);
#else
static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk)
{
return false;
}
#endif
#endif

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* The MIPI SDCA specification is available for public downloads at
* https://www.mipi.org/mipi-sdca-v1-0-download
*
* Copyright(c) 2024 Intel Corporation
*/
#ifndef __SDCA_FUNCTION_H__
#define __SDCA_FUNCTION_H__
/*
* SDCA Function Types from SDCA specification v1.0a Section 5.1.2
* all Function types not described are reserved
* Note that SIMPLE_AMP, SIMPLE_MIC and SIMPLE_JACK Function Types
* are NOT defined in SDCA 1.0a, but they were defined in earlier
* drafts and are planned for 1.1.
*/
enum sdca_function_type {
SDCA_FUNCTION_TYPE_SMART_AMP = 0x01, /* Amplifier with protection features */
SDCA_FUNCTION_TYPE_SIMPLE_AMP = 0x02, /* subset of SmartAmp */
SDCA_FUNCTION_TYPE_SMART_MIC = 0x03, /* Smart microphone with acoustic triggers */
SDCA_FUNCTION_TYPE_SIMPLE_MIC = 0x04, /* subset of SmartMic */
SDCA_FUNCTION_TYPE_SPEAKER_MIC = 0x05, /* Combination of SmartMic and SmartAmp */
SDCA_FUNCTION_TYPE_UAJ = 0x06, /* 3.5mm Universal Audio jack */
SDCA_FUNCTION_TYPE_RJ = 0x07, /* Retaskable jack */
SDCA_FUNCTION_TYPE_SIMPLE_JACK = 0x08, /* Subset of UAJ */
SDCA_FUNCTION_TYPE_HID = 0x0A, /* Human Interface Device, for e.g. buttons */
SDCA_FUNCTION_TYPE_IMP_DEF = 0x1F, /* Implementation-defined function */
};
/* Human-readable names used for kernel logs and Function device registration/bind */
#define SDCA_FUNCTION_TYPE_SMART_AMP_NAME "SmartAmp"
#define SDCA_FUNCTION_TYPE_SIMPLE_AMP_NAME "SimpleAmp"
#define SDCA_FUNCTION_TYPE_SMART_MIC_NAME "SmartMic"
#define SDCA_FUNCTION_TYPE_SIMPLE_MIC_NAME "SimpleMic"
#define SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME "SpeakerMic"
#define SDCA_FUNCTION_TYPE_UAJ_NAME "UAJ"
#define SDCA_FUNCTION_TYPE_RJ_NAME "RJ"
#define SDCA_FUNCTION_TYPE_SIMPLE_NAME "SimpleJack"
#define SDCA_FUNCTION_TYPE_HID_NAME "HID"
enum sdca_entity0_controls {
SDCA_CONTROL_ENTITY_0_COMMIT_GROUP_MASK = 0x01,
SDCA_CONTROL_ENTITY_0_INTSTAT_CLEAR = 0x02,
SDCA_CONTROL_ENTITY_0_INT_ENABLE = 0x03,
SDCA_CONTROL_ENTITY_0_FUNCTION_SDCA_VERSION = 0x04,
SDCA_CONTROL_ENTITY_0_FUNCTION_TOPOLOGY = 0x05,
SDCA_CONTROL_ENTITY_0_FUNCTION_MANUFACTURER_ID = 0x06,
SDCA_CONTROL_ENTITY_0_FUNCTION_ID = 0x07,
SDCA_CONTROL_ENTITY_0_FUNCTION_VERSION = 0x08
};
#endif

View File

@ -185,6 +185,10 @@ struct snd_soc_acpi_link_adr {
* ACPI ID alone is not sufficient, wrong or misleading
* @quirk_data: data used to uniquely identify a machine, usually a list of
* audio codecs whose presence if checked with ACPI
* @machine_check: pointer to quirk function. The functionality is similar to
* the use of @machine_quirk, except that the return value is a boolean: the intent
* is to skip a machine if the additional hardware/firmware verification invalidates
* the initial selection in the snd_soc_acpi_mach table.
* @pdata: intended for platform data or machine specific-ops. This structure
* is not constant since this field may be updated at run-time
* @sof_tplg_filename: Sound Open Firmware topology file name, if enabled
@ -203,6 +207,7 @@ struct snd_soc_acpi_mach {
const char *board;
struct snd_soc_acpi_mach * (*machine_quirk)(void *arg);
const void *quirk_data;
bool (*machine_check)(void *arg);
void *pdata;
struct snd_soc_acpi_mach_params mach_params;
const char *sof_tplg_filename;
@ -233,7 +238,6 @@ static inline bool snd_soc_acpi_sof_parent(struct device *dev)
bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev,
const struct snd_soc_acpi_link_adr *link,
struct sdw_extended_slave_id *ids,
int num_slaves);
struct sdw_peripherals *peripherals);
#endif

View File

@ -108,6 +108,7 @@ source "sound/soc/pxa/Kconfig"
source "sound/soc/qcom/Kconfig"
source "sound/soc/rockchip/Kconfig"
source "sound/soc/samsung/Kconfig"
source "sound/soc/sdca/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/sof/Kconfig"
source "sound/soc/spear/Kconfig"

View File

@ -61,6 +61,7 @@ obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += qcom/
obj-$(CONFIG_SND_SOC) += rockchip/
obj-$(CONFIG_SND_SOC) += samsung/
obj-$(CONFIG_SND_SOC) += sdca/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += sof/
obj-$(CONFIG_SND_SOC) += spear/

View File

@ -302,8 +302,7 @@ static struct snd_soc_acpi_mach *acp63_sdw_machine_select(struct device *dev)
link = mach->links;
for (i = 0; i < acp_data->info.count && link->num_adr; link++, i++) {
if (!snd_soc_acpi_sdw_link_slaves_found(dev, link,
acp_data->sdw->ids,
acp_data->sdw->num_slaves))
acp_data->sdw->peripherals))
break;
}
if (i == acp_data->info.count || !link->num_adr)

View File

@ -507,3 +507,4 @@ module_sdw_driver(rt712_sdca_sdw_driver);
MODULE_DESCRIPTION("ASoC RT712 SDCA SDW driver");
MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(SND_SOC_SDCA);

View File

@ -18,6 +18,7 @@
#include <linux/pm_runtime.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/sdca.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/slab.h>
#include <sound/soc-dapm.h>
@ -1652,6 +1653,17 @@ int rt712_sdca_init(struct device *dev, struct regmap *regmap,
if (ret < 0)
return ret;
/* only add the dmic component if a SMART_MIC function is exposed in ACPI */
if (sdca_device_quirk_match(slave, SDCA_QUIRKS_RT712_VB)) {
ret = devm_snd_soc_register_component(dev,
&soc_sdca_dev_rt712_dmic,
rt712_sdca_dmic_dai,
ARRAY_SIZE(rt712_sdca_dmic_dai));
if (ret < 0)
return ret;
rt712->dmic_function_found = true;
}
/* set autosuspend parameters */
pm_runtime_set_autosuspend_delay(dev, 3000);
pm_runtime_use_autosuspend(dev);
@ -1799,7 +1811,6 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712)
int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave)
{
struct rt712_sdca_priv *rt712 = dev_get_drvdata(dev);
int ret = 0;
unsigned int val;
struct sdw_slave_prop *prop = &slave->prop;
@ -1829,15 +1840,22 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave)
rt712->version_id = (val & 0x0f00) >> 8;
dev_dbg(&slave->dev, "%s hw_id=0x%x, version_id=0x%x\n", __func__, rt712->hw_id, rt712->version_id);
if (rt712->version_id == RT712_VA)
rt712_sdca_va_io_init(rt712);
else {
/* multilanes and DMIC are supported by rt712vb */
ret = devm_snd_soc_register_component(dev,
&soc_sdca_dev_rt712_dmic, rt712_sdca_dmic_dai, ARRAY_SIZE(rt712_sdca_dmic_dai));
if (ret < 0)
return ret;
if (rt712->version_id == RT712_VA) {
if (rt712->dmic_function_found) {
dev_err(&slave->dev, "%s RT712 VA detected but SMART_MIC function exposed in ACPI\n",
__func__);
goto suspend;
}
rt712_sdca_va_io_init(rt712);
} else {
if (!rt712->dmic_function_found) {
dev_err(&slave->dev, "%s RT712 VB detected but no SMART_MIC function exposed in ACPI\n",
__func__);
goto suspend;
}
/* multilanes and DMIC are supported by rt712vb */
prop->lane_control_support = true;
rt712_sdca_vb_io_init(rt712);
}
@ -1862,10 +1880,12 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave)
/* Mark Slave initialization complete */
rt712->hw_init = true;
dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
suspend:
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;
}

View File

@ -36,6 +36,7 @@ struct rt712_sdca_priv {
unsigned int scp_sdca_stat2;
unsigned int hw_id;
unsigned int version_id;
bool dmic_function_found;
bool fu0f_dapm_mute;
bool fu0f_mixer_l_mute;
bool fu0f_mixer_r_mute;

View File

@ -71,9 +71,14 @@ if SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL
config SND_SOC_ACPI_INTEL_MATCH
tristate
select SND_SOC_ACPI if ACPI
select SND_SOC_ACPI_INTEL_SDCA_QUIRKS
# this option controls the compilation of ACPI matching tables and
# helpers and is not meant to be selected by the user.
config SND_SOC_ACPI_INTEL_SDCA_QUIRKS
tristate
imply SND_SOC_SDCA
endif ## SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL
config SND_SOC_INTEL_KEEMBAY

View File

@ -16,4 +16,7 @@ snd-soc-acpi-intel-match-y := soc-acpi-intel-byt-match.o soc-acpi-intel-cht-matc
snd-soc-acpi-intel-match-y += soc-acpi-intel-ssp-common.o
snd-soc-acpi-intel-sdca-quirks-y += soc-acpi-intel-sdca-quirks.o
obj-$(CONFIG_SND_SOC_ACPI_INTEL_MATCH) += snd-soc-acpi-intel-match.o
obj-$(CONFIG_SND_SOC_ACPI_INTEL_SDCA_QUIRKS) += snd-soc-acpi-intel-sdca-quirks.o

View File

@ -6,9 +6,12 @@
*
*/
#include <linux/soundwire/sdw_intel.h>
#include <sound/sdca.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/soc-acpi-intel-ssp-common.h>
#include "soc-acpi-intel-sdca-quirks.h"
#include "soc-acpi-intel-sdw-mockup-match.h"
static const struct snd_soc_acpi_codecs mtl_rt5682_rt5682s_hp = {
@ -133,6 +136,27 @@ static const struct snd_soc_acpi_endpoint rt712_endpoints[] = {
},
};
static const struct snd_soc_acpi_endpoint rt712_vb_endpoints[] = {
{
.num = 0,
.aggregated = 0,
.group_position = 0,
.group_id = 0,
},
{
.num = 1,
.aggregated = 0,
.group_position = 0,
.group_id = 0,
},
{
.num = 2,
.aggregated = 0,
.group_position = 0,
.group_id = 0,
},
};
/*
* RT722 is a multi-function codec, three endpoints are created for
* its headset, amp and dmic functions.
@ -190,6 +214,15 @@ static const struct snd_soc_acpi_adr_device rt712_0_single_adr[] = {
}
};
static const struct snd_soc_acpi_adr_device rt712_vb_0_single_adr[] = {
{
.adr = 0x000030025D071201ull,
.num_endpoints = ARRAY_SIZE(rt712_vb_endpoints),
.endpoints = rt712_vb_endpoints,
.name_prefix = "rt712"
}
};
static const struct snd_soc_acpi_adr_device rt1712_3_single_adr[] = {
{
.adr = 0x000330025D171201ull,
@ -363,6 +396,15 @@ static const struct snd_soc_acpi_link_adr mtl_712_l0[] = {
{}
};
static const struct snd_soc_acpi_link_adr mtl_712_vb_l0[] = {
{
.mask = BIT(0),
.num_adr = ARRAY_SIZE(rt712_vb_0_single_adr),
.adr_d = rt712_vb_0_single_adr,
},
{}
};
static const struct snd_soc_acpi_endpoint cs42l43_endpoints[] = {
{ /* Jack Playback Endpoint */
.num = 0,
@ -774,6 +816,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = {
.drv_name = "sof_sdw",
.sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg",
},
{
.link_mask = BIT(0),
.links = mtl_712_vb_l0,
.drv_name = "sof_sdw",
.machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb,
.sof_tplg_filename = "sof-mtl-rt712-vb-l0.tplg",
},
{
.link_mask = BIT(0),
.links = mtl_712_l0,
@ -843,3 +892,5 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = {
{},
};
EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_sdw_machines);
MODULE_IMPORT_NS(SND_SOC_ACPI_INTEL_SDCA_QUIRKS);

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* soc-acpi-intel-sdca-quirks.c - tables and support for SDCA quirks
*
* Copyright (c) 2024, Intel Corporation.
*
*/
#include <linux/soundwire/sdw_intel.h>
#include <sound/sdca.h>
#include <sound/soc-acpi.h>
#include "soc-acpi-intel-sdca-quirks.h"
/*
* Pretend machine quirk. The argument type is not the traditional
* 'struct snd_soc_acpi_mach' pointer but instead the sdw_intel_ctx
* which contains the peripheral information required for the
* SoundWire/SDCA filter on the SMART_MIC setup and interface
* revision. When the return value is false, the entry in the
* 'snd_soc_acpi_mach' table needs to be skipped.
*/
bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg)
{
struct sdw_intel_ctx *ctx = arg;
int i;
if (!ctx)
return false;
for (i = 0; i < ctx->peripherals->num_peripherals; i++) {
if (sdca_device_quirk_match(ctx->peripherals->array[i],
SDCA_QUIRKS_RT712_VB))
return true;
}
return false;
}
EXPORT_SYMBOL_NS(snd_soc_acpi_intel_sdca_is_device_rt712_vb, SND_SOC_ACPI_INTEL_SDCA_QUIRKS);
MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(SND_SOC_SDCA);

View File

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* soc-acpi-intel-sdca-quirks.h - tables and support for SDCA quirks
*
* Copyright (c) 2024, Intel Corporation.
*
*/
#ifndef _SND_SOC_ACPI_INTEL_SDCA_QUIRKS
#define _SND_SOC_ACPI_INTEL_SDCA_QUIRKS
bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg);
#endif

11
sound/soc/sdca/Kconfig Normal file
View File

@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_SOC_SDCA
tristate "ASoC SDCA library"
depends on ACPI
help
This option enables support for the MIPI SoundWire Device
Class for Audio (SDCA).
config SND_SOC_SDCA_OPTIONAL
def_tristate SND_SOC_SDCA || !SND_SOC_SDCA

5
sound/soc/sdca/Makefile Normal file
View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-soc-sdca-objs := sdca_functions.o sdca_device.o
obj-$(CONFIG_SND_SOC_SDCA) += snd-soc-sdca.o

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2024 Intel Corporation
/*
* The MIPI SDCA specification is available for public downloads at
* https://www.mipi.org/mipi-sdca-v1-0-download
*/
#include <linux/acpi.h>
#include <linux/soundwire/sdw.h>
#include <sound/sdca.h>
#include <sound/sdca_function.h>
void sdca_lookup_interface_revision(struct sdw_slave *slave)
{
struct fwnode_handle *fwnode = slave->dev.fwnode;
/*
* if this property is not present, then the sdca_interface_revision will
* remain zero, which will be considered as 'not defined' or 'invalid'.
*/
fwnode_property_read_u32(fwnode, "mipi-sdw-sdca-interface-revision",
&slave->sdca_data.interface_revision);
}
EXPORT_SYMBOL_NS(sdca_lookup_interface_revision, SND_SOC_SDCA);
static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave)
{
struct sdw_slave_id *id = &slave->id;
int i;
/*
* The RT712_VA relies on the v06r04 draft, and the
* RT712_VB on a more recent v08r01 draft.
*/
if (slave->sdca_data.interface_revision < 0x0801)
return false;
if (id->mfg_id != 0x025d)
return false;
if (id->part_id != 0x712 &&
id->part_id != 0x713 &&
id->part_id != 0x716 &&
id->part_id != 0x717)
return false;
for (i = 0; i < slave->sdca_data.num_functions; i++) {
if (slave->sdca_data.sdca_func[i].type ==
SDCA_FUNCTION_TYPE_SMART_MIC)
return true;
}
return false;
}
bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk)
{
switch (quirk) {
case SDCA_QUIRKS_RT712_VB:
return sdca_device_quirk_rt712_vb(slave);
default:
break;
}
return false;
}
EXPORT_SYMBOL_NS(sdca_device_quirk_match, SND_SOC_SDCA);

View File

@ -0,0 +1,173 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2024 Intel Corporation
/*
* The MIPI SDCA specification is available for public downloads at
* https://www.mipi.org/mipi-sdca-v1-0-download
*/
#include <linux/acpi.h>
#include <linux/soundwire/sdw.h>
#include <sound/sdca.h>
#include <sound/sdca_function.h>
static int patch_sdca_function_type(struct device *dev,
u32 interface_revision,
u32 *function_type,
const char **function_name)
{
unsigned long function_type_patch = 0;
/*
* Unfortunately early SDCA specifications used different indices for Functions,
* for backwards compatibility we have to reorder the values found
*/
if (interface_revision >= 0x0801)
goto skip_early_draft_order;
switch (*function_type) {
case 1:
function_type_patch = SDCA_FUNCTION_TYPE_SMART_AMP;
break;
case 2:
function_type_patch = SDCA_FUNCTION_TYPE_SMART_MIC;
break;
case 3:
function_type_patch = SDCA_FUNCTION_TYPE_SPEAKER_MIC;
break;
case 4:
function_type_patch = SDCA_FUNCTION_TYPE_UAJ;
break;
case 5:
function_type_patch = SDCA_FUNCTION_TYPE_RJ;
break;
case 6:
function_type_patch = SDCA_FUNCTION_TYPE_HID;
break;
default:
dev_warn(dev, "%s: SDCA version %#x unsupported function type %d, skipped\n",
__func__, interface_revision, *function_type);
return -EINVAL;
}
skip_early_draft_order:
if (function_type_patch)
*function_type = function_type_patch;
/* now double-check the values */
switch (*function_type) {
case SDCA_FUNCTION_TYPE_SMART_AMP:
*function_name = SDCA_FUNCTION_TYPE_SMART_AMP_NAME;
break;
case SDCA_FUNCTION_TYPE_SMART_MIC:
*function_name = SDCA_FUNCTION_TYPE_SMART_MIC_NAME;
break;
case SDCA_FUNCTION_TYPE_UAJ:
*function_name = SDCA_FUNCTION_TYPE_UAJ_NAME;
break;
case SDCA_FUNCTION_TYPE_HID:
*function_name = SDCA_FUNCTION_TYPE_HID_NAME;
break;
case SDCA_FUNCTION_TYPE_SIMPLE_AMP:
case SDCA_FUNCTION_TYPE_SIMPLE_MIC:
case SDCA_FUNCTION_TYPE_SPEAKER_MIC:
case SDCA_FUNCTION_TYPE_RJ:
case SDCA_FUNCTION_TYPE_IMP_DEF:
dev_warn(dev, "%s: found unsupported SDCA function type %d, skipped\n",
__func__, *function_type);
return -EINVAL;
default:
dev_err(dev, "%s: found invalid SDCA function type %d, skipped\n",
__func__, *function_type);
return -EINVAL;
}
dev_info(dev, "%s: found SDCA function %s (type %d)\n",
__func__, *function_name, *function_type);
return 0;
}
static int find_sdca_function(struct acpi_device *adev, void *data)
{
struct fwnode_handle *function_node = acpi_fwnode_handle(adev);
struct sdca_device_data *sdca_data = data;
struct device *dev = &adev->dev;
struct fwnode_handle *control5; /* used to identify function type */
const char *function_name;
u32 function_type;
int func_index;
u64 addr;
int ret;
if (sdca_data->num_functions >= SDCA_MAX_FUNCTION_COUNT) {
dev_err(dev, "%s: maximum number of functions exceeded\n", __func__);
return -EINVAL;
}
/*
* The number of functions cannot exceed 8, we could use
* acpi_get_local_address() but the value is stored as u64 so
* we might as well avoid casts and intermediate levels
*/
ret = acpi_get_local_u64_address(adev->handle, &addr);
if (ret < 0)
return ret;
if (!addr) {
dev_err(dev, "%s: no addr\n", __func__);
return -ENODEV;
}
/*
* Extracting the topology type for an SDCA function is a
* convoluted process.
* The Function type is only visible as a result of a read
* from a control. In theory this would mean reading from the hardware,
* but the SDCA/DisCo specs defined the notion of "DC value" - a constant
* represented with a DSD subproperty.
* Drivers have to query the properties for the control
* SDCA_CONTROL_ENTITY_0_FUNCTION_TOPOLOGY (0x05)
*/
control5 = fwnode_get_named_child_node(function_node,
"mipi-sdca-control-0x5-subproperties");
if (!control5)
return -ENODEV;
ret = fwnode_property_read_u32(control5, "mipi-sdca-control-dc-value",
&function_type);
fwnode_handle_put(control5);
if (ret < 0) {
dev_err(dev, "%s: the function type can only be determined from ACPI information\n",
__func__);
return ret;
}
ret = patch_sdca_function_type(dev, sdca_data->interface_revision,
&function_type, &function_name);
if (ret < 0)
return ret;
/* store results */
func_index = sdca_data->num_functions;
sdca_data->sdca_func[func_index].adr = addr;
sdca_data->sdca_func[func_index].type = function_type;
sdca_data->sdca_func[func_index].name = function_name;
sdca_data->num_functions++;
return 0;
}
void sdca_lookup_functions(struct sdw_slave *slave)
{
struct device *dev = &slave->dev;
struct acpi_device *adev = to_acpi_device_node(dev->fwnode);
acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data);
}
EXPORT_SYMBOL_NS(sdca_lookup_functions, SND_SOC_SDCA);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SDCA library");

View File

@ -144,8 +144,15 @@ struct asoc_sdw_codec_info codec_info_list[] = {
.widgets = generic_spk_widgets,
.num_widgets = ARRAY_SIZE(generic_spk_widgets),
},
{
.direction = {false, true},
.dai_name = "rt712-sdca-aif3",
.dai_type = SOC_SDW_DAI_TYPE_MIC,
.dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
.rtd_init = asoc_sdw_rt_dmic_rtd_init,
},
},
.dai_num = 2,
.dai_num = 3,
},
{
.part_id = 0x1712,
@ -178,8 +185,15 @@ struct asoc_sdw_codec_info codec_info_list[] = {
.widgets = generic_jack_widgets,
.num_widgets = ARRAY_SIZE(generic_jack_widgets),
},
{
.direction = {false, true},
.dai_name = "rt712-sdca-aif3",
.dai_type = SOC_SDW_DAI_TYPE_MIC,
.dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
.rtd_init = asoc_sdw_rt_dmic_rtd_init,
},
},
.dai_num = 1,
.dai_num = 2,
},
{
.part_id = 0x1713,

View File

@ -131,8 +131,7 @@ EXPORT_SYMBOL_GPL(snd_soc_acpi_codec_list);
/* Check if all Slaves defined on the link can be found */
bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev,
const struct snd_soc_acpi_link_adr *link,
struct sdw_extended_slave_id *ids,
int num_slaves)
struct sdw_peripherals *peripherals)
{
unsigned int part_id, link_id, unique_id, mfg_id, version;
int i, j, k;
@ -146,22 +145,25 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev,
link_id = SDW_DISCO_LINK_ID(adr);
version = SDW_VERSION(adr);
for (j = 0; j < num_slaves; j++) {
for (j = 0; j < peripherals->num_peripherals; j++) {
struct sdw_slave *peripheral = peripherals->array[j];
/* find out how many identical parts were reported on that link */
if (ids[j].link_id == link_id &&
ids[j].id.part_id == part_id &&
ids[j].id.mfg_id == mfg_id &&
ids[j].id.sdw_version == version)
if (peripheral->bus->link_id == link_id &&
peripheral->id.part_id == part_id &&
peripheral->id.mfg_id == mfg_id &&
peripheral->id.sdw_version == version)
reported_part_count++;
}
for (j = 0; j < num_slaves; j++) {
for (j = 0; j < peripherals->num_peripherals; j++) {
struct sdw_slave *peripheral = peripherals->array[j];
int expected_part_count = 0;
if (ids[j].link_id != link_id ||
ids[j].id.part_id != part_id ||
ids[j].id.mfg_id != mfg_id ||
ids[j].id.sdw_version != version)
if (peripheral->bus->link_id != link_id ||
peripheral->id.part_id != part_id ||
peripheral->id.mfg_id != mfg_id ||
peripheral->id.sdw_version != version)
continue;
/* find out how many identical parts are expected */
@ -180,7 +182,7 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev,
*/
unique_id = SDW_UNIQUE_ID(adr);
if (reported_part_count == 1 ||
ids[j].id.unique_id == unique_id) {
peripheral->id.unique_id == unique_id) {
dev_dbg(dev, "found part_id %#x at link %d\n", part_id, link_id);
break;
}
@ -189,7 +191,7 @@ bool snd_soc_acpi_sdw_link_slaves_found(struct device *dev,
part_id, reported_part_count, expected_part_count, link_id);
}
}
if (j == num_slaves) {
if (j == peripherals->num_peripherals) {
dev_dbg(dev, "Slave part_id %#x not found\n", part_id);
return false;
}

View File

@ -145,8 +145,7 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev *
link = mach->links;
for (i = 0; i < acp_data->info.count && link->num_adr; link++, i++) {
if (!snd_soc_acpi_sdw_link_slaves_found(sdev->dev, link,
acp_data->sdw->ids,
acp_data->sdw->num_slaves))
acp_data->sdw->peripherals))
break;
}
if (i == acp_data->info.count || !link->num_adr)

View File

@ -1064,7 +1064,7 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct snd_soc_acpi_link_adr *link;
struct sdw_extended_slave_id *ids;
struct sdw_peripherals *peripherals;
struct snd_soc_acpi_mach *mach;
struct sof_intel_hda_dev *hdev;
u32 link_mask;
@ -1083,7 +1083,7 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev
return NULL;
}
if (!hdev->sdw->num_slaves) {
if (!hdev->sdw->peripherals || !hdev->sdw->peripherals->num_peripherals) {
dev_warn(sdev->dev, "No SoundWire peripheral detected in ACPI tables\n");
return NULL;
}
@ -1119,13 +1119,13 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev
* are not found on this link.
*/
if (!snd_soc_acpi_sdw_link_slaves_found(sdev->dev, link,
hdev->sdw->ids,
hdev->sdw->num_slaves))
hdev->sdw->peripherals))
break;
}
/* Found if all Slaves are checked */
if (i == hdev->info.count || !link->num_adr)
break;
if (!mach->machine_check || mach->machine_check(hdev->sdw))
break;
}
if (mach && mach->link_mask) {
mach->mach_params.links = mach->links;
@ -1136,10 +1136,13 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev
}
dev_info(sdev->dev, "No SoundWire machine driver found for the ACPI-reported configuration:\n");
ids = hdev->sdw->ids;
for (i = 0; i < hdev->sdw->num_slaves; i++)
peripherals = hdev->sdw->peripherals;
for (i = 0; i < peripherals->num_peripherals; i++)
dev_info(sdev->dev, "link %d mfg_id 0x%04x part_id 0x%04x version %#x\n",
ids[i].link_id, ids[i].id.mfg_id, ids[i].id.part_id, ids[i].id.sdw_version);
peripherals->array[i]->bus->link_id,
peripherals->array[i]->id.mfg_id,
peripherals->array[i]->id.part_id,
peripherals->array[i]->id.sdw_version);
return NULL;
}