mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>
To: Takashi Iwai <tiwai@suse.com>,
	 Thierry Reding <thierry.reding@kernel.org>,
	 Jonathan Hunter <jonathanh@nvidia.com>,
	Jaroslav Kysela <perex@perex.cz>
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-tegra@vger.kernel.org, notify@kernel.org,
	"Cássio Gabriel" <cassiogabrielcontato@gmail.com>
Subject: [PATCH RFC 1/4] ALSA: pcm: Add common CEA channel allocation helpers
Date: Fri, 12 Jun 2026 01:53:23 -0300	[thread overview]
Message-ID: <20260612-alsa-cea-channel-allocation-v1-1-b1bcbb6b1051@gmail.com> (raw)
In-Reply-To: <20260612-alsa-cea-channel-allocation-v1-0-b1bcbb6b1051@gmail.com>

HDA HDMI and Intel HDMI LPE carry independent copies of the ordered
CEA-861 channel allocation table, ELD speaker expansion, and allocation
selection loop. The copies originated from the same implementation but
already differ in table coverage and fallback policy.

Move the immutable descriptors and common transformations next to the
other PCM DRM ELD helpers. Build the derived channel count and speaker
mask at compile time, and make table range and fallback policy explicit
arguments so consumers can preserve their existing contracts.

Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
 include/sound/pcm_drm_eld.h |  16 ++++
 sound/core/pcm_drm_eld.c    | 181 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 197 insertions(+)

diff --git a/include/sound/pcm_drm_eld.h b/include/sound/pcm_drm_eld.h
index 5a38413ada91..917228e97375 100644
--- a/include/sound/pcm_drm_eld.h
+++ b/include/sound/pcm_drm_eld.h
@@ -2,6 +2,8 @@
 #ifndef __SOUND_PCM_DRM_ELD_H
 #define __SOUND_PCM_DRM_ELD_H
 
+struct snd_info_buffer;
+
 enum eld_versions {
 	ELD_VER_CEA_861D	= 2,
 	ELD_VER_PARTIAL		= 31,
@@ -84,6 +86,20 @@ struct snd_parsed_hdmi_eld {
 	struct snd_cea_sad sad[ELD_MAX_SAD];
 };
 
+struct snd_cea_channel_speaker_allocation {
+	unsigned int ca_index;
+	unsigned int speakers[8];
+	unsigned int channels;
+	unsigned int spk_mask;
+};
+
+extern const struct snd_cea_channel_speaker_allocation
+	snd_cea_channel_allocations[];
+extern const unsigned int snd_cea_channel_allocations_count;
+unsigned int snd_cea_spk_mask_from_alloc(unsigned int spk_alloc);
+int snd_cea_channel_allocation(unsigned int spk_alloc, unsigned int channels,
+			       unsigned int max_ca, bool fallback);
+
 int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld);
 
 int snd_parse_eld(struct device *dev, struct snd_parsed_hdmi_eld *e,
diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c
index 1941ee520063..20156a9e3ed9 100644
--- a/sound/core/pcm_drm_eld.c
+++ b/sound/core/pcm_drm_eld.c
@@ -24,6 +24,187 @@
 #define SAD1_RATE_176400_MASK	BIT(5)
 #define SAD1_RATE_192000_MASK	BIT(6)
 
+/*
+ * CEA speaker placement:
+ *
+ *        FLH       FCH        FRH
+ *  FLW    FL  FLC   FC   FRC   FR   FRW
+ *
+ *                                  LFE
+ *                     TC
+ *
+ *          RL  RLC   RC   RRC   RR
+ *
+ * The Left/Right Surround channel notions LS/RS in SMPTE 320M correspond to
+ * CEA RL/RR; the SMPTE channel assignment C/LFE is swapped to CEA LFE/FC.
+ */
+enum cea_speaker_placement {
+	FL  = BIT(0),	/* Front Left */
+	FC  = BIT(1),	/* Front Center */
+	FR  = BIT(2),	/* Front Right */
+	FLC = BIT(3),	/* Front Left Center */
+	FRC = BIT(4),	/* Front Right Center */
+	RL  = BIT(5),	/* Rear Left */
+	RC  = BIT(6),	/* Rear Center */
+	RR  = BIT(7),	/* Rear Right */
+	RLC = BIT(8),	/* Rear Left Center */
+	RRC = BIT(9),	/* Rear Right Center */
+	LFE = BIT(10),	/* Low Frequency Effect */
+	FLW = BIT(11),	/* Front Left Wide */
+	FRW = BIT(12),	/* Front Right Wide */
+	FLH = BIT(13),	/* Front Left High */
+	FCH = BIT(14),	/* Front Center High */
+	FRH = BIT(15),	/* Front Right High */
+	TC  = BIT(16),	/* Top Center */
+};
+
+#define CEA_CHANNEL_ALLOCATION(_ca, _s7, _s6, _s5, _s4, _s3, _s2, _s1, _s0) \
+	{ .ca_index = (_ca), .speakers = { (_s7), (_s6), (_s5), (_s4), \
+		(_s3), (_s2), (_s1), (_s0) }, \
+	  .channels = !!(_s7) + !!(_s6) + !!(_s5) + !!(_s4) + \
+		!!(_s3) + !!(_s2) + !!(_s1) + !!(_s0), \
+	  .spk_mask = (_s7) | (_s6) | (_s5) | (_s4) | (_s3) | (_s2) | \
+		(_s1) | (_s0) }
+
+/*
+ * This is an ordered list. Earlier entries have a better chance of being
+ * selected by snd_cea_channel_allocation().
+ */
+const struct snd_cea_channel_speaker_allocation snd_cea_channel_allocations[] = {
+	/*                         channel:  7    6    5    4    3    2    1    0 */
+	CEA_CHANNEL_ALLOCATION(0x00,   0,   0,   0,   0,   0,   0,  FR,  FL),
+	/* 2.1 */
+	CEA_CHANNEL_ALLOCATION(0x01,   0,   0,   0,   0,   0, LFE,  FR,  FL),
+	/* Dolby Surround */
+	CEA_CHANNEL_ALLOCATION(0x02,   0,   0,   0,   0,  FC,   0,  FR,  FL),
+	/* surround40 */
+	CEA_CHANNEL_ALLOCATION(0x08,   0,   0,  RR,  RL,   0,   0,  FR,  FL),
+	/* surround41 */
+	CEA_CHANNEL_ALLOCATION(0x09,   0,   0,  RR,  RL,   0, LFE,  FR,  FL),
+	/* surround50 */
+	CEA_CHANNEL_ALLOCATION(0x0a,   0,   0,  RR,  RL,  FC,   0,  FR,  FL),
+	/* surround51 */
+	CEA_CHANNEL_ALLOCATION(0x0b,   0,   0,  RR,  RL,  FC, LFE,  FR,  FL),
+	/* 6.1 */
+	CEA_CHANNEL_ALLOCATION(0x0f,   0,  RC,  RR,  RL,  FC, LFE,  FR,  FL),
+	/* surround71 */
+	CEA_CHANNEL_ALLOCATION(0x13, RRC, RLC,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x03,   0,   0,   0,   0,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x04,   0,   0,   0,  RC,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x05,   0,   0,   0,  RC,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x06,   0,   0,   0,  RC,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x07,   0,   0,   0,  RC,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x0c,   0,  RC,  RR,  RL,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x0d,   0,  RC,  RR,  RL,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x0e,   0,  RC,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x10, RRC, RLC,  RR,  RL,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x11, RRC, RLC,  RR,  RL,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x12, RRC, RLC,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x14, FRC, FLC,   0,   0,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x15, FRC, FLC,   0,   0,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x16, FRC, FLC,   0,   0,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x17, FRC, FLC,   0,   0,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x18, FRC, FLC,   0,  RC,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x19, FRC, FLC,   0,  RC,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1a, FRC, FLC,   0,  RC,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1b, FRC, FLC,   0,  RC,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1c, FRC, FLC,  RR,  RL,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1d, FRC, FLC,  RR,  RL,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1e, FRC, FLC,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x1f, FRC, FLC,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x20,   0, FCH,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x21,   0, FCH,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x22,  TC,   0,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x23,  TC,   0,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x24, FRH, FLH,  RR,  RL,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x25, FRH, FLH,  RR,  RL,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x26, FRW, FLW,  RR,  RL,   0,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x27, FRW, FLW,  RR,  RL,   0, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x28,  TC,  RC,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x29,  TC,  RC,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2a, FCH,  RC,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2b, FCH,  RC,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2c,  TC, FCH,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2d,  TC, FCH,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2e, FRH, FLH,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x2f, FRH, FLH,  RR,  RL,  FC, LFE,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x30, FRW, FLW,  RR,  RL,  FC,   0,  FR,  FL),
+	CEA_CHANNEL_ALLOCATION(0x31, FRW, FLW,  RR,  RL,  FC, LFE,  FR,  FL),
+};
+EXPORT_SYMBOL_GPL(snd_cea_channel_allocations);
+
+const unsigned int snd_cea_channel_allocations_count =
+	ARRAY_SIZE(snd_cea_channel_allocations);
+EXPORT_SYMBOL_GPL(snd_cea_channel_allocations_count);
+
+/* ELD SA bits in the CEA Speaker Allocation data block. */
+static const unsigned int eld_speaker_allocation_bits[] = {
+	[0] = FL | FR,
+	[1] = LFE,
+	[2] = FC,
+	[3] = RL | RR,
+	[4] = RC,
+	[5] = FLC | FRC,
+	[6] = RLC | RRC,
+	/* Not represented by the baseline ELD speaker allocation byte. */
+	[7] = FLW | FRW,
+	[8] = FLH | FRH,
+	[9] = TC,
+	[10] = FCH,
+};
+
+unsigned int snd_cea_spk_mask_from_alloc(unsigned int spk_alloc)
+{
+	unsigned int spk_mask = 0;
+	int i;
+
+	/* Expand ELD's compact, paired speaker allocation into a full mask. */
+	for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++)
+		if (spk_alloc & BIT(i))
+			spk_mask |= eld_speaker_allocation_bits[i];
+
+	return spk_mask;
+}
+EXPORT_SYMBOL_GPL(snd_cea_spk_mask_from_alloc);
+
+/*
+ * The transformation takes two steps:
+ *
+ *      spk_alloc => eld_speaker_allocation_bits[] => spk_mask
+ *       spk_mask => snd_cea_channel_allocations[] => CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+ */
+int snd_cea_channel_allocation(unsigned int spk_alloc, unsigned int channels,
+			       unsigned int max_ca, bool fallback)
+{
+	unsigned int spk_mask, i;
+
+	/* CA 0 is the basic stereo allocation. */
+	if (channels <= 2)
+		return 0;
+
+	spk_mask = snd_cea_spk_mask_from_alloc(spk_alloc);
+	for (i = 0; i < snd_cea_channel_allocations_count; i++) {
+		const struct snd_cea_channel_speaker_allocation *ca;
+
+		ca = &snd_cea_channel_allocations[i];
+		if (ca->ca_index <= max_ca && ca->channels == channels &&
+		    (spk_mask & ca->spk_mask) == ca->spk_mask)
+			return ca->ca_index;
+	}
+
+	/* Fall back to the preferred allocation with the requested channels. */
+	if (fallback)
+		for (i = 0; i < snd_cea_channel_allocations_count; i++)
+			if (snd_cea_channel_allocations[i].ca_index <= max_ca &&
+			    snd_cea_channel_allocations[i].channels == channels)
+				return snd_cea_channel_allocations[i].ca_index;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_cea_channel_allocation);
+
 static const unsigned int eld_rates[] = {
 	32000,
 	44100,

-- 
2.54.0


  reply	other threads:[~2026-06-12  4:53 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-12  4:53 [PATCH RFC 0/4] ALSA: Consolidate CEA channel allocation data Cássio Gabriel
2026-06-12  4:53 ` Cássio Gabriel [this message]
2026-06-12  4:53 ` [PATCH RFC 2/4] ALSA: hda: Make CEA channel allocation interfaces const Cássio Gabriel
2026-06-12  4:53 ` [PATCH RFC 3/4] ALSA: hda: Use common CEA channel allocations Cássio Gabriel
2026-06-12  4:53 ` [PATCH RFC 4/4] ALSA: x86: " Cássio Gabriel
2026-06-12  9:13 ` [PATCH RFC 0/4] ALSA: Consolidate CEA channel allocation data Takashi Iwai
2026-06-12 11:44   ` Cássio Gabriel

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=20260612-alsa-cea-channel-allocation-v1-1-b1bcbb6b1051@gmail.com \
    --to=cassiogabrielcontato@gmail.com \
    --cc=jonathanh@nvidia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=linux-tegra@vger.kernel.org \
    --cc=notify@kernel.org \
    --cc=perex@perex.cz \
    --cc=thierry.reding@kernel.org \
    --cc=tiwai@suse.com \
    /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®