162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HDMI Channel map support helpers
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <sound/control.h>
862306a36Sopenharmony_ci#include <sound/tlv.h>
962306a36Sopenharmony_ci#include <sound/hda_chmap.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/*
1262306a36Sopenharmony_ci * CEA speaker placement:
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *        FLH       FCH        FRH
1562306a36Sopenharmony_ci *  FLW    FL  FLC   FC   FRC   FR   FRW
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci *                                  LFE
1862306a36Sopenharmony_ci *                     TC
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci *          RL  RLC   RC   RRC   RR
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to
2362306a36Sopenharmony_ci * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC.
2462306a36Sopenharmony_ci */
2562306a36Sopenharmony_cienum cea_speaker_placement {
2662306a36Sopenharmony_ci	FL  = (1 <<  0),	/* Front Left           */
2762306a36Sopenharmony_ci	FC  = (1 <<  1),	/* Front Center         */
2862306a36Sopenharmony_ci	FR  = (1 <<  2),	/* Front Right          */
2962306a36Sopenharmony_ci	FLC = (1 <<  3),	/* Front Left Center    */
3062306a36Sopenharmony_ci	FRC = (1 <<  4),	/* Front Right Center   */
3162306a36Sopenharmony_ci	RL  = (1 <<  5),	/* Rear Left            */
3262306a36Sopenharmony_ci	RC  = (1 <<  6),	/* Rear Center          */
3362306a36Sopenharmony_ci	RR  = (1 <<  7),	/* Rear Right           */
3462306a36Sopenharmony_ci	RLC = (1 <<  8),	/* Rear Left Center     */
3562306a36Sopenharmony_ci	RRC = (1 <<  9),	/* Rear Right Center    */
3662306a36Sopenharmony_ci	LFE = (1 << 10),	/* Low Frequency Effect */
3762306a36Sopenharmony_ci	FLW = (1 << 11),	/* Front Left Wide      */
3862306a36Sopenharmony_ci	FRW = (1 << 12),	/* Front Right Wide     */
3962306a36Sopenharmony_ci	FLH = (1 << 13),	/* Front Left High      */
4062306a36Sopenharmony_ci	FCH = (1 << 14),	/* Front Center High    */
4162306a36Sopenharmony_ci	FRH = (1 << 15),	/* Front Right High     */
4262306a36Sopenharmony_ci	TC  = (1 << 16),	/* Top Center           */
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic const char * const cea_speaker_allocation_names[] = {
4662306a36Sopenharmony_ci	/*  0 */ "FL/FR",
4762306a36Sopenharmony_ci	/*  1 */ "LFE",
4862306a36Sopenharmony_ci	/*  2 */ "FC",
4962306a36Sopenharmony_ci	/*  3 */ "RL/RR",
5062306a36Sopenharmony_ci	/*  4 */ "RC",
5162306a36Sopenharmony_ci	/*  5 */ "FLC/FRC",
5262306a36Sopenharmony_ci	/*  6 */ "RLC/RRC",
5362306a36Sopenharmony_ci	/*  7 */ "FLW/FRW",
5462306a36Sopenharmony_ci	/*  8 */ "FLH/FRH",
5562306a36Sopenharmony_ci	/*  9 */ "TC",
5662306a36Sopenharmony_ci	/* 10 */ "FCH",
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/*
6062306a36Sopenharmony_ci * ELD SA bits in the CEA Speaker Allocation data block
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_cistatic const int eld_speaker_allocation_bits[] = {
6362306a36Sopenharmony_ci	[0] = FL | FR,
6462306a36Sopenharmony_ci	[1] = LFE,
6562306a36Sopenharmony_ci	[2] = FC,
6662306a36Sopenharmony_ci	[3] = RL | RR,
6762306a36Sopenharmony_ci	[4] = RC,
6862306a36Sopenharmony_ci	[5] = FLC | FRC,
6962306a36Sopenharmony_ci	[6] = RLC | RRC,
7062306a36Sopenharmony_ci	/* the following are not defined in ELD yet */
7162306a36Sopenharmony_ci	[7] = FLW | FRW,
7262306a36Sopenharmony_ci	[8] = FLH | FRH,
7362306a36Sopenharmony_ci	[9] = TC,
7462306a36Sopenharmony_ci	[10] = FCH,
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/*
7862306a36Sopenharmony_ci * ALSA sequence is:
7962306a36Sopenharmony_ci *
8062306a36Sopenharmony_ci *       surround40   surround41   surround50   surround51   surround71
8162306a36Sopenharmony_ci * ch0   front left   =            =            =            =
8262306a36Sopenharmony_ci * ch1   front right  =            =            =            =
8362306a36Sopenharmony_ci * ch2   rear left    =            =            =            =
8462306a36Sopenharmony_ci * ch3   rear right   =            =            =            =
8562306a36Sopenharmony_ci * ch4                LFE          center       center       center
8662306a36Sopenharmony_ci * ch5                                          LFE          LFE
8762306a36Sopenharmony_ci * ch6                                                       side left
8862306a36Sopenharmony_ci * ch7                                                       side right
8962306a36Sopenharmony_ci *
9062306a36Sopenharmony_ci * surround71 = {FL, FR, RLC, RRC, FC, LFE, RL, RR}
9162306a36Sopenharmony_ci */
9262306a36Sopenharmony_cistatic int hdmi_channel_mapping[0x32][8] = {
9362306a36Sopenharmony_ci	/* stereo */
9462306a36Sopenharmony_ci	[0x00] = { 0x00, 0x11, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
9562306a36Sopenharmony_ci	/* 2.1 */
9662306a36Sopenharmony_ci	[0x01] = { 0x00, 0x11, 0x22, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
9762306a36Sopenharmony_ci	/* Dolby Surround */
9862306a36Sopenharmony_ci	[0x02] = { 0x00, 0x11, 0x23, 0xf2, 0xf4, 0xf5, 0xf6, 0xf7 },
9962306a36Sopenharmony_ci	/* surround40 */
10062306a36Sopenharmony_ci	[0x08] = { 0x00, 0x11, 0x24, 0x35, 0xf3, 0xf2, 0xf6, 0xf7 },
10162306a36Sopenharmony_ci	/* 4ch */
10262306a36Sopenharmony_ci	[0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 },
10362306a36Sopenharmony_ci	/* surround41 */
10462306a36Sopenharmony_ci	[0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 },
10562306a36Sopenharmony_ci	/* surround50 */
10662306a36Sopenharmony_ci	[0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 },
10762306a36Sopenharmony_ci	/* surround51 */
10862306a36Sopenharmony_ci	[0x0b] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0x52, 0xf6, 0xf7 },
10962306a36Sopenharmony_ci	/* 7.1 */
11062306a36Sopenharmony_ci	[0x13] = { 0x00, 0x11, 0x26, 0x37, 0x43, 0x52, 0x64, 0x75 },
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci/*
11462306a36Sopenharmony_ci * This is an ordered list!
11562306a36Sopenharmony_ci *
11662306a36Sopenharmony_ci * The preceding ones have better chances to be selected by
11762306a36Sopenharmony_ci * hdmi_channel_allocation().
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_cistatic struct hdac_cea_channel_speaker_allocation channel_allocations[] = {
12062306a36Sopenharmony_ci/*			  channel:   7     6    5    4    3     2    1    0  */
12162306a36Sopenharmony_ci{ .ca_index = 0x00,  .speakers = {   0,    0,   0,   0,   0,    0,  FR,  FL } },
12262306a36Sopenharmony_ci				 /* 2.1 */
12362306a36Sopenharmony_ci{ .ca_index = 0x01,  .speakers = {   0,    0,   0,   0,   0,  LFE,  FR,  FL } },
12462306a36Sopenharmony_ci				 /* Dolby Surround */
12562306a36Sopenharmony_ci{ .ca_index = 0x02,  .speakers = {   0,    0,   0,   0,  FC,    0,  FR,  FL } },
12662306a36Sopenharmony_ci				 /* surround40 */
12762306a36Sopenharmony_ci{ .ca_index = 0x08,  .speakers = {   0,    0,  RR,  RL,   0,    0,  FR,  FL } },
12862306a36Sopenharmony_ci				 /* surround41 */
12962306a36Sopenharmony_ci{ .ca_index = 0x09,  .speakers = {   0,    0,  RR,  RL,   0,  LFE,  FR,  FL } },
13062306a36Sopenharmony_ci				 /* surround50 */
13162306a36Sopenharmony_ci{ .ca_index = 0x0a,  .speakers = {   0,    0,  RR,  RL,  FC,    0,  FR,  FL } },
13262306a36Sopenharmony_ci				 /* surround51 */
13362306a36Sopenharmony_ci{ .ca_index = 0x0b,  .speakers = {   0,    0,  RR,  RL,  FC,  LFE,  FR,  FL } },
13462306a36Sopenharmony_ci				 /* 6.1 */
13562306a36Sopenharmony_ci{ .ca_index = 0x0f,  .speakers = {   0,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
13662306a36Sopenharmony_ci				 /* surround71 */
13762306a36Sopenharmony_ci{ .ca_index = 0x13,  .speakers = { RRC,  RLC,  RR,  RL,  FC,  LFE,  FR,  FL } },
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci{ .ca_index = 0x03,  .speakers = {   0,    0,   0,   0,  FC,  LFE,  FR,  FL } },
14062306a36Sopenharmony_ci{ .ca_index = 0x04,  .speakers = {   0,    0,   0,  RC,   0,    0,  FR,  FL } },
14162306a36Sopenharmony_ci{ .ca_index = 0x05,  .speakers = {   0,    0,   0,  RC,   0,  LFE,  FR,  FL } },
14262306a36Sopenharmony_ci{ .ca_index = 0x06,  .speakers = {   0,    0,   0,  RC,  FC,    0,  FR,  FL } },
14362306a36Sopenharmony_ci{ .ca_index = 0x07,  .speakers = {   0,    0,   0,  RC,  FC,  LFE,  FR,  FL } },
14462306a36Sopenharmony_ci{ .ca_index = 0x0c,  .speakers = {   0,   RC,  RR,  RL,   0,    0,  FR,  FL } },
14562306a36Sopenharmony_ci{ .ca_index = 0x0d,  .speakers = {   0,   RC,  RR,  RL,   0,  LFE,  FR,  FL } },
14662306a36Sopenharmony_ci{ .ca_index = 0x0e,  .speakers = {   0,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
14762306a36Sopenharmony_ci{ .ca_index = 0x10,  .speakers = { RRC,  RLC,  RR,  RL,   0,    0,  FR,  FL } },
14862306a36Sopenharmony_ci{ .ca_index = 0x11,  .speakers = { RRC,  RLC,  RR,  RL,   0,  LFE,  FR,  FL } },
14962306a36Sopenharmony_ci{ .ca_index = 0x12,  .speakers = { RRC,  RLC,  RR,  RL,  FC,    0,  FR,  FL } },
15062306a36Sopenharmony_ci{ .ca_index = 0x14,  .speakers = { FRC,  FLC,   0,   0,   0,    0,  FR,  FL } },
15162306a36Sopenharmony_ci{ .ca_index = 0x15,  .speakers = { FRC,  FLC,   0,   0,   0,  LFE,  FR,  FL } },
15262306a36Sopenharmony_ci{ .ca_index = 0x16,  .speakers = { FRC,  FLC,   0,   0,  FC,    0,  FR,  FL } },
15362306a36Sopenharmony_ci{ .ca_index = 0x17,  .speakers = { FRC,  FLC,   0,   0,  FC,  LFE,  FR,  FL } },
15462306a36Sopenharmony_ci{ .ca_index = 0x18,  .speakers = { FRC,  FLC,   0,  RC,   0,    0,  FR,  FL } },
15562306a36Sopenharmony_ci{ .ca_index = 0x19,  .speakers = { FRC,  FLC,   0,  RC,   0,  LFE,  FR,  FL } },
15662306a36Sopenharmony_ci{ .ca_index = 0x1a,  .speakers = { FRC,  FLC,   0,  RC,  FC,    0,  FR,  FL } },
15762306a36Sopenharmony_ci{ .ca_index = 0x1b,  .speakers = { FRC,  FLC,   0,  RC,  FC,  LFE,  FR,  FL } },
15862306a36Sopenharmony_ci{ .ca_index = 0x1c,  .speakers = { FRC,  FLC,  RR,  RL,   0,    0,  FR,  FL } },
15962306a36Sopenharmony_ci{ .ca_index = 0x1d,  .speakers = { FRC,  FLC,  RR,  RL,   0,  LFE,  FR,  FL } },
16062306a36Sopenharmony_ci{ .ca_index = 0x1e,  .speakers = { FRC,  FLC,  RR,  RL,  FC,    0,  FR,  FL } },
16162306a36Sopenharmony_ci{ .ca_index = 0x1f,  .speakers = { FRC,  FLC,  RR,  RL,  FC,  LFE,  FR,  FL } },
16262306a36Sopenharmony_ci{ .ca_index = 0x20,  .speakers = {   0,  FCH,  RR,  RL,  FC,    0,  FR,  FL } },
16362306a36Sopenharmony_ci{ .ca_index = 0x21,  .speakers = {   0,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL } },
16462306a36Sopenharmony_ci{ .ca_index = 0x22,  .speakers = {  TC,    0,  RR,  RL,  FC,    0,  FR,  FL } },
16562306a36Sopenharmony_ci{ .ca_index = 0x23,  .speakers = {  TC,    0,  RR,  RL,  FC,  LFE,  FR,  FL } },
16662306a36Sopenharmony_ci{ .ca_index = 0x24,  .speakers = { FRH,  FLH,  RR,  RL,   0,    0,  FR,  FL } },
16762306a36Sopenharmony_ci{ .ca_index = 0x25,  .speakers = { FRH,  FLH,  RR,  RL,   0,  LFE,  FR,  FL } },
16862306a36Sopenharmony_ci{ .ca_index = 0x26,  .speakers = { FRW,  FLW,  RR,  RL,   0,    0,  FR,  FL } },
16962306a36Sopenharmony_ci{ .ca_index = 0x27,  .speakers = { FRW,  FLW,  RR,  RL,   0,  LFE,  FR,  FL } },
17062306a36Sopenharmony_ci{ .ca_index = 0x28,  .speakers = {  TC,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
17162306a36Sopenharmony_ci{ .ca_index = 0x29,  .speakers = {  TC,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
17262306a36Sopenharmony_ci{ .ca_index = 0x2a,  .speakers = { FCH,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
17362306a36Sopenharmony_ci{ .ca_index = 0x2b,  .speakers = { FCH,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
17462306a36Sopenharmony_ci{ .ca_index = 0x2c,  .speakers = {  TC,  FCH,  RR,  RL,  FC,    0,  FR,  FL } },
17562306a36Sopenharmony_ci{ .ca_index = 0x2d,  .speakers = {  TC,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL } },
17662306a36Sopenharmony_ci{ .ca_index = 0x2e,  .speakers = { FRH,  FLH,  RR,  RL,  FC,    0,  FR,  FL } },
17762306a36Sopenharmony_ci{ .ca_index = 0x2f,  .speakers = { FRH,  FLH,  RR,  RL,  FC,  LFE,  FR,  FL } },
17862306a36Sopenharmony_ci{ .ca_index = 0x30,  .speakers = { FRW,  FLW,  RR,  RL,  FC,    0,  FR,  FL } },
17962306a36Sopenharmony_ci{ .ca_index = 0x31,  .speakers = { FRW,  FLW,  RR,  RL,  FC,  LFE,  FR,  FL } },
18062306a36Sopenharmony_ci};
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic int hdmi_pin_set_slot_channel(struct hdac_device *codec,
18362306a36Sopenharmony_ci		hda_nid_t pin_nid, int asp_slot, int channel)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	return snd_hdac_codec_write(codec, pin_nid, 0,
18662306a36Sopenharmony_ci				AC_VERB_SET_HDMI_CHAN_SLOT,
18762306a36Sopenharmony_ci				(channel << 4) | asp_slot);
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic int hdmi_pin_get_slot_channel(struct hdac_device *codec,
19162306a36Sopenharmony_ci			hda_nid_t pin_nid, int asp_slot)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	return (snd_hdac_codec_read(codec, pin_nid, 0,
19462306a36Sopenharmony_ci				   AC_VERB_GET_HDMI_CHAN_SLOT,
19562306a36Sopenharmony_ci				   asp_slot) & 0xf0) >> 4;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int hdmi_get_channel_count(struct hdac_device *codec, hda_nid_t cvt_nid)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	return 1 + snd_hdac_codec_read(codec, cvt_nid, 0,
20162306a36Sopenharmony_ci					AC_VERB_GET_CVT_CHAN_COUNT, 0);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic void hdmi_set_channel_count(struct hdac_device *codec,
20562306a36Sopenharmony_ci				   hda_nid_t cvt_nid, int chs)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	if (chs != hdmi_get_channel_count(codec, cvt_nid))
20862306a36Sopenharmony_ci		snd_hdac_codec_write(codec, cvt_nid, 0,
20962306a36Sopenharmony_ci				    AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/*
21362306a36Sopenharmony_ci * Channel mapping routines
21462306a36Sopenharmony_ci */
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci/*
21762306a36Sopenharmony_ci * Compute derived values in channel_allocations[].
21862306a36Sopenharmony_ci */
21962306a36Sopenharmony_cistatic void init_channel_allocations(void)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	int i, j;
22262306a36Sopenharmony_ci	struct hdac_cea_channel_speaker_allocation *p;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
22562306a36Sopenharmony_ci		p = channel_allocations + i;
22662306a36Sopenharmony_ci		p->channels = 0;
22762306a36Sopenharmony_ci		p->spk_mask = 0;
22862306a36Sopenharmony_ci		for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
22962306a36Sopenharmony_ci			if (p->speakers[j]) {
23062306a36Sopenharmony_ci				p->channels++;
23162306a36Sopenharmony_ci				p->spk_mask |= p->speakers[j];
23262306a36Sopenharmony_ci			}
23362306a36Sopenharmony_ci	}
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int get_channel_allocation_order(int ca)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	int i;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
24162306a36Sopenharmony_ci		if (channel_allocations[i].ca_index == ca)
24262306a36Sopenharmony_ci			break;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci	return i;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_civoid snd_hdac_print_channel_allocation(int spk_alloc, char *buf, int buflen)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	int i, j;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) {
25262306a36Sopenharmony_ci		if (spk_alloc & (1 << i))
25362306a36Sopenharmony_ci			j += scnprintf(buf + j, buflen - j,  " %s",
25462306a36Sopenharmony_ci					cea_speaker_allocation_names[i]);
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci	buf[j] = '\0';	/* necessary when j == 0 */
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_print_channel_allocation);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci/*
26162306a36Sopenharmony_ci * The transformation takes two steps:
26262306a36Sopenharmony_ci *
26362306a36Sopenharmony_ci *	eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask
26462306a36Sopenharmony_ci *	      spk_mask => (channel_allocations[])         => ai->CA
26562306a36Sopenharmony_ci *
26662306a36Sopenharmony_ci * TODO: it could select the wrong CA from multiple candidates.
26762306a36Sopenharmony_ci*/
26862306a36Sopenharmony_cistatic int hdmi_channel_allocation_spk_alloc_blk(struct hdac_device *codec,
26962306a36Sopenharmony_ci				   int spk_alloc, int channels)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	int i;
27262306a36Sopenharmony_ci	int ca = 0;
27362306a36Sopenharmony_ci	int spk_mask = 0;
27462306a36Sopenharmony_ci	char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	/*
27762306a36Sopenharmony_ci	 * CA defaults to 0 for basic stereo audio
27862306a36Sopenharmony_ci	 */
27962306a36Sopenharmony_ci	if (channels <= 2)
28062306a36Sopenharmony_ci		return 0;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	/*
28362306a36Sopenharmony_ci	 * expand ELD's speaker allocation mask
28462306a36Sopenharmony_ci	 *
28562306a36Sopenharmony_ci	 * ELD tells the speaker mask in a compact(paired) form,
28662306a36Sopenharmony_ci	 * expand ELD's notions to match the ones used by Audio InfoFrame.
28762306a36Sopenharmony_ci	 */
28862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
28962306a36Sopenharmony_ci		if (spk_alloc & (1 << i))
29062306a36Sopenharmony_ci			spk_mask |= eld_speaker_allocation_bits[i];
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	/* search for the first working match in the CA table */
29462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
29562306a36Sopenharmony_ci		if (channels == channel_allocations[i].channels &&
29662306a36Sopenharmony_ci		    (spk_mask & channel_allocations[i].spk_mask) ==
29762306a36Sopenharmony_ci				channel_allocations[i].spk_mask) {
29862306a36Sopenharmony_ci			ca = channel_allocations[i].ca_index;
29962306a36Sopenharmony_ci			break;
30062306a36Sopenharmony_ci		}
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (!ca) {
30462306a36Sopenharmony_ci		/*
30562306a36Sopenharmony_ci		 * if there was no match, select the regular ALSA channel
30662306a36Sopenharmony_ci		 * allocation with the matching number of channels
30762306a36Sopenharmony_ci		 */
30862306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
30962306a36Sopenharmony_ci			if (channels == channel_allocations[i].channels) {
31062306a36Sopenharmony_ci				ca = channel_allocations[i].ca_index;
31162306a36Sopenharmony_ci				break;
31262306a36Sopenharmony_ci			}
31362306a36Sopenharmony_ci		}
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	snd_hdac_print_channel_allocation(spk_alloc, buf, sizeof(buf));
31762306a36Sopenharmony_ci	dev_dbg(&codec->dev, "HDMI: select CA 0x%x for %d-channel allocation: %s\n",
31862306a36Sopenharmony_ci		    ca, channels, buf);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	return ca;
32162306a36Sopenharmony_ci}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic void hdmi_debug_channel_mapping(struct hdac_chmap *chmap,
32462306a36Sopenharmony_ci				       hda_nid_t pin_nid)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci#ifdef CONFIG_SND_DEBUG_VERBOSE
32762306a36Sopenharmony_ci	int i;
32862306a36Sopenharmony_ci	int channel;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
33162306a36Sopenharmony_ci		channel = chmap->ops.pin_get_slot_channel(
33262306a36Sopenharmony_ci				chmap->hdac, pin_nid, i);
33362306a36Sopenharmony_ci		dev_dbg(&chmap->hdac->dev, "HDMI: ASP channel %d => slot %d\n",
33462306a36Sopenharmony_ci						channel, i);
33562306a36Sopenharmony_ci	}
33662306a36Sopenharmony_ci#endif
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cistatic void hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap,
34062306a36Sopenharmony_ci				       hda_nid_t pin_nid,
34162306a36Sopenharmony_ci				       bool non_pcm,
34262306a36Sopenharmony_ci				       int ca)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct hdac_cea_channel_speaker_allocation *ch_alloc;
34562306a36Sopenharmony_ci	int i;
34662306a36Sopenharmony_ci	int err;
34762306a36Sopenharmony_ci	int order;
34862306a36Sopenharmony_ci	int non_pcm_mapping[8];
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	order = get_channel_allocation_order(ca);
35162306a36Sopenharmony_ci	ch_alloc = &channel_allocations[order];
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	if (hdmi_channel_mapping[ca][1] == 0) {
35462306a36Sopenharmony_ci		int hdmi_slot = 0;
35562306a36Sopenharmony_ci		/* fill actual channel mappings in ALSA channel (i) order */
35662306a36Sopenharmony_ci		for (i = 0; i < ch_alloc->channels; i++) {
35762306a36Sopenharmony_ci			while (!WARN_ON(hdmi_slot >= 8) &&
35862306a36Sopenharmony_ci			       !ch_alloc->speakers[7 - hdmi_slot])
35962306a36Sopenharmony_ci				hdmi_slot++; /* skip zero slots */
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci			hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++;
36262306a36Sopenharmony_ci		}
36362306a36Sopenharmony_ci		/* fill the rest of the slots with ALSA channel 0xf */
36462306a36Sopenharmony_ci		for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++)
36562306a36Sopenharmony_ci			if (!ch_alloc->speakers[7 - hdmi_slot])
36662306a36Sopenharmony_ci				hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (non_pcm) {
37062306a36Sopenharmony_ci		for (i = 0; i < ch_alloc->channels; i++)
37162306a36Sopenharmony_ci			non_pcm_mapping[i] = (i << 4) | i;
37262306a36Sopenharmony_ci		for (; i < 8; i++)
37362306a36Sopenharmony_ci			non_pcm_mapping[i] = (0xf << 4) | i;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
37762306a36Sopenharmony_ci		int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i];
37862306a36Sopenharmony_ci		int hdmi_slot = slotsetup & 0x0f;
37962306a36Sopenharmony_ci		int channel = (slotsetup & 0xf0) >> 4;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		err = chmap->ops.pin_set_slot_channel(chmap->hdac,
38262306a36Sopenharmony_ci				pin_nid, hdmi_slot, channel);
38362306a36Sopenharmony_ci		if (err) {
38462306a36Sopenharmony_ci			dev_dbg(&chmap->hdac->dev, "HDMI: channel mapping failed\n");
38562306a36Sopenharmony_ci			break;
38662306a36Sopenharmony_ci		}
38762306a36Sopenharmony_ci	}
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_cistruct channel_map_table {
39162306a36Sopenharmony_ci	unsigned char map;		/* ALSA API channel map position */
39262306a36Sopenharmony_ci	int spk_mask;			/* speaker position bit mask */
39362306a36Sopenharmony_ci};
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic struct channel_map_table map_tables[] = {
39662306a36Sopenharmony_ci	{ SNDRV_CHMAP_FL,	FL },
39762306a36Sopenharmony_ci	{ SNDRV_CHMAP_FR,	FR },
39862306a36Sopenharmony_ci	{ SNDRV_CHMAP_RL,	RL },
39962306a36Sopenharmony_ci	{ SNDRV_CHMAP_RR,	RR },
40062306a36Sopenharmony_ci	{ SNDRV_CHMAP_LFE,	LFE },
40162306a36Sopenharmony_ci	{ SNDRV_CHMAP_FC,	FC },
40262306a36Sopenharmony_ci	{ SNDRV_CHMAP_RLC,	RLC },
40362306a36Sopenharmony_ci	{ SNDRV_CHMAP_RRC,	RRC },
40462306a36Sopenharmony_ci	{ SNDRV_CHMAP_RC,	RC },
40562306a36Sopenharmony_ci	{ SNDRV_CHMAP_FLC,	FLC },
40662306a36Sopenharmony_ci	{ SNDRV_CHMAP_FRC,	FRC },
40762306a36Sopenharmony_ci	{ SNDRV_CHMAP_TFL,	FLH },
40862306a36Sopenharmony_ci	{ SNDRV_CHMAP_TFR,	FRH },
40962306a36Sopenharmony_ci	{ SNDRV_CHMAP_FLW,	FLW },
41062306a36Sopenharmony_ci	{ SNDRV_CHMAP_FRW,	FRW },
41162306a36Sopenharmony_ci	{ SNDRV_CHMAP_TC,	TC },
41262306a36Sopenharmony_ci	{ SNDRV_CHMAP_TFC,	FCH },
41362306a36Sopenharmony_ci	{} /* terminator */
41462306a36Sopenharmony_ci};
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci/* from ALSA API channel position to speaker bit mask */
41762306a36Sopenharmony_ciint snd_hdac_chmap_to_spk_mask(unsigned char c)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	struct channel_map_table *t = map_tables;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	for (; t->map; t++) {
42262306a36Sopenharmony_ci		if (t->map == c)
42362306a36Sopenharmony_ci			return t->spk_mask;
42462306a36Sopenharmony_ci	}
42562306a36Sopenharmony_ci	return 0;
42662306a36Sopenharmony_ci}
42762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_chmap_to_spk_mask);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci/* from ALSA API channel position to CEA slot */
43062306a36Sopenharmony_cistatic int to_cea_slot(int ordered_ca, unsigned char pos)
43162306a36Sopenharmony_ci{
43262306a36Sopenharmony_ci	int mask = snd_hdac_chmap_to_spk_mask(pos);
43362306a36Sopenharmony_ci	int i;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* Add sanity check to pass klockwork check.
43662306a36Sopenharmony_ci	 * This should never happen.
43762306a36Sopenharmony_ci	 */
43862306a36Sopenharmony_ci	if (ordered_ca >= ARRAY_SIZE(channel_allocations))
43962306a36Sopenharmony_ci		return -1;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	if (mask) {
44262306a36Sopenharmony_ci		for (i = 0; i < 8; i++) {
44362306a36Sopenharmony_ci			if (channel_allocations[ordered_ca].speakers[7 - i] == mask)
44462306a36Sopenharmony_ci				return i;
44562306a36Sopenharmony_ci		}
44662306a36Sopenharmony_ci	}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	return -1;
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci/* from speaker bit mask to ALSA API channel position */
45262306a36Sopenharmony_ciint snd_hdac_spk_to_chmap(int spk)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	struct channel_map_table *t = map_tables;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	for (; t->map; t++) {
45762306a36Sopenharmony_ci		if (t->spk_mask == spk)
45862306a36Sopenharmony_ci			return t->map;
45962306a36Sopenharmony_ci	}
46062306a36Sopenharmony_ci	return 0;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci/* from CEA slot to ALSA API channel position */
46562306a36Sopenharmony_cistatic int from_cea_slot(int ordered_ca, unsigned char slot)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	int mask;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	/* Add sanity check to pass klockwork check.
47062306a36Sopenharmony_ci	 * This should never happen.
47162306a36Sopenharmony_ci	 */
47262306a36Sopenharmony_ci	if (slot >= 8)
47362306a36Sopenharmony_ci		return 0;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	mask = channel_allocations[ordered_ca].speakers[7 - slot];
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	return snd_hdac_spk_to_chmap(mask);
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci/* get the CA index corresponding to the given ALSA API channel map */
48162306a36Sopenharmony_cistatic int hdmi_manual_channel_allocation(int chs, unsigned char *map)
48262306a36Sopenharmony_ci{
48362306a36Sopenharmony_ci	int i, spks = 0, spk_mask = 0;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	for (i = 0; i < chs; i++) {
48662306a36Sopenharmony_ci		int mask = snd_hdac_chmap_to_spk_mask(map[i]);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci		if (mask) {
48962306a36Sopenharmony_ci			spk_mask |= mask;
49062306a36Sopenharmony_ci			spks++;
49162306a36Sopenharmony_ci		}
49262306a36Sopenharmony_ci	}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
49562306a36Sopenharmony_ci		if ((chs == channel_allocations[i].channels ||
49662306a36Sopenharmony_ci		     spks == channel_allocations[i].channels) &&
49762306a36Sopenharmony_ci		    (spk_mask & channel_allocations[i].spk_mask) ==
49862306a36Sopenharmony_ci				channel_allocations[i].spk_mask)
49962306a36Sopenharmony_ci			return channel_allocations[i].ca_index;
50062306a36Sopenharmony_ci	}
50162306a36Sopenharmony_ci	return -1;
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci/* set up the channel slots for the given ALSA API channel map */
50562306a36Sopenharmony_cistatic int hdmi_manual_setup_channel_mapping(struct hdac_chmap *chmap,
50662306a36Sopenharmony_ci					     hda_nid_t pin_nid,
50762306a36Sopenharmony_ci					     int chs, unsigned char *map,
50862306a36Sopenharmony_ci					     int ca)
50962306a36Sopenharmony_ci{
51062306a36Sopenharmony_ci	int ordered_ca = get_channel_allocation_order(ca);
51162306a36Sopenharmony_ci	int alsa_pos, hdmi_slot;
51262306a36Sopenharmony_ci	int assignments[8] = {[0 ... 7] = 0xf};
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	for (alsa_pos = 0; alsa_pos < chs; alsa_pos++) {
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci		hdmi_slot = to_cea_slot(ordered_ca, map[alsa_pos]);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci		if (hdmi_slot < 0)
51962306a36Sopenharmony_ci			continue; /* unassigned channel */
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci		assignments[hdmi_slot] = alsa_pos;
52262306a36Sopenharmony_ci	}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) {
52562306a36Sopenharmony_ci		int err;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci		err = chmap->ops.pin_set_slot_channel(chmap->hdac,
52862306a36Sopenharmony_ci				pin_nid, hdmi_slot, assignments[hdmi_slot]);
52962306a36Sopenharmony_ci		if (err)
53062306a36Sopenharmony_ci			return -EINVAL;
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci	return 0;
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci/* store ALSA API channel map from the current default map */
53662306a36Sopenharmony_cistatic void hdmi_setup_fake_chmap(unsigned char *map, int ca)
53762306a36Sopenharmony_ci{
53862306a36Sopenharmony_ci	int i;
53962306a36Sopenharmony_ci	int ordered_ca = get_channel_allocation_order(ca);
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
54262306a36Sopenharmony_ci		if (ordered_ca < ARRAY_SIZE(channel_allocations) &&
54362306a36Sopenharmony_ci		    i < channel_allocations[ordered_ca].channels)
54462306a36Sopenharmony_ci			map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f);
54562306a36Sopenharmony_ci		else
54662306a36Sopenharmony_ci			map[i] = 0;
54762306a36Sopenharmony_ci	}
54862306a36Sopenharmony_ci}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_civoid snd_hdac_setup_channel_mapping(struct hdac_chmap *chmap,
55162306a36Sopenharmony_ci				       hda_nid_t pin_nid, bool non_pcm, int ca,
55262306a36Sopenharmony_ci				       int channels, unsigned char *map,
55362306a36Sopenharmony_ci				       bool chmap_set)
55462306a36Sopenharmony_ci{
55562306a36Sopenharmony_ci	if (!non_pcm && chmap_set) {
55662306a36Sopenharmony_ci		hdmi_manual_setup_channel_mapping(chmap, pin_nid,
55762306a36Sopenharmony_ci						  channels, map, ca);
55862306a36Sopenharmony_ci	} else {
55962306a36Sopenharmony_ci		hdmi_std_setup_channel_mapping(chmap, pin_nid, non_pcm, ca);
56062306a36Sopenharmony_ci		hdmi_setup_fake_chmap(map, ca);
56162306a36Sopenharmony_ci	}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	hdmi_debug_channel_mapping(chmap, pin_nid);
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_setup_channel_mapping);
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ciint snd_hdac_get_active_channels(int ca)
56862306a36Sopenharmony_ci{
56962306a36Sopenharmony_ci	int ordered_ca = get_channel_allocation_order(ca);
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	/* Add sanity check to pass klockwork check.
57262306a36Sopenharmony_ci	 * This should never happen.
57362306a36Sopenharmony_ci	 */
57462306a36Sopenharmony_ci	if (ordered_ca >= ARRAY_SIZE(channel_allocations))
57562306a36Sopenharmony_ci		ordered_ca = 0;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	return channel_allocations[ordered_ca].channels;
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_get_active_channels);
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_cistruct hdac_cea_channel_speaker_allocation *snd_hdac_get_ch_alloc_from_ca(int ca)
58262306a36Sopenharmony_ci{
58362306a36Sopenharmony_ci	return &channel_allocations[get_channel_allocation_order(ca)];
58462306a36Sopenharmony_ci}
58562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_get_ch_alloc_from_ca);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ciint snd_hdac_channel_allocation(struct hdac_device *hdac, int spk_alloc,
58862306a36Sopenharmony_ci		int channels, bool chmap_set, bool non_pcm, unsigned char *map)
58962306a36Sopenharmony_ci{
59062306a36Sopenharmony_ci	int ca;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	if (!non_pcm && chmap_set)
59362306a36Sopenharmony_ci		ca = hdmi_manual_channel_allocation(channels, map);
59462306a36Sopenharmony_ci	else
59562306a36Sopenharmony_ci		ca = hdmi_channel_allocation_spk_alloc_blk(hdac,
59662306a36Sopenharmony_ci					spk_alloc, channels);
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	if (ca < 0)
59962306a36Sopenharmony_ci		ca = 0;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	return ca;
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_channel_allocation);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci/*
60662306a36Sopenharmony_ci * ALSA API channel-map control callbacks
60762306a36Sopenharmony_ci */
60862306a36Sopenharmony_cistatic int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
60962306a36Sopenharmony_ci			       struct snd_ctl_elem_info *uinfo)
61062306a36Sopenharmony_ci{
61162306a36Sopenharmony_ci	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
61262306a36Sopenharmony_ci	struct hdac_chmap *chmap = info->private_data;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
61562306a36Sopenharmony_ci	uinfo->count = chmap->channels_max;
61662306a36Sopenharmony_ci	uinfo->value.integer.min = 0;
61762306a36Sopenharmony_ci	uinfo->value.integer.max = SNDRV_CHMAP_LAST;
61862306a36Sopenharmony_ci	return 0;
61962306a36Sopenharmony_ci}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_cistatic int hdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap,
62262306a36Sopenharmony_ci		struct hdac_cea_channel_speaker_allocation *cap, int channels)
62362306a36Sopenharmony_ci{
62462306a36Sopenharmony_ci	/* If the speaker allocation matches the channel count, it is OK.*/
62562306a36Sopenharmony_ci	if (cap->channels != channels)
62662306a36Sopenharmony_ci		return -1;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	/* all channels are remappable freely */
62962306a36Sopenharmony_ci	return SNDRV_CTL_TLVT_CHMAP_VAR;
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
63362306a36Sopenharmony_ci		struct hdac_cea_channel_speaker_allocation *cap,
63462306a36Sopenharmony_ci		unsigned int *chmap, int channels)
63562306a36Sopenharmony_ci{
63662306a36Sopenharmony_ci	int count = 0;
63762306a36Sopenharmony_ci	int c;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	for (c = 7; c >= 0; c--) {
64062306a36Sopenharmony_ci		int spk = cap->speakers[c];
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci		if (!spk)
64362306a36Sopenharmony_ci			continue;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci		chmap[count++] = snd_hdac_spk_to_chmap(spk);
64662306a36Sopenharmony_ci	}
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	WARN_ON(count != channels);
64962306a36Sopenharmony_ci}
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_cistatic int spk_mask_from_spk_alloc(int spk_alloc)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	int i;
65462306a36Sopenharmony_ci	int spk_mask = eld_speaker_allocation_bits[0];
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
65762306a36Sopenharmony_ci		if (spk_alloc & (1 << i))
65862306a36Sopenharmony_ci			spk_mask |= eld_speaker_allocation_bits[i];
65962306a36Sopenharmony_ci	}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	return spk_mask;
66262306a36Sopenharmony_ci}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_cistatic int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
66562306a36Sopenharmony_ci			      unsigned int size, unsigned int __user *tlv)
66662306a36Sopenharmony_ci{
66762306a36Sopenharmony_ci	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
66862306a36Sopenharmony_ci	struct hdac_chmap *chmap = info->private_data;
66962306a36Sopenharmony_ci	int pcm_idx = kcontrol->private_value;
67062306a36Sopenharmony_ci	unsigned int __user *dst;
67162306a36Sopenharmony_ci	int chs, count = 0;
67262306a36Sopenharmony_ci	unsigned long max_chs;
67362306a36Sopenharmony_ci	int type;
67462306a36Sopenharmony_ci	int spk_alloc, spk_mask;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	if (size < 8)
67762306a36Sopenharmony_ci		return -ENOMEM;
67862306a36Sopenharmony_ci	if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
67962306a36Sopenharmony_ci		return -EFAULT;
68062306a36Sopenharmony_ci	size -= 8;
68162306a36Sopenharmony_ci	dst = tlv + 2;
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_ci	spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx);
68462306a36Sopenharmony_ci	spk_mask = spk_mask_from_spk_alloc(spk_alloc);
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	max_chs = hweight_long(spk_mask);
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	for (chs = 2; chs <= max_chs; chs++) {
68962306a36Sopenharmony_ci		int i;
69062306a36Sopenharmony_ci		struct hdac_cea_channel_speaker_allocation *cap;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci		cap = channel_allocations;
69362306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
69462306a36Sopenharmony_ci			int chs_bytes = chs * 4;
69562306a36Sopenharmony_ci			unsigned int tlv_chmap[8];
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci			if (cap->channels != chs)
69862306a36Sopenharmony_ci				continue;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci			if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
70162306a36Sopenharmony_ci				continue;
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci			type = chmap->ops.chmap_cea_alloc_validate_get_type(
70462306a36Sopenharmony_ci							chmap, cap, chs);
70562306a36Sopenharmony_ci			if (type < 0)
70662306a36Sopenharmony_ci				return -ENODEV;
70762306a36Sopenharmony_ci			if (size < 8)
70862306a36Sopenharmony_ci				return -ENOMEM;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci			if (put_user(type, dst) ||
71162306a36Sopenharmony_ci			    put_user(chs_bytes, dst + 1))
71262306a36Sopenharmony_ci				return -EFAULT;
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci			dst += 2;
71562306a36Sopenharmony_ci			size -= 8;
71662306a36Sopenharmony_ci			count += 8;
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci			if (size < chs_bytes)
71962306a36Sopenharmony_ci				return -ENOMEM;
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci			size -= chs_bytes;
72262306a36Sopenharmony_ci			count += chs_bytes;
72362306a36Sopenharmony_ci			chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap,
72462306a36Sopenharmony_ci						tlv_chmap, chs);
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci			if (copy_to_user(dst, tlv_chmap, chs_bytes))
72762306a36Sopenharmony_ci				return -EFAULT;
72862306a36Sopenharmony_ci			dst += chs;
72962306a36Sopenharmony_ci		}
73062306a36Sopenharmony_ci	}
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci	if (put_user(count, tlv + 1))
73362306a36Sopenharmony_ci		return -EFAULT;
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci	return 0;
73662306a36Sopenharmony_ci}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_cistatic int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol,
73962306a36Sopenharmony_ci			      struct snd_ctl_elem_value *ucontrol)
74062306a36Sopenharmony_ci{
74162306a36Sopenharmony_ci	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
74262306a36Sopenharmony_ci	struct hdac_chmap *chmap = info->private_data;
74362306a36Sopenharmony_ci	int pcm_idx = kcontrol->private_value;
74462306a36Sopenharmony_ci	unsigned char pcm_chmap[8];
74562306a36Sopenharmony_ci	int i;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	memset(pcm_chmap, 0, sizeof(pcm_chmap));
74862306a36Sopenharmony_ci	chmap->ops.get_chmap(chmap->hdac, pcm_idx, pcm_chmap);
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(pcm_chmap); i++)
75162306a36Sopenharmony_ci		ucontrol->value.integer.value[i] = pcm_chmap[i];
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	return 0;
75462306a36Sopenharmony_ci}
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_cistatic int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
75762306a36Sopenharmony_ci			      struct snd_ctl_elem_value *ucontrol)
75862306a36Sopenharmony_ci{
75962306a36Sopenharmony_ci	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
76062306a36Sopenharmony_ci	struct hdac_chmap *hchmap = info->private_data;
76162306a36Sopenharmony_ci	int pcm_idx = kcontrol->private_value;
76262306a36Sopenharmony_ci	unsigned int ctl_idx;
76362306a36Sopenharmony_ci	struct snd_pcm_substream *substream;
76462306a36Sopenharmony_ci	unsigned char chmap[8], per_pin_chmap[8];
76562306a36Sopenharmony_ci	int i, err, ca, prepared = 0;
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	/* No monitor is connected in dyn_pcm_assign.
76862306a36Sopenharmony_ci	 * It's invalid to setup the chmap
76962306a36Sopenharmony_ci	 */
77062306a36Sopenharmony_ci	if (!hchmap->ops.is_pcm_attached(hchmap->hdac, pcm_idx))
77162306a36Sopenharmony_ci		return 0;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
77462306a36Sopenharmony_ci	substream = snd_pcm_chmap_substream(info, ctl_idx);
77562306a36Sopenharmony_ci	if (!substream || !substream->runtime)
77662306a36Sopenharmony_ci		return 0; /* just for avoiding error from alsactl restore */
77762306a36Sopenharmony_ci	switch (substream->runtime->state) {
77862306a36Sopenharmony_ci	case SNDRV_PCM_STATE_OPEN:
77962306a36Sopenharmony_ci	case SNDRV_PCM_STATE_SETUP:
78062306a36Sopenharmony_ci		break;
78162306a36Sopenharmony_ci	case SNDRV_PCM_STATE_PREPARED:
78262306a36Sopenharmony_ci		prepared = 1;
78362306a36Sopenharmony_ci		break;
78462306a36Sopenharmony_ci	default:
78562306a36Sopenharmony_ci		return -EBUSY;
78662306a36Sopenharmony_ci	}
78762306a36Sopenharmony_ci	memset(chmap, 0, sizeof(chmap));
78862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(chmap); i++)
78962306a36Sopenharmony_ci		chmap[i] = ucontrol->value.integer.value[i];
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	hchmap->ops.get_chmap(hchmap->hdac, pcm_idx, per_pin_chmap);
79262306a36Sopenharmony_ci	if (!memcmp(chmap, per_pin_chmap, sizeof(chmap)))
79362306a36Sopenharmony_ci		return 0;
79462306a36Sopenharmony_ci	ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
79562306a36Sopenharmony_ci	if (ca < 0)
79662306a36Sopenharmony_ci		return -EINVAL;
79762306a36Sopenharmony_ci	if (hchmap->ops.chmap_validate) {
79862306a36Sopenharmony_ci		err = hchmap->ops.chmap_validate(hchmap, ca,
79962306a36Sopenharmony_ci				ARRAY_SIZE(chmap), chmap);
80062306a36Sopenharmony_ci		if (err)
80162306a36Sopenharmony_ci			return err;
80262306a36Sopenharmony_ci	}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	hchmap->ops.set_chmap(hchmap->hdac, pcm_idx, chmap, prepared);
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci	return 0;
80762306a36Sopenharmony_ci}
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_cistatic const struct hdac_chmap_ops chmap_ops = {
81062306a36Sopenharmony_ci	.chmap_cea_alloc_validate_get_type	= hdmi_chmap_cea_alloc_validate_get_type,
81162306a36Sopenharmony_ci	.cea_alloc_to_tlv_chmap			= hdmi_cea_alloc_to_tlv_chmap,
81262306a36Sopenharmony_ci	.pin_get_slot_channel			= hdmi_pin_get_slot_channel,
81362306a36Sopenharmony_ci	.pin_set_slot_channel			= hdmi_pin_set_slot_channel,
81462306a36Sopenharmony_ci	.set_channel_count			= hdmi_set_channel_count,
81562306a36Sopenharmony_ci};
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_civoid snd_hdac_register_chmap_ops(struct hdac_device *hdac,
81862306a36Sopenharmony_ci				struct hdac_chmap *chmap)
81962306a36Sopenharmony_ci{
82062306a36Sopenharmony_ci	chmap->ops = chmap_ops;
82162306a36Sopenharmony_ci	chmap->hdac = hdac;
82262306a36Sopenharmony_ci	init_channel_allocations();
82362306a36Sopenharmony_ci}
82462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_register_chmap_ops);
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ciint snd_hdac_add_chmap_ctls(struct snd_pcm *pcm, int pcm_idx,
82762306a36Sopenharmony_ci				struct hdac_chmap *hchmap)
82862306a36Sopenharmony_ci{
82962306a36Sopenharmony_ci	struct snd_pcm_chmap *chmap;
83062306a36Sopenharmony_ci	struct snd_kcontrol *kctl;
83162306a36Sopenharmony_ci	int err, i;
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_ci	err = snd_pcm_add_chmap_ctls(pcm,
83462306a36Sopenharmony_ci				     SNDRV_PCM_STREAM_PLAYBACK,
83562306a36Sopenharmony_ci				     NULL, 0, pcm_idx, &chmap);
83662306a36Sopenharmony_ci	if (err < 0)
83762306a36Sopenharmony_ci		return err;
83862306a36Sopenharmony_ci	/* override handlers */
83962306a36Sopenharmony_ci	chmap->private_data = hchmap;
84062306a36Sopenharmony_ci	kctl = chmap->kctl;
84162306a36Sopenharmony_ci	for (i = 0; i < kctl->count; i++)
84262306a36Sopenharmony_ci		kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
84362306a36Sopenharmony_ci	kctl->info = hdmi_chmap_ctl_info;
84462306a36Sopenharmony_ci	kctl->get = hdmi_chmap_ctl_get;
84562306a36Sopenharmony_ci	kctl->put = hdmi_chmap_ctl_put;
84662306a36Sopenharmony_ci	kctl->tlv.c = hdmi_chmap_ctl_tlv;
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	return 0;
84962306a36Sopenharmony_ci}
85062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_add_chmap_ctls);
851