| /* |
| * hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver |
| * |
| * Parse a codec proc file |
| * |
| * Copyright (c) Takashi Iwai <tiwai@suse.de> |
| * |
| * This driver is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This driver is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include "hda-types.h" |
| #include "hda-log.h" |
| |
| enum { |
| PARSE_START, |
| PARSE_ROOT, |
| PARSE_DEFAULT_PCM, |
| PARSE_GPIO, |
| PARSE_NODE, |
| PARSE_NODE_PCM, |
| PARSE_NODE_CONNECTIONS, |
| /* post-codec */ |
| PARSE_POST, |
| PARSE_SYSFS, |
| }; |
| |
| static struct xhda_codec *codec; |
| static struct xhda_node *current_node; |
| static int parse_mode; |
| |
| static const char *strmatch(const char *buf, const char *str) |
| { |
| int len = strlen(str); |
| if (!strncmp(buf, str, len)) |
| return buf + len; |
| return NULL; |
| } |
| |
| static struct xhda_node *create_node(unsigned int nid, unsigned int wcaps) |
| { |
| struct xhda_node *node; |
| struct xhda_node **prevp; |
| |
| node = xalloc(sizeof(*node)); |
| node->nid = nid; |
| node->wcaps = wcaps; |
| |
| if (nid > codec->num_widgets) |
| codec->num_widgets = nid; |
| /* insert it */ |
| for (prevp = &codec->afg.next; *prevp; prevp = &(*prevp)->next) { |
| if ((*prevp)->nid >= nid) { |
| if ((*prevp)->nid == nid) { |
| hda_log(HDA_LOG_ERR, |
| "Duplicated widget NID 0x%x\n", nid); |
| free(node); |
| return NULL; |
| } |
| break; |
| } |
| } |
| node->next = *prevp; |
| *prevp = node; |
| return node; |
| } |
| |
| static int parse_codec_recursive(const char *buffer); |
| |
| static int parse_node(const char *buf) |
| { |
| int nid, wcaps; |
| const char *p; |
| |
| if (sscanf(buf, "Node 0x%02x", &nid) != 1) { |
| hda_log(HDA_LOG_ERR, "No NID defined\n"); |
| hda_log(HDA_LOG_ERR, " bad line: %s\n", buf); |
| return -EINVAL; |
| } |
| p = strstr(buf, "wcaps "); |
| if (!p) { |
| hda_log(HDA_LOG_ERR, "No wcaps defined\n"); |
| hda_log(HDA_LOG_ERR, " bad line: %s\n", buf); |
| return -EINVAL; |
| } |
| if (sscanf(p, "wcaps 0x%x:", &wcaps) != 1) { |
| hda_log(HDA_LOG_ERR, "No wcaps defined\n"); |
| hda_log(HDA_LOG_ERR, " bad line: %s\n", buf); |
| return -EINVAL; |
| } |
| current_node = create_node(nid, wcaps); |
| if (!current_node) |
| return -ENOMEM; |
| parse_mode = PARSE_NODE; |
| return 0; |
| } |
| |
| static int parse_old_pcm_info(const char *buf, struct xhda_pcm_info *pcm) |
| { |
| if (sscanf(buf, "rates 0x%x, bits 0x%02x, types 0x%x", |
| &pcm->rates, &pcm->bits, &pcm->formats) != 3) { |
| hda_log(HDA_LOG_ERR, "invalid old amp_caps\n"); |
| hda_log(HDA_LOG_ERR, " bad line: %s\n", buf); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int parse_amp_caps(const char *buf, struct xhda_amp_caps *caps) |
| { |
| int n; |
| if (strmatch(buf, "N/A")) |
| return 0; |
| n = sscanf(buf, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, mute=%d", |
| &caps->ofs, &caps->nsteps, &caps->stepsize, &caps->mute); |
| if (n != 4) { |
| hda_log(HDA_LOG_ERR, "invalid amp_caps\n"); |
| hda_log(HDA_LOG_ERR, " bad line: %s\n", buf); |
| return -EINVAL; |
| } |
| caps->override = 1; |
| return 0; |
| } |
| |
| static int parse_amp_vals(const char *buf, struct xhda_amp_vals *vals, |
| struct xhda_amp_vals *orig_vals) |
| { |
| while (*buf && isspace(*buf)) |
| buf++; |
| if (!*buf || *buf == '\n') |
| return 0; /* no values */ |
| if (*buf == '[') { |
| int n = 0; |
| /* new format */ |
| do { |
| int v1, v2; |
| if (sscanf(buf, "[0x%02x 0x%02x]", &v1, &v2) != 2) |
| break; |
| vals->vals[n][0] = v1; |
| vals->vals[n][1] = v2; |
| n++; |
| while (*buf != ']') |
| buf++; |
| buf++; |
| while (*buf && isspace(*buf)) |
| buf++; |
| } while (*buf && *buf != '\n'); |
| } else { |
| /* old format */ |
| char *p = (char *)buf; |
| vals->vals[0][0] = strtoul(p, &p, 0); |
| vals->vals[0][1] = strtoul(p, &p, 0); |
| } |
| if (orig_vals) |
| *orig_vals = *vals; |
| return 0; |
| } |
| |
| struct dig_conv_bits { |
| const char *name; |
| unsigned int bits; |
| }; |
| |
| /* DIGITAL1 bits */ |
| #define AC_DIG1_ENABLE (1<<0) |
| #define AC_DIG1_V (1<<1) |
| #define AC_DIG1_VCFG (1<<2) |
| #define AC_DIG1_EMPHASIS (1<<3) |
| #define AC_DIG1_COPYRIGHT (1<<4) |
| #define AC_DIG1_NONAUDIO (1<<5) |
| #define AC_DIG1_PROFESSIONAL (1<<6) |
| #define AC_DIG1_LEVEL (1<<7) |
| |
| static struct dig_conv_bits dig_conv_bits[] = { |
| { " Enabled", AC_DIG1_ENABLE }, |
| { " ValidityCfg", AC_DIG1_VCFG }, |
| { " Validity", AC_DIG1_V }, |
| { " Preemphasis", AC_DIG1_EMPHASIS }, |
| { " Copyright", AC_DIG1_COPYRIGHT }, |
| { " Non-Audio", AC_DIG1_NONAUDIO }, |
| { " Pro", AC_DIG1_PROFESSIONAL }, |
| { " GenLevel", AC_DIG1_LEVEL }, |
| {} |
| }; |
| |
| static int parse_node_digital(const char *buf, struct xhda_node *node) |
| { |
| for (;;) { |
| const struct dig_conv_bits *dig; |
| for (dig = dig_conv_bits; dig->name; dig++) { |
| const char *p = strmatch(buf, dig->name); |
| if (p) { |
| node->dig_conv |= dig->bits; |
| buf = p; |
| break; |
| } |
| } |
| if (!dig->name) |
| return 0; |
| } |
| } |
| |
| static int parse_node_items(const char *buf) |
| { |
| struct xhda_node *node = current_node; |
| const char *head = buf; |
| const char *p; |
| |
| if (*head == ' ') { |
| int i; |
| for (i = 0; i < 2 && *head == ' '; i++) |
| head++; |
| } |
| if ((p = strmatch(head, "Amp-In caps: "))) { |
| return parse_amp_caps(p, &node->amp_in_caps); |
| } else if ((p = strmatch(head, "Amp-Out caps: "))) { |
| return parse_amp_caps(p, &node->amp_out_caps); |
| } else if ((p = strmatch(head, "Amp-In vals: "))) { |
| return parse_amp_vals(p, &node->amp_in_vals, |
| &node->orig_amp_in_vals); |
| } else if ((p = strmatch(head, "Amp-Out vals: "))) { |
| return parse_amp_vals(p, &node->amp_out_vals, |
| &node->orig_amp_out_vals); |
| } else if ((p = strmatch(head, "Pin-ctls: "))) { |
| node->pinctl = strtoul(p, NULL, 0); |
| if (node->pincap) |
| hda_verify_pin_ctl(node, 0, &node->pinctl); |
| node->orig_pinctl = node->pinctl; |
| } else if ((p = strmatch(head, "Pincap "))) { |
| const char *end; |
| /* a special hack to avoid the old bug */ |
| if (strmatch(p, "0x08") && |
| ((end = strchr(p, ':'))) && |
| (size_t)(end - p) != 10) |
| node->pincap = strtoul(p + 4, NULL, 16); |
| else |
| node->pincap = strtoul(p, NULL, 0); |
| } else if ((p = strmatch(head, "Pin Default "))) { |
| node->pin_default = strtoul(p, NULL, 0); |
| } else if ((p = strmatch(head, "Unsolicited: "))) { |
| /* not yet */ |
| } else if ((p = strmatch(head, "Power: "))) { |
| sscanf(p, "setting=D%d, actual=D%d", |
| &node->power_setting, &node->power_current); |
| } else if ((p = strmatch(head, "Processing caps: "))) { |
| sscanf(p, "benign=%d, ncoeff=%d", |
| &node->coef_benign, &node->coef_num); |
| } else if ((p = strmatch(head, "Digital:"))) { |
| return parse_node_digital(p, node); |
| } else if ((p = strmatch(head, "Digital category: "))) { |
| node->dig_category = strtoul(p, NULL, 0); |
| } else if ((p = strmatch(head, "PCM:"))) { |
| if (*p == ' ') |
| return parse_old_pcm_info(p + 1, &node->pcm); |
| parse_mode = PARSE_NODE_PCM; |
| } else if ((p = strmatch(head, "Connection: "))) { |
| node->num_nodes = strtoul(p, NULL, 0); |
| if (node->num_nodes >= 128) { |
| hda_log(HDA_LOG_WARN, "Clearing invalid connection# %d @ NID 0x%x\n", node->num_nodes, node->nid); |
| node->num_nodes = 0; |
| } |
| if (node->num_nodes) |
| parse_mode = PARSE_NODE_CONNECTIONS; |
| } |
| return 0; |
| } |
| |
| static int parse_node_connections(const char *buf) |
| { |
| struct xhda_node *node = current_node; |
| char *p = (char *)buf; |
| int i; |
| |
| for (i = 0; i < node->num_nodes; i++) { |
| while (*p && (*p == '*' || isspace(*p))) |
| p++; |
| node->node[i] = strtoul(p, &p, 0); |
| if (*p == '*') { |
| node->curr_conn = i; |
| p++; |
| } |
| } |
| parse_mode = PARSE_ROOT; |
| current_node = NULL; /* reset */ |
| return 0; |
| } |
| |
| static int parse_pcm_items(const char *buf, struct xhda_pcm_info *pcm) |
| { |
| const char *p; |
| |
| while (*buf && isspace(*buf)) |
| buf++; |
| if ((p = strmatch(buf, "rates ["))) |
| pcm->rates = strtoul(p, NULL, 0); |
| else if ((p = strmatch(buf, "bits ["))) { |
| pcm->bits = strtoul(p, NULL, 0); |
| if (pcm->bits >= 0x1000) { |
| /* workaround a temporary bug */ |
| pcm->bits >>= 16; |
| } |
| } else if ((p = strmatch(buf, "formats ["))) |
| pcm->formats = strtoul(p, NULL, 0); |
| else |
| return 1; |
| return 0; |
| } |
| |
| static int parse_default_pcm_items(const char *buf) |
| { |
| int err = parse_pcm_items(buf, &codec->afg.pcm); |
| if (err < 0) |
| return err; |
| if (err > 0) { |
| parse_mode = PARSE_ROOT; |
| return parse_codec_recursive(buf); |
| } |
| return 0; |
| } |
| |
| static int parse_default_pcm(const char *buf) |
| { |
| if (*buf == ' ') { |
| /* old format */ |
| return parse_old_pcm_info(buf + 1, &codec->afg.pcm); |
| } |
| parse_mode = PARSE_DEFAULT_PCM; |
| return 0; |
| } |
| |
| static int parse_node_pcm(const char *buf) |
| { |
| int err = parse_pcm_items(buf, ¤t_node->pcm); |
| if (err < 0) |
| return err; |
| if (err > 0) { |
| parse_mode = PARSE_NODE; |
| return parse_codec_recursive(buf); |
| } |
| return 0; |
| } |
| |
| static int parse_gpio(const char *buf) |
| { |
| unsigned int gpio_io, gpio_o, gpio_i, gpio_unsol, gpio_wake; |
| struct xhda_node *node = current_node ? current_node : &codec->afg; |
| |
| while (*buf && isspace(*buf)) |
| buf++; |
| if (sscanf(buf, "io=%d, o=%d, i=%d, unsolicited=%d, wake=%d", |
| &gpio_io, &gpio_o, &gpio_i, &gpio_unsol, &gpio_wake) != 5) { |
| /* an invalid line; skip */ |
| return 0; |
| } |
| node->gpio_cap = gpio_io | |
| (gpio_o << 8) | |
| (gpio_i << 16) | |
| (gpio_unsol << 30) | |
| (gpio_wake << 31); |
| parse_mode = PARSE_GPIO; |
| return 0; |
| } |
| |
| static int parse_gpio_items(const char *buf) |
| { |
| struct xhda_node *node = current_node ? current_node : &codec->afg; |
| unsigned int i, enable, dir, wake, sticky, data; |
| unsigned int unsol = 0; |
| |
| if (sscanf(buf, " IO[%d]: enable=%d, dir=%d, wake=%d, sticky=%d, data=%d, unsol=%d", |
| &i, &enable, &dir, &wake, &sticky, &data, &unsol) != 7) { |
| if (sscanf(buf, " IO[%d]: enable=%d, dir=%d, wake=%d, sticky=%d, data=%d", |
| &i, &enable, &dir, &wake, &sticky, &data) != 6) { |
| /* an invalid line; abort */ |
| parse_mode = PARSE_NODE; |
| return parse_codec_recursive(buf); |
| } |
| } |
| node->gpio_mask |= (enable << i); |
| node->gpio_dir |= (dir << i); |
| node->gpio_wake |= (wake << i); |
| node->gpio_sticky |= (sticky << i); |
| node->gpio_data |= (data << i); |
| node->gpio_unsol |= (unsol << i); |
| return 0; |
| } |
| |
| static int in_modem_whitelist(struct xhda_codec *codec) |
| { |
| switch (codec->vendor_id) { |
| case 0x14f15045: |
| case 0x14f15047: |
| case 0x14f15051: |
| case 0x14f15067: |
| case 0x14f15069: |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int parse_root(const char *buffer) |
| { |
| const char *p; |
| |
| if ((p = strmatch(buffer, "Address: "))) { |
| codec->addr = strtoul(p, NULL, 0); |
| } else if ((p = strmatch(buffer, "Function Id: "))) { |
| unsigned int id; |
| id = strtoul(p, NULL, 0); |
| if (strmatch(buffer, "AFG Function Id: ")) { |
| if (id != 1) |
| hda_log(HDA_LOG_WARN, "AFG ID != 1 (%d)\n", id); |
| codec->function_id = id; |
| } else if (strmatch(buffer, "MFG Function Id: ")) |
| codec->modem_function_id = id; |
| else |
| codec->function_id = 0x01; /* workaround for old bug */ |
| return 0; |
| } else if ((p = strmatch(buffer, "Vendor Id: "))) { |
| codec->vendor_id = strtoul(p, NULL, 0); |
| return 0; |
| } else if ((p = strmatch(buffer, "Subsystem Id: "))) { |
| codec->subsystem_id = strtoul(p, NULL, 0); |
| return 0; |
| } else if ((p = strmatch(buffer, "Revision Id: "))) { |
| codec->revision_id = strtoul(p, NULL, 0); |
| return 0; |
| } else if ((p = strmatch(buffer, "Default PCM:"))) { |
| return parse_default_pcm(p); |
| } else if ((p = strmatch(buffer, "Default Amp-In caps: "))) { |
| return parse_amp_caps(p, &codec->afg.amp_in_caps); |
| } else if ((p = strmatch(buffer, "Default Amp-Out caps: "))) { |
| return parse_amp_caps(p, &codec->afg.amp_out_caps); |
| } else if ((p = strmatch(buffer, "GPIO: "))) { |
| return parse_gpio(p); |
| } else if (strmatch(buffer, "Node ")) { |
| return parse_node(buffer); |
| } else if ((p = strmatch(buffer, "Modem Function Group: "))) { |
| codec->mfg.nid = strtoul(p, NULL, 0); |
| if (!codec->function_id && !in_modem_whitelist(codec)) |
| return -EBADFD; |
| return 0; |
| } |
| return 0; /* ignore */ |
| } |
| |
| static int parse_codec_recursive(const char *buffer) |
| { |
| switch (parse_mode) { |
| case PARSE_ROOT: |
| return parse_root(buffer); |
| case PARSE_DEFAULT_PCM: |
| return parse_default_pcm_items(buffer); |
| case PARSE_GPIO: |
| return parse_gpio_items(buffer); |
| case PARSE_NODE: |
| return parse_node_items(buffer); |
| case PARSE_NODE_PCM: |
| return parse_node_pcm(buffer); |
| case PARSE_NODE_CONNECTIONS: |
| return parse_node_connections(buffer); |
| } |
| return 0; |
| } |
| |
| /* check the extra information from alsa-info output */ |
| static int check_alsa_info(char *line, int override) |
| { |
| int dummy, classid, vendor, id, rev; |
| |
| if (sscanf(line, "%02x:%02x.%1d %04x: %04x:%04x (rev %02x)", |
| &dummy, &dummy, &dummy, &classid, &vendor, &id, &rev) == 7) { |
| if (classid == 0x0403 && (override || !codec->pci_vendor)) { |
| codec->pci_vendor = vendor; |
| codec->pci_device = id; |
| codec->pci_revision = rev; |
| } |
| return 0; |
| } |
| if (sscanf(line, "%02x:%02x.%1d Audio device [%04x]:", |
| &dummy, &dummy, &dummy, &classid) == 4 && |
| classid == 0x0403 && |
| (override || !codec->pci_vendor)) { |
| if (sscanf(line + strlen(line) - 12, "[%04x:%04x]", |
| &vendor, &id) == 2) { |
| codec->pci_vendor = vendor; |
| codec->pci_device = id; |
| } |
| return 0; |
| } |
| if (sscanf(line, "\tSubsystem: %04x:%04x", &vendor, &id) == 2 || |
| sscanf(line, " Subsystem: %04x:%04x", &vendor, &id) == 2) { |
| if (override || !codec->pci_subvendor) { |
| codec->pci_subvendor = vendor; |
| codec->pci_subdevice = id; |
| hda_log(HDA_LOG_INFO, "Getting PCI ID %04x:%04x (%04x:%04x) rev %02x\n", |
| codec->pci_vendor, codec->pci_device, |
| codec->pci_subvendor, codec->pci_subdevice, |
| codec->pci_revision); |
| } |
| return 1; |
| } |
| if (!strncmp(line, "\tSubsystem: ", 12) || |
| !strncmp(line, " Subsystem: ", 19)) { |
| if ((override || !codec->pci_subvendor) && |
| sscanf(line + strlen(line) - 12, "[%04x:%04x]", |
| &vendor, &id) == 2) { |
| codec->pci_subvendor = vendor; |
| codec->pci_subdevice = id; |
| hda_log(HDA_LOG_INFO, "Getting PCI ID %04x:%04x (%04x:%04x) rev %02x\n", |
| codec->pci_vendor, codec->pci_device, |
| codec->pci_subvendor, codec->pci_subdevice, |
| codec->pci_revision); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int add_sysfs_list(struct xhda_codec *codec, int *vals) |
| { |
| struct xhda_sysfs_value *item = xalloc(sizeof(*item)); |
| struct xhda_sysfs_value *p; |
| |
| memcpy(item->val, vals, sizeof(item->val)); |
| item->next = NULL; |
| if (!codec->sysfs_list->entry.vals) |
| codec->sysfs_list->entry.vals = item; |
| else { |
| for (p = codec->sysfs_list->entry.vals; p->next; p = p->next) |
| ; |
| p->next = item; |
| } |
| return 0; |
| } |
| |
| static int add_sysfs_hint(struct xhda_codec *codec, char *line) |
| { |
| struct xhda_sysfs_hints *item = xalloc(sizeof(*item)); |
| struct xhda_sysfs_hints *p; |
| |
| item->line = strdup(line); |
| item->next = NULL; |
| if (!codec->sysfs_list->entry.hints) |
| codec->sysfs_list->entry.hints = item; |
| else { |
| for (p = codec->sysfs_list->entry.hints; p->next; p = p->next) |
| ; |
| p->next = item; |
| } |
| return 0; |
| } |
| |
| static int do_parse_sysfs(struct xhda_codec *codec, char *buffer) |
| { |
| if (strmatch(buffer, "!!--")) |
| return 0; |
| if (strmatch(buffer, "/sys/class/sound/hwC")) { |
| char *p; |
| struct xhda_sysfs_list *sys; |
| |
| sys = xalloc(sizeof(*sys)); |
| sys->id = strdup(buffer + strlen("/sys/class/sound/")); |
| p = strchr(sys->id, ':'); |
| if (p) |
| *p = 0; |
| p = strchr(sys->id, '/'); |
| if (!p) { |
| free(sys->id); |
| free(sys); |
| return 0; |
| } |
| if (!strcmp(p + 1, "init_verbs")) |
| sys->type = XHDA_SYS_VERBS; |
| else if (!strcmp(p + 1, "hints")) |
| sys->type = XHDA_SYS_HINTS; |
| else |
| sys->type = XHDA_SYS_PINCFG; |
| hda_log(HDA_LOG_INFO, "Adding sysfs entry %s\n", sys->id); |
| sys->next = codec->sysfs_list; |
| sys->entry.vals = NULL; |
| codec->sysfs_list = sys; |
| return 0; |
| } |
| if (strchr(buffer, '=')) { |
| add_sysfs_hint(codec, buffer); |
| } else if (isdigit(*buffer)) { |
| int val[3]; |
| if (!codec->sysfs_list) |
| return 0; |
| switch (codec->sysfs_list->type) { |
| case XHDA_SYS_VERBS: |
| if (sscanf(buffer, "%i %i %i", &val[0], &val[1], &val[2]) != 3) |
| return 0; |
| break; |
| case XHDA_SYS_PINCFG: |
| if (sscanf(buffer, "%i %i", &val[0], &val[1]) != 2) |
| return 0; |
| val[2] = 0; |
| break; |
| default: |
| return 0; |
| } |
| add_sysfs_list(codec, val); |
| return 0; |
| } |
| if (!*buffer || isspace(*buffer)) |
| return 0; |
| return 1; /* no more data */ |
| } |
| |
| static void clear_codec(struct xhda_codec *codec) |
| { |
| free(codec->parsed_name); |
| codec->parsed_name = NULL; |
| codec->function_id = 0; |
| codec->modem_function_id = 0; |
| codec->mfg.nid = 0; |
| codec->num_widgets = 1; |
| } |
| |
| int parse_codec_proc(FILE *fp, struct xhda_codec *codecp, int codec_index) |
| { |
| char buffer[256], *p; |
| int curidx = -1, pciidx = 0; |
| int err = 0; |
| |
| codec = codecp; |
| parse_mode = PARSE_START; |
| current_node = NULL; |
| |
| codec->num_widgets = 1; |
| codec->afg.nid = 0x01; |
| while (fgets(buffer, sizeof(buffer), fp)) { |
| again: |
| if (parse_mode == PARSE_START) { |
| if (!strmatch(buffer, "Codec: ")) { |
| if (check_alsa_info(buffer, pciidx == codec_index)) |
| pciidx++; |
| continue; |
| } |
| curidx++; |
| if (codec_index < 0 || curidx == codec_index) |
| parse_mode = PARSE_ROOT; |
| p = strrchr(buffer, '\n'); |
| if (p) |
| *p = 0; |
| codec->parsed_name = strdup(buffer + strlen("Codec: ")); |
| if (!codec->parsed_name) |
| return -ENOMEM; |
| } else if (strmatch(buffer, "Codec: ")) { |
| if (codec->mfg.nid && |
| codec->num_widgets <= codec->mfg.nid && |
| !in_modem_whitelist(codec)) { |
| hda_log(HDA_LOG_INFO, "Codec %d is a modem codec, skipping\n", curidx); |
| parse_mode = PARSE_START; |
| clear_codec(codec); |
| goto again; |
| } |
| parse_mode = PARSE_POST; |
| continue; |
| } |
| |
| if (parse_mode < PARSE_POST) { |
| if (!isspace(*buffer) && |
| (*buffer < 'A' || *buffer >= 'Z')) { |
| /* no more lines */ |
| parse_mode = PARSE_POST; |
| } |
| } |
| if (parse_mode == PARSE_POST) { |
| if (strmatch(buffer, "!!Sysfs Files")) |
| parse_mode = PARSE_SYSFS; |
| continue; |
| } |
| if (parse_mode == PARSE_SYSFS) { |
| if (do_parse_sysfs(codec, buffer) > 0) |
| parse_mode = PARSE_POST; |
| continue; |
| } |
| |
| if (parse_mode > PARSE_ROOT && *buffer != ' ') { |
| current_node = NULL; |
| parse_mode = PARSE_ROOT; |
| } |
| err = parse_codec_recursive(buffer); |
| if (err == -EBADFD) { |
| if (codec_index < 0) { |
| hda_log(HDA_LOG_INFO, "Codec %d is a modem codec, skipping\n", curidx); |
| parse_mode = PARSE_START; |
| clear_codec(codec); |
| continue; |
| } |
| hda_log(HDA_LOG_ERR, "Codec %d is a modem codec, aborting\n", curidx); |
| return err; |
| } |
| else if (err < 0) { |
| hda_log(HDA_LOG_ERR, "ERROR %d\n", err); |
| return err; |
| } |
| } |
| |
| if (curidx < codec_index) { |
| hda_log(HDA_LOG_ERR, "Codec index %d requested, but found only %d codecs\n", codec_index, curidx+1); |
| return -ENODEV; |
| } |
| |
| add_codec_extensions(codec); |
| |
| return 0; |
| } |