/* * 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 corresponds to * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC.
*/ enum cea_speaker_placement {
FL = (1 << 0), /* Front Left */
FC = (1 << 1), /* Front Center */
FR = (1 << 2), /* Front Right */
FLC = (1 << 3), /* Front Left Center */
FRC = (1 << 4), /* Front Right Center */
RL = (1 << 5), /* Rear Left */
RC = (1 << 6), /* Rear Center */
RR = (1 << 7), /* Rear Right */
RLC = (1 << 8), /* Rear Left Center */
RRC = (1 << 9), /* Rear Right Center */
LFE = (1 << 10), /* Low Frequency Effect */
FLW = (1 << 11), /* Front Left Wide */
FRW = (1 << 12), /* Front Right Wide */
FLH = (1 << 13), /* Front Left High */
FCH = (1 << 14), /* Front Center High */
FRH = (1 << 15), /* Front Right High */
TC = (1 << 16), /* Top Center */
};
/* * Compute derived values in channel_allocations[].
*/ staticvoid init_channel_allocations(void)
{ int i, j; struct hdac_cea_channel_speaker_allocation *p;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
p = channel_allocations + i;
p->channels = 0;
p->spk_mask = 0; for (j = 0; j < ARRAY_SIZE(p->speakers); j++) if (p->speakers[j]) {
p->channels++;
p->spk_mask |= p->speakers[j];
}
}
}
staticint get_channel_allocation_order(int ca)
{ int i;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if (channel_allocations[i].ca_index == ca) break;
} return i;
}
void snd_hdac_print_channel_allocation(int spk_alloc, char *buf, int buflen)
{ int i, j;
for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) { if (spk_alloc & (1 << i))
j += scnprintf(buf + j, buflen - j, " %s",
cea_speaker_allocation_names[i]);
}
buf[j] = '\0'; /* necessary when j == 0 */
}
EXPORT_SYMBOL_GPL(snd_hdac_print_channel_allocation);
/* * The transformation takes two steps: * * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask * spk_mask => (channel_allocations[]) => ai->CA * * TODO: it could select the wrong CA from multiple candidates.
*/ staticint hdmi_channel_allocation_spk_alloc_blk(struct hdac_device *codec, int spk_alloc, int channels)
{ int i; int ca = 0; int spk_mask = 0; char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
/* * CA defaults to 0 for basic stereo audio
*/ if (channels <= 2) return 0;
/* * expand ELD's speaker allocation mask * * ELD tells the speaker mask in a compact(paired) form, * expand ELD's notions to match the ones used by Audio InfoFrame.
*/ for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { if (spk_alloc & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
/* search for the first working match in the CA table */ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if (channels == channel_allocations[i].channels &&
(spk_mask & channel_allocations[i].spk_mask) ==
channel_allocations[i].spk_mask) {
ca = channel_allocations[i].ca_index; break;
}
}
if (!ca) { /* * if there was no match, select the regular ALSA channel * allocation with the matching number of channels
*/ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if (channels == channel_allocations[i].channels) {
ca = channel_allocations[i].ca_index; break;
}
}
}
snd_hdac_print_channel_allocation(spk_alloc, buf, sizeof(buf));
dev_dbg(&codec->dev, "HDMI: select CA 0x%x for %d-channel allocation: %s\n",
ca, channels, buf);
return ca;
}
staticvoid hdmi_debug_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid)
{ #ifdef CONFIG_SND_DEBUG_VERBOSE int i; int channel;
for (i = 0; i < 8; i++) {
channel = chmap->ops.pin_get_slot_channel(
chmap->hdac, pin_nid, i);
dev_dbg(&chmap->hdac->dev, "HDMI: ASP channel %d => slot %d\n",
channel, i);
} #endif
}
staticvoid hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid, bool non_pcm, int ca)
{ struct hdac_cea_channel_speaker_allocation *ch_alloc; int i; int err; int order; int non_pcm_mapping[8];
order = get_channel_allocation_order(ca);
ch_alloc = &channel_allocations[order];
if (hdmi_channel_mapping[ca][1] == 0) { int hdmi_slot = 0; /* fill actual channel mappings in ALSA channel (i) order */ for (i = 0; i < ch_alloc->channels; i++) { while (!WARN_ON(hdmi_slot >= 8) &&
!ch_alloc->speakers[7 - hdmi_slot])
hdmi_slot++; /* skip zero slots */
hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++;
} /* fill the rest of the slots with ALSA channel 0xf */ for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) if (!ch_alloc->speakers[7 - hdmi_slot])
hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot;
}
if (non_pcm) { for (i = 0; i < ch_alloc->channels; i++)
non_pcm_mapping[i] = (i << 4) | i; for (; i < 8; i++)
non_pcm_mapping[i] = (0xf << 4) | i;
}
for (i = 0; i < 8; i++) { int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i]; int hdmi_slot = slotsetup & 0x0f; int channel = (slotsetup & 0xf0) >> 4;
/* from ALSA API channel position to speaker bit mask */ int snd_hdac_chmap_to_spk_mask(unsignedchar c)
{ struct channel_map_table *t = map_tables;
for (; t->map; t++) { if (t->map == c) return t->spk_mask;
} return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_chmap_to_spk_mask);
/* from ALSA API channel position to CEA slot */ staticint to_cea_slot(int ordered_ca, unsignedchar pos)
{ int mask = snd_hdac_chmap_to_spk_mask(pos); int i;
/* Add sanity check to pass klockwork check. * This should never happen.
*/ if (ordered_ca >= ARRAY_SIZE(channel_allocations)) return -1;
if (mask) { for (i = 0; i < 8; i++) { if (channel_allocations[ordered_ca].speakers[7 - i] == mask) return i;
}
}
return -1;
}
/* from speaker bit mask to ALSA API channel position */ int snd_hdac_spk_to_chmap(int spk)
{ struct channel_map_table *t = map_tables;
for (; t->map; t++) { if (t->spk_mask == spk) return t->map;
} return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap);
/* from CEA slot to ALSA API channel position */ staticint from_cea_slot(int ordered_ca, unsignedchar slot)
{ int mask;
/* Add sanity check to pass klockwork check. * This should never happen.
*/ if (slot >= 8) return 0;
/* get the CA index corresponding to the given ALSA API channel map */ staticint hdmi_manual_channel_allocation(int chs, unsignedchar *map)
{ int i, spks = 0, spk_mask = 0;
for (i = 0; i < chs; i++) { int mask = snd_hdac_chmap_to_spk_mask(map[i]);
if (mask) {
spk_mask |= mask;
spks++;
}
}
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if ((chs == channel_allocations[i].channels ||
spks == channel_allocations[i].channels) &&
(spk_mask & channel_allocations[i].spk_mask) ==
channel_allocations[i].spk_mask) return channel_allocations[i].ca_index;
} return -1;
}
/* set up the channel slots for the given ALSA API channel map */ staticint hdmi_manual_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid, int chs, unsignedchar *map, int ca)
{ int ordered_ca = get_channel_allocation_order(ca); int alsa_pos, hdmi_slot; int assignments[8] = {[0 ... 7] = 0xf};
/* store ALSA API channel map from the current default map */ staticvoid hdmi_setup_fake_chmap(unsignedchar *map, int ca)
{ int i; int ordered_ca = get_channel_allocation_order(ca);
for (i = 0; i < 8; i++) { if (ordered_ca < ARRAY_SIZE(channel_allocations) &&
i < channel_allocations[ordered_ca].channels)
map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f); else
map[i] = 0;
}
}
void snd_hdac_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid, bool non_pcm, int ca, int channels, unsignedchar *map, bool chmap_set)
{ if (!non_pcm && chmap_set) {
hdmi_manual_setup_channel_mapping(chmap, pin_nid,
channels, map, ca);
} else {
hdmi_std_setup_channel_mapping(chmap, pin_nid, non_pcm, ca);
hdmi_setup_fake_chmap(map, ca);
}
int snd_hdac_channel_allocation(struct hdac_device *hdac, int spk_alloc, int channels, bool chmap_set, bool non_pcm, unsignedchar *map)
{ int ca;
if (!non_pcm && chmap_set)
ca = hdmi_manual_channel_allocation(channels, map); else
ca = hdmi_channel_allocation_spk_alloc_blk(hdac,
spk_alloc, channels);
staticint hdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap, struct hdac_cea_channel_speaker_allocation *cap, int channels)
{ /* If the speaker allocation matches the channel count, it is OK.*/ if (cap->channels != channels) return -1;
/* all channels are remappable freely */ return SNDRV_CTL_TLVT_CHMAP_VAR;
}
staticvoid hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap, struct hdac_cea_channel_speaker_allocation *cap, unsignedint *chmap, int channels)
{ int count = 0; int c;
for (c = 7; c >= 0; c--) { int spk = cap->speakers[c];
if (!spk) continue;
chmap[count++] = snd_hdac_spk_to_chmap(spk);
}
WARN_ON(count != channels);
}
staticint spk_mask_from_spk_alloc(int spk_alloc)
{ int i; int spk_mask = eld_speaker_allocation_bits[0];
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { if (spk_alloc & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
return spk_mask;
}
staticint hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsignedint size, unsignedint __user *tlv)
{ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); struct hdac_chmap *chmap = info->private_data; int pcm_idx = kcontrol->private_value; unsignedint __user *dst; int chs, count = 0; unsignedlong max_chs; int type; int spk_alloc, spk_mask;
if (size < 8) return -ENOMEM; if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) return -EFAULT;
size -= 8;
dst = tlv + 2;
for (i = 0; i < ARRAY_SIZE(pcm_chmap); i++)
ucontrol->value.integer.value[i] = pcm_chmap[i];
return 0;
}
/* a simple sanity check for input values to chmap kcontrol */ staticint chmap_value_check(struct hdac_chmap *hchmap, conststruct snd_ctl_elem_value *ucontrol)
{ int i;
for (i = 0; i < hchmap->channels_max; i++) { if (ucontrol->value.integer.value[i] < 0 ||
ucontrol->value.integer.value[i] > SNDRV_CHMAP_LAST) return -EINVAL;
} return 0;
}
staticint hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); struct hdac_chmap *hchmap = info->private_data; int pcm_idx = kcontrol->private_value; unsignedint ctl_idx; struct snd_pcm_substream *substream; unsignedchar chmap[8], per_pin_chmap[8]; int i, err, ca, prepared = 0;
err = chmap_value_check(hchmap, ucontrol); if (err < 0) return err;
/* No monitor is connected in dyn_pcm_assign. * It's invalid to setup the chmap
*/ if (!hchmap->ops.is_pcm_attached(hchmap->hdac, pcm_idx)) return 0;
ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
substream = snd_pcm_chmap_substream(info, ctl_idx); if (!substream || !substream->runtime) return 0; /* just for avoiding error from alsactl restore */ switch (substream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: break; case SNDRV_PCM_STATE_PREPARED:
prepared = 1; break; default: return -EBUSY;
}
memset(chmap, 0, sizeof(chmap)); for (i = 0; i < ARRAY_SIZE(chmap); i++)
chmap[i] = ucontrol->value.integer.value[i];
hchmap->ops.get_chmap(hchmap->hdac, pcm_idx, per_pin_chmap); if (!memcmp(chmap, per_pin_chmap, sizeof(chmap))) return 0;
ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap); if (ca < 0) return -EINVAL; if (hchmap->ops.chmap_validate) {
err = hchmap->ops.chmap_validate(hchmap, ca,
ARRAY_SIZE(chmap), chmap); if (err) return err;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.