265 lines
6.8 KiB
C
265 lines
6.8 KiB
C
/*
|
|
* Copyright © 2019 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Authors: Simon Ser <simon.ser@intel.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <dirent.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "igt_core.h"
|
|
#include "igt_eld.h"
|
|
|
|
#define ELD_PREFIX "eld#"
|
|
#define ELD_DELIM " \t"
|
|
|
|
/**
|
|
* EDID-Like Data (ELD) is metadata parsed and exposed by ALSA for HDMI and
|
|
* DisplayPort connectors supporting audio. This includes the monitor name and
|
|
* the supported audio parameters (formats, sampling rates, sample sizes and so
|
|
* on).
|
|
*
|
|
* Audio parameters come from Short Audio Descriptors (SAD) blocks in the
|
|
* EDID. Enumerations from igt_edid are used since they are the same.
|
|
*/
|
|
|
|
static enum cea_sad_format parse_sad_coding_type(const char *value)
|
|
{
|
|
if (strcmp(value, "LPCM") == 0)
|
|
return CEA_SAD_FORMAT_PCM;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static enum cea_sad_sampling_rate parse_sad_rate(const char *value)
|
|
{
|
|
switch (atoi(value)) {
|
|
case 32000:
|
|
return CEA_SAD_SAMPLING_RATE_32KHZ;
|
|
case 44100:
|
|
return CEA_SAD_SAMPLING_RATE_44KHZ;
|
|
case 48000:
|
|
return CEA_SAD_SAMPLING_RATE_48KHZ;
|
|
case 88000:
|
|
return CEA_SAD_SAMPLING_RATE_88KHZ;
|
|
case 96000:
|
|
return CEA_SAD_SAMPLING_RATE_96KHZ;
|
|
case 176000:
|
|
return CEA_SAD_SAMPLING_RATE_176KHZ;
|
|
case 192000:
|
|
return CEA_SAD_SAMPLING_RATE_192KHZ;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static enum cea_sad_pcm_sample_size parse_sad_bit(const char *value)
|
|
{
|
|
switch (atoi(value)) {
|
|
case 16:
|
|
return CEA_SAD_SAMPLE_SIZE_16;
|
|
case 20:
|
|
return CEA_SAD_SAMPLE_SIZE_20;
|
|
case 24:
|
|
return CEA_SAD_SAMPLE_SIZE_24;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void parse_sad_field(struct eld_sad *sad, const char *key, char *value)
|
|
{
|
|
char *tok;
|
|
|
|
/* Some fields are prefixed with the raw hex value, strip it */
|
|
if (value[0] == '[') {
|
|
value = strchr(value, ' ');
|
|
igt_assert(value != NULL);
|
|
value++; /* skip the space */
|
|
}
|
|
|
|
/* Single-value fields */
|
|
if (strcmp(key, "coding_type") == 0)
|
|
sad->coding_type = parse_sad_coding_type(value);
|
|
else if (strcmp(key, "channels") == 0)
|
|
sad->channels = atoi(value);
|
|
|
|
/* Multiple-value fields */
|
|
tok = strtok(value, " ");
|
|
while (tok) {
|
|
if (strcmp(key, "rates") == 0)
|
|
sad->rates |= parse_sad_rate(tok);
|
|
else if (strcmp(key, "bits") == 0)
|
|
sad->bits |= parse_sad_bit(tok);
|
|
|
|
tok = strtok(NULL, " ");
|
|
}
|
|
}
|
|
|
|
/** eld_parse_entry: parse an ELD entry
|
|
*
|
|
* Here is an example of an ELD entry:
|
|
*
|
|
* $ cat /proc/asound/card0/eld#0.2
|
|
* monitor_present 1
|
|
* eld_valid 1
|
|
* monitor_name U2879G6
|
|
* connection_type DisplayPort
|
|
* eld_version [0x2] CEA-861D or below
|
|
* edid_version [0x3] CEA-861-B, C or D
|
|
* manufacture_id 0xe305
|
|
* product_id 0x2879
|
|
* port_id 0x800
|
|
* support_hdcp 0
|
|
* support_ai 0
|
|
* audio_sync_delay 0
|
|
* speakers [0x1] FL/FR
|
|
* sad_count 1
|
|
* sad0_coding_type [0x1] LPCM
|
|
* sad0_channels 2
|
|
* sad0_rates [0xe0] 32000 44100 48000
|
|
* sad0_bits [0xe0000] 16 20 24
|
|
*
|
|
* Each entry contains one or more SAD blocks. Their contents is exposed in
|
|
* sadN_* fields.
|
|
*/
|
|
static bool eld_parse_entry(const char *path, struct eld_entry *eld)
|
|
{
|
|
FILE *f;
|
|
char buf[1024];
|
|
char *key, *value, *sad_key;
|
|
size_t len;
|
|
bool monitor_present = false;
|
|
int sad_index;
|
|
|
|
memset(eld, 0, sizeof(*eld));
|
|
|
|
f = fopen(path, "r");
|
|
if (!f) {
|
|
igt_debug("Failed to open ELD file: %s\n", path);
|
|
return false;
|
|
}
|
|
|
|
while ((fgets(buf, sizeof(buf), f)) != NULL) {
|
|
len = strlen(buf);
|
|
if (buf[len - 1] == '\n')
|
|
buf[len - 1] = '\0';
|
|
|
|
key = strtok(buf, ELD_DELIM);
|
|
value = strtok(NULL, "");
|
|
/* Skip whitespace at the beginning */
|
|
value += strspn(value, ELD_DELIM);
|
|
|
|
if (strcmp(key, "monitor_present") == 0)
|
|
monitor_present = strcmp(value, "1") == 0;
|
|
else if (strcmp(key, "eld_valid") == 0)
|
|
eld->valid = strcmp(value, "1") == 0;
|
|
else if (strcmp(key, "monitor_name") == 0)
|
|
snprintf(eld->monitor_name, sizeof(eld->monitor_name),
|
|
"%s", value);
|
|
else if (strcmp(key, "sad_count") == 0)
|
|
eld->sads_len = atoi(value);
|
|
else if (sscanf(key, "sad%d_%ms", &sad_index, &sad_key) == 2) {
|
|
igt_assert(sad_index < ELD_SADS_CAP);
|
|
igt_assert(sad_index < eld->sads_len);
|
|
parse_sad_field(&eld->sads[sad_index], sad_key, value);
|
|
free(sad_key);
|
|
}
|
|
}
|
|
|
|
if (ferror(f) != 0) {
|
|
igt_debug("Failed to read ELD file %s: %d\n", path, ferror(f));
|
|
return false;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (!monitor_present)
|
|
igt_debug("Monitor not present in ELD: %s\n", path);
|
|
return monitor_present;
|
|
}
|
|
|
|
/** eld_get_igt: retrieve the ALSA ELD entry matching the IGT EDID */
|
|
bool eld_get_igt(struct eld_entry *eld)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
int i, n_elds;
|
|
char card[64];
|
|
char path[PATH_MAX];
|
|
|
|
n_elds = 0;
|
|
for (i = 0; i < 8; i++) {
|
|
snprintf(card, sizeof(card), "/proc/asound/card%d", i);
|
|
dir = opendir(card);
|
|
if (!dir)
|
|
continue;
|
|
|
|
while ((dirent = readdir(dir))) {
|
|
if (strncmp(dirent->d_name, ELD_PREFIX,
|
|
strlen(ELD_PREFIX)) != 0)
|
|
continue;
|
|
|
|
n_elds++;
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", card,
|
|
dirent->d_name);
|
|
if (!eld_parse_entry(path, eld)) {
|
|
continue;
|
|
}
|
|
|
|
if (!eld->valid) {
|
|
igt_debug("Skipping invalid ELD: %s\n", path);
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(eld->monitor_name, "IGT") != 0) {
|
|
igt_debug("Skipping non-IGT ELD: %s "
|
|
"(monitor name: %s)\n",
|
|
path, eld->monitor_name);
|
|
continue;
|
|
}
|
|
|
|
closedir(dir);
|
|
return true;
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
if (n_elds == 0)
|
|
igt_debug("Found zero ELDs\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
/** eld_has_igt: check whether ALSA has detected the audio-capable IGT EDID by
|
|
* parsing ELD entries */
|
|
bool eld_has_igt(void)
|
|
{
|
|
struct eld_entry eld;
|
|
return eld_get_igt(&eld);
|
|
}
|