From: James Calligeros <jcalligeros99@gmail.com>
To: "Martin Povišer" <povik+lin@cutebit.org>,
"Liam Girdwood" <lgirdwood@gmail.com>,
"Mark Brown" <broonie@kernel.org>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Sven Peter" <sven@kernel.org>, "Janne Grunau" <j@jannau.net>,
"Neal Gompa" <neal@gompa.dev>,
"David Rhodes" <david.rhodes@cirrus.com>,
"Richard Fitzgerald" <rf@opensource.cirrus.com>,
"Jaroslav Kysela" <perex@perex.cz>,
"Takashi Iwai" <tiwai@suse.com>, "Ulf Hansson" <ulfh@kernel.org>,
"Amit Kucheria" <amit.kucheria@linaro.org>,
"Rafael J. Wysocki" <rafael.j.wysocki@intel.com>,
"Lars-Peter Clausen" <lars@metafoo.de>,
"Vinod Koul" <vkoul@kernel.org>,
"Matthias Brugger" <matthias.bgg@gmail.com>,
"AngeloGioacchino Del Regno"
<angelogioacchino.delregno@collabora.com>,
"Shenghao Ding" <shenghao-ding@ti.com>,
"Kevin Lu" <kevin-lu@ti.com>, "Baojun Xu" <baojun.xu@ti.com>,
"Sen Wang" <sen@ti.com>,
"James Calligeros" <jcalligeros99@gmail.com>,
"James Schulman" <james.schulman@cirrus.com>
Cc: asahi@lists.linux.dev, linux-sound@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
patches@opensource.cirrus.com, Takashi Iwai <tiwai@suse.de>,
linux-mediatek@lists.infradead.org,
Hector Martin <marcan@marcan.st>,
Sasha Finkelstein <k@chaosmail.tech>
Subject: [PATCH 16/28] ASoC: apple: Add macaudio machine driver
Date: Sun, 20 Sep 2026 14:53:55 +1000 [thread overview]
Message-ID: <20260920-macaudio-v1-16-741cc20a74e5@gmail.com> (raw)
In-Reply-To: <20260920-macaudio-v1-0-741cc20a74e5@gmail.com>
From: Martin Povišer <povik+lin@cutebit.org>
Apple Silicon Macs have a complex audio subsystem consisting
of an I2S peripheral (MCA) and multiple codecs of various
models and capabilities. Some machines have a basic mono
speaker with hardware downmix, while others have a very
intricate stereo system consisting of multiple codecs and
drivers per L/R channel. Some machines report voice coil
voltage and current information back to the SoC, and others
do not. All machines have a headset jack.
Add an ASoC machine driver for this platform.
To express all of this sanely, we initialise the correct
combinations of codecs/I2S ports as DAI backends and link
them to a synthetic frontend. Each frontend is linked to
a PCM, one for the headset codec and one for the speaker
system on the target machine.
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Co-developed-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Co-developed-by: James Calligeros <jcalligeros99@gmail.com>
Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
sound/soc/apple/Kconfig | 19 +
sound/soc/apple/Makefile | 4 +
sound/soc/apple/macaudio.c | 1729 +++++++++++++++++++++++++
3 files changed, 1752 insertions(+)
diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index f1d071a99fbf..8cca86dfd8c0 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -9,4 +9,23 @@ config SND_SOC_APPLE_MCA
This option enables an ASoC platform driver for MCA peripherals found
on Apple Silicon SoCs.
+config SND_SOC_APPLE_MACAUDIO
+ tristate "Sound support for Apple Silicon Macs"
+ depends on ARCH_APPLE || COMPILE_TEST
+ select SND_SOC_APPLE_MCA
+ select SND_SIMPLE_CARD_UTILS
+ select APPLE_ADMAC if DMADEVICES
+ select COMMON_CLK_APPLE_NCO if COMMON_CLK
+ select SND_SOC_TAS2764 if I2C
+ select SND_SOC_TAS2770 if I2C
+ select SND_SOC_CS42L83 if I2C
+ select SND_SOC_CS42L84 if I2C
+ help
+ This option enables an ASoC machine-level driver for Apple Silicon Macs
+ and it also enables the required SoC and codec drivers for overall
+ sound support on these machines. Say Y here if you have an Apple Silicon
+ Mac.
+
+ This driver can also be compiled as a module. If so, it will be called
+ snd-soc-macaudio.
endmenu
diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile
index 252ba5fc18cb..f1621c03b8ee 100644
--- a/sound/soc/apple/Makefile
+++ b/sound/soc/apple/Makefile
@@ -2,3 +2,7 @@
snd-soc-apple-mca-y := mca.o
obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o
+
+snd-soc-macaudio-objs := macaudio.o
+
+obj-$(CONFIG_SND_SOC_APPLE_MACAUDIO) += snd-soc-macaudio.o
diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
new file mode 100644
index 000000000000..5528a64b2b44
--- /dev/null
+++ b/sound/soc/apple/macaudio.c
@@ -0,0 +1,1729 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ASoC machine driver for Apple Silicon Macs
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/qcom/{sc7180.c|common.c}
+ * Copyright (c) 2018, Linaro Limited.
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *
+ * The platform driver has independent frontend and backend DAIs with the
+ * option of routing backends to any of the frontends. The platform
+ * driver configures the routing based on DPCM couplings in ASoC runtime
+ * structures, which in turn are determined from DAPM paths by ASoC. But the
+ * platform driver doesn't supply relevant DAPM paths and leaves that up for
+ * the machine driver to fill in. The filled-in virtual topology can be
+ * anything as long as any backend isn't connected to more than one frontend
+ * at any given time. (The limitation is due to the unsupported case of
+ * reparenting of live BEs.)
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/simple_card_utils.h>
+#include <sound/soc.h>
+#include <sound/soc-jack.h>
+#include <uapi/linux/input-event-codes.h>
+
+#define DRIVER_NAME "snd-soc-macaudio"
+
+/*
+ * CPU side is bit and frame clock provider
+ * I2S has both clocks inverted
+ */
+#define MACAUDIO_DAI_FMT (SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_CBC_CFC | \
+ SND_SOC_DAIFMT_GATED | \
+ SND_SOC_DAIFMT_IB_IF)
+#define MACAUDIO_JACK_MASK (SND_JACK_HEADSET | SND_JACK_HEADPHONE)
+#define MACAUDIO_SLOTWIDTH 32
+/*
+ * Maximum BCLK frequency
+ *
+ * Codec maximums:
+ * CS42L42 26.0 MHz
+ * TAS2770 27.1 MHz
+ * TAS2764 24.576 MHz
+ */
+#define MACAUDIO_MAX_BCLK_FREQ 24576000
+
+#define SPEAKER_MAGIC_VALUE ((s32)0xdec1be15)
+/* milliseconds */
+#define SPEAKER_LOCK_TIMEOUT 250
+
+enum macaudio_amp_type {
+ AMP_NONE,
+ AMP_TAS5770,
+ AMP_SN012776,
+ AMP_SSM3515,
+};
+
+enum macaudio_spkr_config {
+ SPKR_NONE, /* No speakers */
+ SPKR_1W, /* 1 woofer / ch */
+ SPKR_2W, /* 2 woofers / ch */
+ SPKR_1W1T, /* 1 woofer + 1 tweeter / ch */
+ SPKR_2W1T, /* 2 woofers + 1 tweeter / ch */
+};
+
+struct macaudio_platform_cfg {
+ bool enable_speakers;
+ enum macaudio_amp_type amp;
+ enum macaudio_spkr_config speakers;
+ bool stereo;
+ int amp_gain;
+ int safe_vol;
+};
+
+static const char * const volume_control_names[] = {
+ [AMP_TAS5770] = "* Speaker Playback Volume",
+ [AMP_SN012776] = "* Speaker Volume",
+ [AMP_SSM3515] = "* DAC Playback Volume",
+};
+
+#define SN012776_0DB 201
+#define SN012776_DB(x) (SN012776_0DB + 2 * (x))
+/* Same as SN012776 */
+#define TAS5770_0DB SN012776_0DB
+#define TAS5770_DB(x) SN012776_DB(x)
+
+#define SSM3515_0DB (255 - 64) /* +24dB max, steps of 3/8 dB */
+#define SSM3515_DB(x) (SSM3515_0DB + (8 * (x) / 3))
+
+struct ma_codec_idle {
+ int idle_mode;
+ u32 idle_mask;
+};
+
+struct macaudio_snd_data {
+ struct snd_soc_card card;
+ struct snd_soc_jack jack;
+ int jack_plugin_state;
+
+ const struct macaudio_platform_cfg *cfg;
+ bool has_speakers;
+ bool has_sense;
+ bool has_safety;
+ unsigned int max_channels;
+
+ struct macaudio_link_props {
+ /* frontend props */
+ unsigned int bclk_ratio;
+ bool is_sense;
+
+ /* backend props */
+ bool is_speakers;
+ bool is_headphones;
+ unsigned int tdm_mask;
+ struct ma_codec_idle *codecs;
+ } *link_props;
+
+ int speaker_sample_rate;
+ struct snd_kcontrol *speaker_sample_rate_kctl;
+
+ struct mutex volume_lock_mutex;
+ bool speaker_volume_unlocked;
+ bool speaker_volume_was_locked;
+ struct snd_kcontrol *speaker_lock_kctl;
+ struct snd_ctl_file *speaker_lock_owner;
+ u64 bes_active;
+ bool speaker_lock_timeout_enabled;
+ ktime_t speaker_lock_timeout;
+ ktime_t speaker_lock_remain;
+ struct delayed_work lock_timeout_work;
+ struct work_struct lock_update_work;
+
+};
+
+static int please_blow_up_my_speakers;
+module_param(please_blow_up_my_speakers, int, 0644);
+MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations");
+
+SND_SOC_DAILINK_DEFS(primary,
+ DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU
+ DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+ DAILINK_COMP_ARRAY(COMP_EMPTY())); // platform (filled at runtime)
+
+SND_SOC_DAILINK_DEFS(secondary,
+ DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-1")), // CPU
+ DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(sense,
+ DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-2")), // CPU
+ DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link macaudio_fe_links[] = {
+ {
+ .name = "Primary",
+ .stream_name = "Primary",
+ .dynamic = 1,
+ .dpcm_merged_rate = 1,
+ .dpcm_merged_chan = 1,
+ .dpcm_merged_format = 1,
+ .dai_fmt = MACAUDIO_DAI_FMT,
+ SND_SOC_DAILINK_REG(primary),
+ },
+ {
+ .name = "Secondary",
+ .stream_name = "Secondary",
+ .dynamic = 1,
+ .dpcm_merged_rate = 1,
+ .dpcm_merged_chan = 1,
+ .dpcm_merged_format = 1,
+ .dai_fmt = MACAUDIO_DAI_FMT,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(secondary),
+ },
+ {
+ .name = "Speaker Sense",
+ .stream_name = "Speaker Sense",
+ .capture_only = 1,
+ .dynamic = 1,
+ .dai_fmt = (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBP_CFP |
+ SND_SOC_DAIFMT_GATED |
+ SND_SOC_DAIFMT_IB_IF),
+ SND_SOC_DAILINK_REG(sense),
+ },
+};
+
+static struct macaudio_link_props macaudio_fe_link_props[] = {
+ {
+ /*
+ * Primary FE
+ *
+ * The bclk ratio at 64 for the primary frontend is important
+ * to ensure that the headphones codec's idea of left and right
+ * in a stereo stream over I2S fits in nicely with everyone else's.
+ * (This is until the headphones codec's driver supports
+ * set_tdm_slot.)
+ *
+ * The low bclk ratio precludes transmitting more than two
+ * channels over I2S, but that's okay since there is the secondary
+ * FE for speaker arrays anyway.
+ */
+ .bclk_ratio = 64,
+ },
+ {
+ /*
+ * Secondary FE
+ *
+ * Here we want frames plenty long to be able to drive all
+ * those fancy speaker arrays.
+ */
+ .bclk_ratio = 256,
+ },
+ {
+ .is_sense = 1,
+ }
+};
+
+static void macaudio_vlimit_unlock(struct macaudio_snd_data *ma, bool unlock)
+{
+ int ret, max;
+ const char *name = volume_control_names[ma->cfg->amp];
+
+ if (!name) {
+ WARN_ON_ONCE(1);
+ return;
+ }
+
+ switch (ma->cfg->amp) {
+ case AMP_NONE:
+ WARN_ON_ONCE(1);
+ return;
+ case AMP_TAS5770:
+ if (unlock)
+ max = TAS5770_0DB;
+ else
+ max = 1; //TAS5770_DB(ma->cfg->safe_vol);
+ break;
+ case AMP_SN012776:
+ if (unlock)
+ max = SN012776_0DB;
+ else
+ max = 1; //SN012776_DB(ma->cfg->safe_vol);
+ break;
+ case AMP_SSM3515:
+ if (unlock)
+ max = SSM3515_0DB;
+ else
+ max = SSM3515_DB(ma->cfg->safe_vol);
+ break;
+ }
+
+ ret = snd_soc_limit_volume(&ma->card, name, max);
+ if (ret < 0)
+ dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n",
+ unlock ? "un" : "", name, ret);
+}
+
+static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
+{
+ int i;
+ bool unlock = true;
+ struct snd_kcontrol *kctl;
+ const char *reason;
+
+ /* Do nothing if there is no safety configured */
+ if (!ma->has_safety)
+ return;
+
+ /* Check that someone is holding the main lock */
+ if (!ma->speaker_lock_owner) {
+ reason = "Main control not locked";
+ unlock = false;
+ }
+
+ /* Check that the control has been pinged within the timeout */
+ if (ma->speaker_lock_remain <= 0) {
+ reason = "Lock timeout";
+ unlock = false;
+ }
+
+ /* Check that *every* limited control is locked by the same owner */
+ list_for_each_entry(kctl, &ma->card.snd_card->controls, list) {
+ if (!snd_soc_control_matches(kctl, volume_control_names[ma->cfg->amp]))
+ continue;
+
+ for (i = 0; i < kctl->count; i++) {
+ if (kctl->vd[i].owner != ma->speaker_lock_owner) {
+ reason = "Not all child controls locked by the same process";
+ unlock = false;
+ }
+ }
+ }
+
+
+ if (unlock != ma->speaker_volume_unlocked) {
+ if (unlock) {
+ dev_info(ma->card.dev, "Speaker volumes unlocked\n");
+ } else {
+ dev_info(ma->card.dev, "Speaker volumes locked: %s\n", reason);
+ ma->speaker_volume_was_locked = true;
+ }
+
+ macaudio_vlimit_unlock(ma, unlock);
+ ma->speaker_volume_unlocked = unlock;
+ snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &ma->speaker_lock_kctl->id);
+ }
+}
+
+static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma)
+{
+ mutex_lock(&ma->volume_lock_mutex);
+
+ if (ma->speaker_lock_timeout_enabled) {
+ mutex_unlock(&ma->volume_lock_mutex);
+ return;
+ }
+
+ if (ma->speaker_lock_remain > 0) {
+ ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain);
+ schedule_delayed_work(&ma->lock_timeout_work,
+ usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain)));
+ dev_dbg(ma->card.dev, "Enabling volume limit timeout: %ld us left\n",
+ (long)ktime_to_us(ma->speaker_lock_remain));
+ }
+
+ macaudio_vlimit_update(ma);
+
+ ma->speaker_lock_timeout_enabled = true;
+ mutex_unlock(&ma->volume_lock_mutex);
+}
+
+static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma)
+{
+ ktime_t now;
+
+ mutex_lock(&ma->volume_lock_mutex);
+
+ if (!ma->speaker_lock_timeout_enabled) {
+ mutex_unlock(&ma->volume_lock_mutex);
+ return;
+ }
+
+ now = ktime_get();
+
+ cancel_delayed_work(&ma->lock_timeout_work);
+
+ if (ktime_after(now, ma->speaker_lock_timeout))
+ ma->speaker_lock_remain = 0;
+ else if (ma->speaker_lock_remain > 0)
+ ma->speaker_lock_remain = ktime_sub(ma->speaker_lock_timeout, now);
+
+ dev_dbg(ma->card.dev, "Disabling volume limit timeout: %ld us left\n",
+ (long)ktime_to_us(ma->speaker_lock_remain));
+
+ macaudio_vlimit_update(ma);
+
+ ma->speaker_lock_timeout_enabled = false;
+
+ mutex_unlock(&ma->volume_lock_mutex);
+}
+
+static void macaudio_vlimit_timeout_work(struct work_struct *wrk)
+{
+ struct macaudio_snd_data *ma = container_of(to_delayed_work(wrk),
+ struct macaudio_snd_data, lock_timeout_work);
+
+ mutex_lock(&ma->volume_lock_mutex);
+
+ ma->speaker_lock_remain = 0;
+ macaudio_vlimit_update(ma);
+
+ mutex_unlock(&ma->volume_lock_mutex);
+}
+
+static void macaudio_vlimit_update_work(struct work_struct *wrk)
+{
+ struct macaudio_snd_data *ma = container_of(wrk,
+ struct macaudio_snd_data, lock_update_work);
+
+ if (ma->bes_active)
+ macaudio_vlimit_enable_timeout(ma);
+ else
+ macaudio_vlimit_disable_timeout(ma);
+}
+
+static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target,
+ struct snd_soc_dai_link *source)
+{
+ memcpy(target, source, sizeof(struct snd_soc_dai_link));
+
+ target->cpus = devm_kmemdup(dev, target->cpus,
+ sizeof(*target->cpus) * target->num_cpus,
+ GFP_KERNEL);
+ target->codecs = devm_kmemdup(dev, target->codecs,
+ sizeof(*target->codecs) * target->num_codecs,
+ GFP_KERNEL);
+ target->platforms = devm_kmemdup(dev, target->platforms,
+ sizeof(*target->platforms) * target->num_platforms,
+ GFP_KERNEL);
+
+ if (!target->cpus || !target->codecs || !target->platforms)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int macaudio_parse_of_component(struct device_node *node, int index,
+ struct snd_soc_dai_link_component *comp)
+{
+ struct of_phandle_args args;
+ int ret;
+
+ ret = of_parse_phandle_with_args(node, "sound-dai", "#sound-dai-cells",
+ index, &args);
+ if (ret)
+ return ret;
+ comp->of_node = args.np;
+ return snd_soc_get_dai_name(&args, &comp->dai_name);
+}
+
+/*
+ * Parse one DPCM backend from the devicetree. This means taking one
+ * of the CPU DAIs and combining it with one or more CODEC DAIs.
+ */
+static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma,
+ struct snd_soc_dai_link *link,
+ int be_index, int ncodecs_per_be,
+ struct device_node *cpu,
+ struct device_node *codec)
+{
+ struct snd_soc_dai_link_component *comp;
+ struct device *dev = ma->card.dev;
+ int codec_base = be_index * ncodecs_per_be;
+ int ret, i;
+
+ link->no_pcm = 1;
+
+ link->dai_fmt = MACAUDIO_DAI_FMT;
+
+ link->num_codecs = ncodecs_per_be;
+ link->codecs = devm_kcalloc(dev, ncodecs_per_be,
+ sizeof(*comp), GFP_KERNEL);
+ link->num_cpus = 1;
+ link->cpus = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
+
+ if (!link->codecs || !link->cpus)
+ return -ENOMEM;
+
+ link->num_platforms = 0;
+
+ for_each_link_codecs(link, i, comp) {
+ ret = macaudio_parse_of_component(codec, codec_base + i, comp);
+ if (ret)
+ return dev_err_probe(ma->card.dev, ret,
+ "parsing CODEC DAI of link '%s' at %pOF\n",
+ link->name, codec);
+ }
+
+ ret = macaudio_parse_of_component(cpu, be_index, link->cpus);
+ if (ret)
+ return dev_err_probe(ma->card.dev, ret, "parsing CPU DAI of link '%s' at %pOF\n",
+ link->name, codec);
+
+ link->name = link->cpus[0].dai_name;
+
+ return 0;
+}
+
+static int macaudio_get_codec_idle_props(struct device_node *np,
+ struct ma_codec_idle *idle, int i,
+ int be_index, int ncodecs_per_cpu)
+{
+ char propname[32];
+ int ret, sys_codec;
+
+ /*
+ * Depending on the BE index on the DAI, we need to offset our i
+ */
+ sys_codec = i + (be_index * ncodecs_per_cpu);
+
+ snprintf(propname, 32, "dai-tdm-idle-mode-%d", sys_codec);
+ ret = of_property_match_string(np, propname, "zero");
+ if (!ret) {
+ idle->idle_mode = SND_SOC_DAI_TDM_IDLE_ZERO;
+ } else {
+ ret = of_property_match_string(np, propname, "pulldown");
+ if (!ret)
+ idle->idle_mode = SND_SOC_DAI_TDM_IDLE_PULLDOWN;
+ else
+ return ret;
+ }
+
+ snprintf(propname, 32, "dai-tdm-slot-tx-idle-mask-%d", sys_codec);
+ of_property_read_u32(np, propname, &idle->idle_mask);
+
+ return 0;
+}
+
+static int macaudio_parse_of(struct macaudio_snd_data *ma)
+{
+ struct device_node *codec = NULL;
+ struct device_node *cpu = NULL;
+ struct device_node *np = NULL;
+ struct device_node *platform = NULL;
+ struct snd_soc_dai_link *link = NULL;
+ struct snd_soc_card *card = &ma->card;
+ struct device *dev = card->dev;
+ struct macaudio_link_props *link_props;
+ int ret, num_links, i;
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret) {
+ dev_err_probe(dev, ret, "parsing card name\n");
+ return ret;
+ }
+ /*
+ * Set long_name to prevent snd_soc_set_dmi_name() from setting one from
+ * make believe data u-boot provides in its SMBIOS emulation.
+ */
+ card->long_name = card->name;
+
+ /* Populate links, start with the fixed number of FE links */
+ num_links = ARRAY_SIZE(macaudio_fe_links);
+
+ /* Now add together the (dynamic) number of BE links */
+ for_each_available_child_of_node(dev->of_node, np) {
+ int num_cpus;
+
+ cpu = of_get_child_by_name(np, "cpu");
+ if (!cpu) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "missing CPU DAI node at %pOF\n", np);
+ goto err_free;
+ }
+
+ num_cpus = of_count_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells");
+
+ if (num_cpus <= 0) {
+ ret = dev_err_probe(card->dev, -EINVAL,
+ "missing sound-dai property at %pOF\n", cpu);
+ goto err_free;
+ }
+ of_node_put(cpu);
+ cpu = NULL;
+
+ /* Each CPU specified counts as one BE link */
+ num_links += num_cpus;
+ }
+
+ /* Allocate the DAI link array */
+ card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+ ma->link_props = devm_kcalloc(dev, num_links, sizeof(*ma->link_props), GFP_KERNEL);
+ if (!card->dai_link || !ma->link_props)
+ return -ENOMEM;
+
+ link = card->dai_link;
+ link_props = ma->link_props;
+
+ for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) {
+ ret = macaudio_copy_link(dev, link, &macaudio_fe_links[i]);
+ if (ret)
+ goto err_free;
+
+ memcpy(link_props, &macaudio_fe_link_props[i], sizeof(struct macaudio_link_props));
+ link++; link_props++;
+ }
+
+ for (i = 0; i < num_links; i++)
+ card->dai_link[i].id = i;
+
+ /* We might disable the speakers, so count again */
+ num_links = ARRAY_SIZE(macaudio_fe_links);
+
+ /* Fill in the BEs */
+ for_each_available_child_of_node(dev->of_node, np) {
+ const char *link_name;
+ bool speakers;
+ int be_index, num_codecs, num_bes, ncodecs_per_cpu, nchannels;
+ unsigned int left_mask, right_mask;
+
+ ret = of_property_read_string(np, "link-name", &link_name);
+ if (ret) {
+ dev_err_probe(card->dev, ret, "missing link name\n");
+ goto err_free;
+ }
+
+ dev_dbg(ma->card.dev, "parsing link '%s'\n", link_name);
+
+ speakers = !strcmp(link_name, "Speaker")
+ || !strcmp(link_name, "Speakers");
+ if (speakers) {
+ if (!ma->cfg->enable_speakers && !please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, disabling speakers\n");
+ continue;
+ }
+ ma->has_speakers = 1;
+ if (ma->cfg->amp != AMP_SSM3515 && ma->cfg->safe_vol != 0)
+ ma->has_sense = 1;
+ }
+
+ cpu = of_get_child_by_name(np, "cpu");
+ codec = of_get_child_by_name(np, "codec");
+
+ if (!codec || !cpu) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "missing DAI specifications for '%s'\n", link_name);
+ goto err_free;
+ }
+
+ num_bes = of_count_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells");
+ if (num_bes <= 0) {
+ ret = dev_err_probe(card->dev, -EINVAL,
+ "missing sound-dai property at %pOF\n", cpu);
+ goto err_free;
+ }
+
+ num_codecs = of_count_phandle_with_args(codec, "sound-dai",
+ "#sound-dai-cells");
+ if (num_codecs <= 0) {
+ ret = dev_err_probe(card->dev, -EINVAL,
+ "missing sound-dai property at %pOF\n", codec);
+ goto err_free;
+ }
+
+ dev_dbg(ma->card.dev, "link '%s': %d CPUs %d CODECs\n",
+ link_name, num_bes, num_codecs);
+
+ if (num_codecs % num_bes != 0) {
+ ret = dev_err_probe(card->dev, -EINVAL,
+ "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n",
+ num_codecs, num_bes, np);
+ goto err_free;
+ }
+
+ /*
+ * Now parse the cpu/codec lists into a number of DPCM backend links.
+ * In each link there will be one DAI from the cpu list paired with
+ * an evenly distributed number of DAIs from the codec list. (As is
+ * the binding semantics.)
+ */
+ ncodecs_per_cpu = num_codecs / num_bes;
+ nchannels = num_codecs * (speakers ? 1 : 2);
+
+ /* Save the max number of channels on the platform */
+ if (nchannels > ma->max_channels)
+ ma->max_channels = nchannels;
+
+ /*
+ * If there is a single speaker, assign two channels to it, because
+ * it can do downmix.
+ */
+ if (nchannels < 2)
+ nchannels = 2;
+
+ left_mask = 0;
+ for (i = 0; i < nchannels; i += 2)
+ left_mask = left_mask << 2 | 1;
+ right_mask = left_mask << 1;
+
+ for (be_index = 0; be_index < num_bes; be_index++) {
+ /*
+ * Set initial link name to be overwritten by a BE-specific
+ * name later so that we can use at least use the provisional
+ * name in error messages.
+ */
+ link->name = link_name;
+
+ ret = macaudio_parse_of_be_dai_link(ma, link, be_index,
+ ncodecs_per_cpu, cpu, codec);
+ if (ret)
+ goto err_free;
+
+ link_props->is_speakers = speakers;
+ link_props->is_headphones = !speakers;
+
+ link_props->codecs = devm_kcalloc(dev, ncodecs_per_cpu,
+ sizeof(struct ma_codec_idle), GFP_KERNEL);
+ if (!link_props->codecs)
+ return -ENOMEM;
+ for (i = 0; i < ncodecs_per_cpu; i++) {
+ ret = macaudio_get_codec_idle_props(np, &link_props->codecs[i],
+ i, be_index, ncodecs_per_cpu);
+
+ /* Not every codec's bus keeper will be configured */
+ if (ret)
+ dev_dbg(card->dev, "Codec %d has no idle props\n", i);
+ }
+
+ if (num_bes == 2)
+ /* This sound peripheral is split between left and right BE */
+ link_props->tdm_mask = be_index ? right_mask : left_mask;
+ else
+ /* One BE covers all of the peripheral */
+ link_props->tdm_mask = left_mask | right_mask;
+
+ /* Steal platform OF reference for use in FE links later */
+ platform = link->cpus->of_node;
+
+ link++; link_props++;
+ }
+
+ of_node_put(codec);
+ of_node_put(cpu);
+ cpu = codec = NULL;
+
+ num_links += num_bes;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++)
+ card->dai_link[i].platforms->of_node = platform;
+
+ /* Skip the speaker sense PCM link if this amp has no sense (or no speakers) */
+ if (!ma->has_sense) {
+ for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) {
+ if (ma->link_props[i].is_sense) {
+ memmove(&card->dai_link[i], &card->dai_link[i + 1],
+ (num_links - i - 1) * sizeof(struct snd_soc_dai_link));
+ num_links--;
+ break;
+ }
+ }
+ }
+
+ card->num_links = num_links;
+
+ return 0;
+
+err_free:
+ of_node_put(codec);
+ of_node_put(cpu);
+ of_node_put(np);
+
+ if (!card->dai_link)
+ return ret;
+
+ for (i = 0; i < num_links; i++) {
+ /*
+ * TODO: If we don't go through this path are the references
+ * freed inside ASoC?
+ */
+ snd_soc_of_put_dai_link_codecs(&card->dai_link[i]);
+ snd_soc_of_put_dai_link_cpus(&card->dai_link[i]);
+ }
+
+ return ret;
+}
+
+static int macaudio_get_runtime_bclk_ratio(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dpcm *dpcm;
+
+ /*
+ * If this is a FE, look it up in link_props directly.
+ * If this is a BE, look it up in the respective FE.
+ */
+ if (!rtd->dai_link->no_pcm)
+ return ma->link_props[rtd->dai_link->id].bclk_ratio;
+
+ for_each_dpcm_fe(rtd, substream->stream, dpcm) {
+ int fe_id = dpcm->fe->dai_link->id;
+
+ return ma->link_props[fe_id].bclk_ratio;
+ }
+
+ return 0;
+}
+
+static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+ int i;
+
+ if (props->is_sense) {
+ rate->min = rate->max = cpu_dai->symmetric_rate;
+ return 0;
+ }
+
+ /* Speakers BE */
+ if (props->is_speakers) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ /* Sense PCM: keep the existing BE rate (0 if not already running) */
+ rate->min = rate->max = cpu_dai->symmetric_rate;
+
+ return 0;
+ }
+
+ /*
+ * Set the sense PCM rate control to inform userspace of the
+ * new sample rate.
+ */
+ ma->speaker_sample_rate = params_rate(params);
+ snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &ma->speaker_sample_rate_kctl->id);
+ }
+
+ if (bclk_ratio) {
+ struct snd_soc_dai *dai;
+ int mclk = params_rate(params) * bclk_ratio;
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ snd_soc_dai_set_sysclk(dai, 0, mclk, SND_SOC_CLOCK_IN);
+ snd_soc_dai_set_bclk_ratio(dai, bclk_ratio);
+ }
+
+ snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
+ snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
+ }
+
+ return 0;
+}
+
+static int macaudio_fe_startup(struct snd_pcm_substream *substream)
+{
+
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ int max_rate, ret;
+
+ if (props->is_sense) {
+ /*
+ * Sense stream will not return data while playback is inactive,
+ * so do not time out.
+ */
+ substream->wait_time = MAX_SCHEDULE_TIMEOUT;
+ return 0;
+ }
+
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ 0, ma->max_channels);
+ if (ret < 0)
+ return ret;
+
+ max_rate = MACAUDIO_MAX_BCLK_FREQ / props->bclk_ratio;
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ 0, max_rate);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int macaudio_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *be;
+ struct snd_soc_dpcm *dpcm;
+
+ be = NULL;
+ for_each_dpcm_be(rtd, substream->stream, dpcm) {
+ be = dpcm->be;
+ break;
+ }
+
+ if (!be) {
+ dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured by the user\n",
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ return macaudio_dpcm_hw_params(substream, params);
+}
+
+
+static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *dai;
+ int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+ int i;
+
+ if (bclk_ratio) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_dai_set_sysclk(dai, 0, 0, SND_SOC_CLOCK_IN);
+
+ snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ }
+}
+
+static int macaudio_be_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ int i;
+
+ if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /*
+ * Clear the DAI rates, so the next open can change the sample rate.
+ * This won't happen automatically if the sense PCM is open.
+ */
+ for_each_rtd_dais(rtd, i, dai) {
+ dai->symmetric_rate = 0;
+ }
+
+ /* Notify userspace that the speakers are closed */
+ ma->speaker_sample_rate = 0;
+ snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &ma->speaker_sample_rate_kctl->id);
+ }
+
+ return 0;
+}
+
+static int macaudio_be_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+ if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ma->bes_active |= BIT(rtd->dai_link->id);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ma->bes_active &= ~BIT(rtd->dai_link->id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ schedule_work(&ma->lock_update_work);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops macaudio_fe_ops = {
+ .startup = macaudio_fe_startup,
+ .shutdown = macaudio_dpcm_shutdown,
+ .hw_params = macaudio_fe_hw_params,
+};
+
+static const struct snd_soc_ops macaudio_be_ops = {
+ .hw_free = macaudio_be_hw_free,
+ .shutdown = macaudio_dpcm_shutdown,
+ .hw_params = macaudio_dpcm_hw_params,
+ .trigger = macaudio_be_trigger,
+};
+
+static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ unsigned int mask;
+ int nslots, ret, i;
+
+ if (!props->tdm_mask)
+ return 0;
+
+ mask = props->tdm_mask;
+ nslots = __fls(mask) + 1;
+
+ if (rtd->dai_link->num_codecs == 1) {
+ ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), mask,
+ 0, nslots, MACAUDIO_SLOTWIDTH);
+
+ /*
+ * Headphones get a pass on -ENOTSUPP (see the comment
+ * around bclk_ratio value for primary FE).
+ */
+ if (ret == -ENOTSUPP && props->is_headphones)
+ return 0;
+
+ return ret;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ int slot = __ffs(mask);
+
+ mask &= ~(1 << slot);
+ ret = snd_soc_dai_set_tdm_slot(dai, 1 << slot, 0, nslots,
+ MACAUDIO_SLOTWIDTH);
+ if (ret)
+ return ret;
+
+ if (props->codecs[i].idle_mode) {
+ ret = snd_soc_dai_set_tdm_idle(dai, props->codecs[i].idle_mask,
+ 0, props->codecs[i].idle_mode, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int macaudio_be_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ int i, ret;
+
+ ret = macaudio_be_assign_tdm(rtd);
+ if (ret < 0)
+ return ret;
+
+ if (props->is_headphones) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_component_set_jack(dai->component, &ma->jack, NULL);
+ }
+
+ return 0;
+}
+
+static void macaudio_be_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ int i;
+
+ if (props->is_headphones) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_component_set_jack(dai->component, NULL, NULL);
+ }
+}
+
+static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH;
+
+ if (props->is_sense)
+ return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), 0, 0xffff, 16, 16);
+
+ return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1,
+ (1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH);
+}
+
+static struct snd_soc_jack_pin macaudio_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int macaudio_probe(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = snd_soc_card_jack_new_pins(card, "Headphone Jack",
+ SND_JACK_HEADSET | SND_JACK_HEADPHONE,
+ &ma->jack, macaudio_jack_pins,
+ ARRAY_SIZE(macaudio_jack_pins));
+ if (ret < 0) {
+ dev_err(card->dev, "jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_soc_dai *dai,
+ bool is_speakers)
+{
+ struct snd_soc_dapm_route routes[2];
+ struct snd_soc_dapm_route *r;
+ int nroutes = 0;
+ int ret;
+
+ memset(routes, 0, sizeof(routes));
+
+ dev_dbg(card->dev, "adding routes for '%s'\n", dai->name);
+
+ r = &routes[nroutes++];
+ if (is_speakers)
+ r->source = "Speaker Playback";
+ else
+ r->source = "Headphone Playback";
+ r->sink = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
+
+ /* If headphone jack, add capture path */
+ if (!is_speakers) {
+ r = &routes[nroutes++];
+ r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
+ r->sink = "Headset Capture";
+ }
+
+ /* If speakers, add sense capture path */
+ if (is_speakers) {
+ r = &routes[nroutes++];
+ r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
+ r->sink = "Speaker Sense Capture";
+ }
+
+ ret = snd_soc_dapm_add_routes(card->dapm, routes, nroutes);
+ if (ret)
+ dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+ dai->name);
+ return ret;
+}
+
+static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component,
+ bool is_speakers)
+{
+ struct snd_soc_dapm_route routes[2];
+ struct snd_soc_dapm_route *r;
+ int nroutes = 0;
+ char buf[32];
+ int ret;
+
+ memset(routes, 0, sizeof(routes));
+
+ /* Connect the far ends of CODECs to pins */
+ if (is_speakers) {
+ r = &routes[nroutes++];
+ r->source = "OUT";
+ if (component->name_prefix) {
+ snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix);
+ r->source = buf;
+ }
+ r->sink = "Speaker";
+ } else {
+ r = &routes[nroutes++];
+ r->source = "Jack HP";
+ r->sink = "Headphone";
+ r = &routes[nroutes++];
+ r->source = "Headset Mic";
+ r->sink = "Jack HS";
+ }
+
+ ret = snd_soc_dapm_add_routes(card->dapm, routes, nroutes);
+ if (ret)
+ dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+ component->name);
+ return ret;
+}
+
+static int macaudio_late_probe(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *dai;
+ int ret, i;
+
+ /* Add the dynamic DAPM routes */
+ for_each_card_rtds(card, rtd) {
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+ if (!rtd->dai_link->no_pcm)
+ continue;
+
+ for_each_rtd_cpu_dais(rtd, i, dai) {
+ ret = macaudio_add_backend_dai_route(card, dai, props->is_speakers);
+
+ if (ret)
+ return ret;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ ret = macaudio_add_pin_routes(card, dai->component,
+ props->is_speakers);
+
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (ma->has_speakers)
+ ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card,
+ "Speaker Sample Rate");
+ if (ma->has_safety) {
+ ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card,
+ "Speaker Volume Unlock");
+
+ mutex_lock(&ma->volume_lock_mutex);
+ macaudio_vlimit_unlock(ma, false);
+ mutex_unlock(&ma->volume_lock_mutex);
+ }
+
+ return 0;
+}
+
+#define CHECK(call, pattern, value, min) \
+ { \
+ int ret = call(card, pattern, value); \
+ int err = (ret >= 0 && ret < min) ? -ERANGE : ret; \
+ if (err < 0) { \
+ dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, \
+ ret); \
+ if (please_blow_up_my_speakers < 2) \
+ return err; \
+ } else { \
+ dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, \
+ pattern, ret); \
+ } \
+ }
+
+#define CHECK_CONCAT(call, suffix, value) \
+ { \
+ snprintf(buf, sizeof(buf), "%s%s", prefix, suffix); \
+ CHECK(call, buf, value, 1); \
+ }
+
+static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, bool tweeter)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ char buf[256];
+
+ if (!ma->has_speakers)
+ return 0;
+
+ switch (ma->cfg->amp) {
+ case AMP_TAS5770:
+ if (ma->cfg->stereo) {
+ CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left");
+ CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0);
+ }
+
+ CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain);
+ break;
+ case AMP_SN012776:
+ if (ma->cfg->stereo) {
+ CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left");
+ CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0);
+ }
+
+ CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain);
+ CHECK_CONCAT(snd_soc_set_enum_kctl, "HPF Corner Frequency",
+ tweeter ? "800 Hz" : "2 Hz");
+
+ if (please_blow_up_my_speakers < 2)
+ CHECK_CONCAT(snd_soc_deactivate_kctl, "HPF Corner Frequency", 0);
+
+ CHECK_CONCAT(snd_soc_set_enum_kctl, "OCE Handling", "Retry");
+ CHECK_CONCAT(snd_soc_deactivate_kctl, "OCE Handling", 0);
+ break;
+ case AMP_SSM3515:
+ /* TODO: check */
+ CHECK_CONCAT(snd_soc_set_enum_kctl, "DAC Analog Gain Select", "8.4 V Span");
+
+ if (please_blow_up_my_speakers < 2)
+ CHECK_CONCAT(snd_soc_deactivate_kctl, "DAC Analog Gain Select", 0);
+
+ /* TODO: HPF, needs new call to set */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int macaudio_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ const char *p;
+
+ /* Set the card ID early to avoid races with udev */
+ p = strrchr(card->name, ' ');
+ if (p) {
+ snprintf(card->snd_card->id, sizeof(card->snd_card->id),
+ "Apple%s", p + 1);
+ }
+
+ if (!ma->has_speakers)
+ return 0;
+
+ /*
+ * This needs some care to avoid matches against cs42l84's
+ * "Jack HPF Corner Frequency".
+ */
+ switch (ma->cfg->speakers) {
+ case SPKR_NONE:
+ WARN_ON(please_blow_up_my_speakers < 2);
+ return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL;
+ case SPKR_1W:
+ /* only 1W stereo system (J313) is uses cs42l83 */
+ if (ma->cfg->stereo) {
+ CHECK(macaudio_set_speaker, "* ", false, 0);
+ } else {
+ CHECK(macaudio_set_speaker, "", false, 0);
+ }
+ break;
+ case SPKR_2W:
+ CHECK(macaudio_set_speaker, "* Front ", false, 0);
+ CHECK(macaudio_set_speaker, "* Rear ", false, 0);
+ break;
+ case SPKR_1W1T:
+ CHECK(macaudio_set_speaker, "* Tweeter ", true, 0);
+ CHECK(macaudio_set_speaker, "* Woofer ", false, 0);
+ break;
+ case SPKR_2W1T:
+ CHECK(macaudio_set_speaker, "* Tweeter ", true, 0);
+ CHECK(macaudio_set_speaker, "* Woofer 1 ", false, 0);
+ CHECK(macaudio_set_speaker, "* Woofer 2 ", false, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const char * const macaudio_spk_mux_texts[] = {
+ "Primary",
+ "Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_spk_mux_enum, macaudio_spk_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_spk_mux =
+ SOC_DAPM_ENUM("Speaker Playback Mux", macaudio_spk_mux_enum);
+
+static const char * const macaudio_hp_mux_texts[] = {
+ "Primary",
+ "Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_hp_mux =
+ SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum);
+
+static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_SPK("Speaker (Static)", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux),
+ SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux),
+
+ SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("Speaker Sense Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static int macaudio_sss_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 192000;
+
+ return 0;
+}
+
+static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ /*
+ * TODO: Check if any locking is in order here. I would
+ * assume there is some ALSA-level lock, but DAPM implementations
+ * of kcontrol ops do explicit locking, so look into it.
+ */
+ uvalue->value.integer.value[0] = ma->speaker_sample_rate;
+
+ return 0;
+}
+
+static int macaudio_slk_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = INT_MIN;
+ uinfo->value.integer.max = INT_MAX;
+
+ return 0;
+}
+
+static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (!ma->speaker_lock_owner)
+ return -EPERM;
+
+ if (uvalue->value.integer.value[0] != SPEAKER_MAGIC_VALUE)
+ return -EINVAL;
+
+ /* Serves as a notification that the lock was lost at some point */
+ if (ma->speaker_volume_was_locked) {
+ ma->speaker_volume_was_locked = false;
+ return -ETIMEDOUT;
+ }
+
+ mutex_lock(&ma->volume_lock_mutex);
+
+ cancel_delayed_work(&ma->lock_timeout_work);
+
+ ma->speaker_lock_remain = ms_to_ktime(SPEAKER_LOCK_TIMEOUT);
+ ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain);
+ macaudio_vlimit_update(ma);
+
+ if (ma->speaker_lock_timeout_enabled) {
+ dev_dbg(ma->card.dev, "Volume limit timeout ping: %ld us left\n",
+ (long)ktime_to_us(ma->speaker_lock_remain));
+ schedule_delayed_work(&ma->lock_timeout_work,
+ usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain)));
+ }
+
+ mutex_unlock(&ma->volume_lock_mutex);
+
+ return 0;
+}
+
+static int macaudio_slk_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ uvalue->value.integer.value[0] = ma->speaker_volume_unlocked ? 1 : 0;
+
+ return 0;
+}
+
+static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ mutex_lock(&ma->volume_lock_mutex);
+ ma->speaker_lock_owner = owner;
+ macaudio_vlimit_update(ma);
+
+ /*
+ * Reset the unintended lock flag when the control is first locked.
+ * At this point the state is locked and cannot be unlocked until
+ * userspace writes to this control, so this cannot spuriously become
+ * true again until that point.
+ */
+ ma->speaker_volume_was_locked = false;
+
+ mutex_unlock(&ma->volume_lock_mutex);
+
+ return 0;
+}
+
+static void macaudio_slk_unlock(struct snd_kcontrol *kcontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ ma->speaker_lock_owner = NULL;
+ ma->speaker_lock_timeout = 0;
+ macaudio_vlimit_update(ma);
+}
+
+/*
+ * Speaker limit controls go last. We only drop the unlock control,
+ * leaving sample rate, since that can be useful for safety
+ * bring-up before the kernel-side caps are ready.
+ */
+#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 1
+/*
+ * If there are no speakers configured at all, we can drop both
+ * controls.
+ */
+#define MACAUDIO_NUM_SPEAKER_CONTROLS 2
+
+static const struct snd_kcontrol_new macaudio_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .name = "Speaker Sample Rate",
+ .info = macaudio_sss_info, .get = macaudio_sss_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .name = "Speaker Volume Unlock",
+ .info = macaudio_slk_info,
+ .put = macaudio_slk_put, .get = macaudio_slk_get,
+ .lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock,
+ },
+};
+
+static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
+ /* Playback paths */
+ { "Speaker Playback Mux", "Primary", "PCM0 TX" },
+ { "Speaker Playback Mux", "Secondary", "PCM1 TX" },
+ { "Speaker Playback", NULL, "Speaker Playback Mux"},
+
+ { "Headphone Playback Mux", "Primary", "PCM0 TX" },
+ { "Headphone Playback Mux", "Secondary", "PCM1 TX" },
+ { "Headphone Playback", NULL, "Headphone Playback Mux"},
+ /*
+ * Additional paths (to specific I2S ports) are added dynamically.
+ */
+
+ /* Capture paths */
+ { "PCM0 RX", NULL, "Headset Capture" },
+
+ /* Sense paths */
+ { "PCM2 RX", NULL, "Speaker Sense Capture" },
+};
+
+/* enable amp speakers stereo gain safe_vol */
+struct macaudio_platform_cfg macaudio_j180_cfg = {
+ false, AMP_SN012776, SPKR_1W1T, false, 10, -20,
+};
+struct macaudio_platform_cfg macaudio_j274_cfg = {
+ true, AMP_TAS5770, SPKR_1W, false, 20, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j293_cfg = {
+ true, AMP_TAS5770, SPKR_2W, true, 15, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j313_cfg = {
+ true, AMP_TAS5770, SPKR_1W, true, 10, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j314_cfg = {
+ true, AMP_SN012776, SPKR_2W1T, true, 15, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
+ true, AMP_SN012776, SPKR_1W, false, 20, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j413_cfg = {
+ true, AMP_SN012776, SPKR_1W1T, true, 15, -20,
+};
+
+struct macaudio_platform_cfg macaudio_j45x_cfg = {
+ false, AMP_SSM3515, SPKR_1W1T, true, 9, -20, /* TODO: gain?? */
+};
+
+struct macaudio_platform_cfg macaudio_j493_cfg = {
+ true, AMP_SN012776, SPKR_2W, true, 15, -20,
+};
+
+struct macaudio_platform_cfg macaudio_fallback_cfg = {
+ false, AMP_NONE, SPKR_NONE, false, 0, 0,
+};
+
+/*
+ * DT compatible/ID table rules:
+ *
+ * 1. Machines with **identical** speaker configurations (amps, models, chassis)
+ * are allowed to declare compatibility with the first model (chronologically),
+ * and are not enumerated in this array.
+ *
+ * 2. Machines with identical amps and speakers (=identical speaker protection
+ * rules) but a different chassis must use different compatibles, but may share
+ * the private data structure here. They are explicitly enumerated.
+ *
+ * 3. Machines with different amps or speaker layouts must use separate
+ * data structures.
+ *
+ * 4. Machines with identical speaker layouts and amps (but possibly different
+ * speaker models/chassis) may share the data structure, since only userspace
+ * cares about that (assuming our general -20dB safe level standard holds).
+ */
+static const struct of_device_id macaudio_snd_device_id[] = {
+ /* Model ID Amp Gain Speakers */
+ /* j180 AID19 sn012776 10 1× 1W+1T */
+ { .compatible = "apple,j180-macaudio", .data = &macaudio_j180_cfg },
+ /* j274 AID6 tas5770 20 1× 1W */
+ { .compatible = "apple,j274-macaudio", .data = &macaudio_j274_cfg },
+ /* j293 AID3 tas5770 15 2× 2W */
+ { .compatible = "apple,j293-macaudio", .data = &macaudio_j293_cfg },
+ /* j313 AID4 tas5770 10 2× 1W */
+ { .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg },
+ /* j314 AID8 sn012776 15 2× 2W+1T */
+ { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg },
+ /* j316 AID9 sn012776 15 2× 2W+1T */
+ { .compatible = "apple,j316-macaudio", .data = &macaudio_j314_cfg },
+ /* j375 AID10 sn012776 15 1× 1W */
+ { .compatible = "apple,j375-macaudio", .data = &macaudio_j37x_j47x_cfg },
+ /* j413 AID13 sn012776 15 2× 1W+1T */
+ { .compatible = "apple,j413-macaudio", .data = &macaudio_j413_cfg },
+ /* j414 AID14 sn012776 15 2× 2W+1T Compat: apple,j314-macaudio */
+ /* j415 AID27 sn012776 15 2× 2W+1T */
+ { .compatible = "apple,j415-macaudio", .data = &macaudio_j314_cfg },
+ /* j416 AID15 sn012776 15 2× 2W+1T Compat: apple,j316-macaudio */
+ /* j456 AID5 ssm3515 15 2× 1W+1T */
+ { .compatible = "apple,j456-macaudio", .data = &macaudio_j45x_cfg },
+ /* j457 AID7 ssm3515 15 2× 1W+1T Compat: apple,j456-macaudio */
+ /* j473 AID12 sn012776 20 1× 1W */
+ { .compatible = "apple,j473-macaudio", .data = &macaudio_j37x_j47x_cfg },
+ /* j474 AID26 sn012776 20 1× 1W Compat: apple,j473-macaudio */
+ /* j475 AID25 sn012776 20 1× 1W Compat: apple,j375-macaudio */
+ /* j493 AID18 sn012776 15 2× 2W */
+ { .compatible = "apple,j493-macaudio", .data = &macaudio_j493_cfg },
+ /* Fallback, jack only */
+ { .compatible = "apple,macaudio"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, macaudio_snd_device_id);
+
+static int macaudio_snd_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct macaudio_snd_data *data;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_dai_link *link;
+ const struct of_device_id *of_id;
+ int ret;
+ int i;
+
+ of_id = of_match_device(macaudio_snd_device_id, dev);
+ if (!of_id)
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ card = &data->card;
+ snd_soc_card_set_drvdata(card, data);
+ dev_set_drvdata(&pdev->dev, data);
+ mutex_init(&data->volume_lock_mutex);
+
+ card->owner = THIS_MODULE;
+ card->driver_name = "macaudio";
+ card->dev = dev;
+ card->dapm_widgets = macaudio_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets);
+ card->dapm_routes = macaudio_dapm_routes;
+ card->num_dapm_routes = ARRAY_SIZE(macaudio_dapm_routes);
+ card->controls = macaudio_controls;
+ card->num_controls = ARRAY_SIZE(macaudio_controls);
+ card->probe = macaudio_probe;
+ card->late_probe = macaudio_late_probe;
+ card->component_chaining = true;
+ card->fully_routed = true;
+
+ if (of_id->data)
+ data->cfg = of_id->data;
+ else
+ data->cfg = &macaudio_fallback_cfg;
+
+ card->fixup_controls = macaudio_fixup_controls;
+
+ ret = macaudio_parse_of(data);
+ if (ret)
+ return ret;
+
+ /* Remove useless controls */
+ if (!data->has_speakers) /* No speakers, remove both */
+ card->num_controls -= MACAUDIO_NUM_SPEAKER_CONTROLS;
+ else if (!data->cfg->safe_vol) /* No safety, remove unlock */
+ card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS;
+ else /* Speakers with safety, mark us as such */
+ data->has_safety = true;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm) {
+ link->ops = &macaudio_be_ops;
+ link->init = macaudio_be_init;
+ link->exit = macaudio_be_exit;
+ } else {
+ link->ops = &macaudio_fe_ops;
+ link->init = macaudio_fe_init;
+ }
+ }
+
+ INIT_WORK(&data->lock_update_work, macaudio_vlimit_update_work);
+ INIT_DELAYED_WORK(&data->lock_timeout_work, macaudio_vlimit_timeout_work);
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static void macaudio_snd_platform_remove(struct platform_device *pdev)
+{
+ struct macaudio_snd_data *ma = dev_get_drvdata(&pdev->dev);
+
+ cancel_delayed_work_sync(&ma->lock_timeout_work);
+}
+
+static struct platform_driver macaudio_snd_driver = {
+ .probe = macaudio_snd_platform_probe,
+ .remove = macaudio_snd_platform_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = macaudio_snd_device_id,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(macaudio_snd_driver);
+
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_DESCRIPTION("Apple Silicon Macs machine-level sound driver");
+MODULE_LICENSE("GPL");
--
2.55.0
next prev parent reply other threads:[~2026-09-20 4:57 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-20 4:53 [PATCH 00/28] Add speaker and headset jack support for Apple Silicon Macs James Calligeros
2026-09-20 4:53 ` [PATCH 01/28] dt-bindings: sound: Add Apple Macs sound peripherals James Calligeros
2026-09-20 4:53 ` [PATCH 02/28] ASoC: cs42l42: Fix typo James Calligeros
2026-09-20 4:53 ` [PATCH 03/28] ASoC: cs42l42: Set a faster digital ramp-up rate James Calligeros
2026-09-20 4:53 ` [PATCH 04/28] ASoC: apple: mca: Fix PD link double-frees James Calligeros
2026-09-20 4:53 ` [PATCH 05/28] alsa: pcm: Remove the qos request only if active James Calligeros
2026-09-20 4:53 ` [PATCH 06/28] ALSA: dmaengine: Always terminate DMA when a PCM is closed James Calligeros
2026-09-20 4:53 ` [PATCH 07/28] ALSA: Support nonatomic dmaengine PCMs James Calligeros
2026-09-20 4:53 ` [PATCH 08/28] ALSA: control: Add kcontrol callbacks for lock/unlock James Calligeros
2026-09-20 4:53 ` [PATCH 09/28] ASoC: ops: Move guts out of snd_soc_limit_volume James Calligeros
2026-09-20 4:53 ` [PATCH 10/28] ASoC: ops: Accept patterns in snd_soc_limit_volume James Calligeros
2026-09-20 4:53 ` [PATCH 11/28] ASoC: ops: Introduce 'snd_soc_deactivate_kctl' James Calligeros
2026-09-20 4:53 ` [PATCH 12/28] ASoC: ops: Introduce 'soc_set_enum_kctl' James Calligeros
2026-09-20 4:53 ` [PATCH 13/28] ASoC: card: Let 'fixup_controls' return errors James Calligeros
2026-09-20 4:53 ` [PATCH 14/28] ASoC: ops: Export snd_soc_control_matches() James Calligeros
2026-09-20 4:53 ` [PATCH 15/28] ASoC: tas2764: Set up V/ISENSE on codec probe James Calligeros
2026-09-20 4:53 ` James Calligeros [this message]
2026-09-20 4:53 ` [PATCH 17/28] arm64: dts: apple: t8103-j274: Add speaker/headset jack nodes James Calligeros
2026-09-20 4:53 ` [PATCH 18/28] arm64: dts: apple: t8103-j313: " James Calligeros
2026-09-20 4:53 ` [PATCH 19/28] arm64: dts: apple: t8103-j293: " James Calligeros
2026-09-20 4:53 ` [PATCH 20/28] arm64: dts: apple: t8103-j45x: Add headset " James Calligeros
2026-09-20 4:54 ` [PATCH 21/28] arm64: dts: apple: t8112-j413: Add speaker/headset " James Calligeros
2026-09-20 4:54 ` [PATCH 22/28] arm64: dts: apple: t8112-j415: " James Calligeros
2026-09-20 4:54 ` [PATCH 23/28] arm64: dts: apple: t8112-j473: " James Calligeros
2026-09-20 4:54 ` [PATCH 24/28] arm64: dts: apple: t8112-j493: " James Calligeros
2026-09-20 4:54 ` [PATCH 25/28] arm64: dts: apple: t600x-j31x: " James Calligeros
2026-09-20 4:54 ` [PATCH 26/28] arm64: dts: apple: t600x-j375: " James Calligeros
2026-09-20 4:54 ` [PATCH 27/28] arm64: dts: apple: t602x-j41x: " James Calligeros
2026-09-20 4:54 ` [PATCH 28/28] arm64: dts: apple: t602x-j47x: " James Calligeros
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260920-macaudio-v1-16-741cc20a74e5@gmail.com \
--to=jcalligeros99@gmail.com \
--cc=amit.kucheria@linaro.org \
--cc=angelogioacchino.delregno@collabora.com \
--cc=asahi@lists.linux.dev \
--cc=baojun.xu@ti.com \
--cc=broonie@kernel.org \
--cc=conor+dt@kernel.org \
--cc=david.rhodes@cirrus.com \
--cc=devicetree@vger.kernel.org \
--cc=j@jannau.net \
--cc=james.schulman@cirrus.com \
--cc=k@chaosmail.tech \
--cc=kevin-lu@ti.com \
--cc=krzk+dt@kernel.org \
--cc=lars@metafoo.de \
--cc=lgirdwood@gmail.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=linux-sound@vger.kernel.org \
--cc=marcan@marcan.st \
--cc=matthias.bgg@gmail.com \
--cc=neal@gompa.dev \
--cc=patches@opensource.cirrus.com \
--cc=perex@perex.cz \
--cc=povik+lin@cutebit.org \
--cc=rafael.j.wysocki@intel.com \
--cc=rf@opensource.cirrus.com \
--cc=robh@kernel.org \
--cc=sen@ti.com \
--cc=shenghao-ding@ti.com \
--cc=sven@kernel.org \
--cc=tiwai@suse.com \
--cc=tiwai@suse.de \
--cc=ulfh@kernel.org \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®