mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 19:29:37 +00:00
![Takashi Iwai](/assets/img/avatar_default.png)
commit2506318e38
upstream. It seems that the HD-audio clear and reconfig sysfs don't work any longer after the recent driver core change. There are multiple issues around that: the linked list corruption and the dead device handling. The former issue is fixed by another patch for the driver core itself, while the latter patch needs to be addressed in HD-audio side. This patch corresponds to the latter, it recovers those broken functions by replacing the device detach and attach actions with the standard core API functions, which are almost equivalent with unbind and bind actions. Fixes:654888327e
("driver core: Avoid binding drivers to dead devices") Cc: <stable@vger.kernel.org> BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=209207 Link: https://lore.kernel.org/r/20201209150119.7705-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
799 lines
19 KiB
C
799 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* sysfs interface for HD-audio codec
|
|
*
|
|
* Copyright (c) 2014 Takashi Iwai <tiwai@suse.de>
|
|
*
|
|
* split from hda_hwdep.c
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/string.h>
|
|
#include <linux/export.h>
|
|
#include <sound/core.h>
|
|
#include <sound/hda_codec.h>
|
|
#include "hda_local.h"
|
|
#include <sound/hda_hwdep.h>
|
|
#include <sound/minors.h>
|
|
|
|
/* hint string pair */
|
|
struct hda_hint {
|
|
const char *key;
|
|
const char *val; /* contained in the same alloc as key */
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static ssize_t power_on_acct_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
snd_hda_update_power_acct(codec);
|
|
return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct));
|
|
}
|
|
|
|
static ssize_t power_off_acct_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
snd_hda_update_power_acct(codec);
|
|
return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct));
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(power_on_acct);
|
|
static DEVICE_ATTR_RO(power_off_acct);
|
|
#endif /* CONFIG_PM */
|
|
|
|
#define CODEC_INFO_SHOW(type, field) \
|
|
static ssize_t type##_show(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
struct hda_codec *codec = dev_get_drvdata(dev); \
|
|
return sprintf(buf, "0x%x\n", codec->field); \
|
|
}
|
|
|
|
#define CODEC_INFO_STR_SHOW(type, field) \
|
|
static ssize_t type##_show(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
struct hda_codec *codec = dev_get_drvdata(dev); \
|
|
return sprintf(buf, "%s\n", \
|
|
codec->field ? codec->field : ""); \
|
|
}
|
|
|
|
CODEC_INFO_SHOW(vendor_id, core.vendor_id);
|
|
CODEC_INFO_SHOW(subsystem_id, core.subsystem_id);
|
|
CODEC_INFO_SHOW(revision_id, core.revision_id);
|
|
CODEC_INFO_SHOW(afg, core.afg);
|
|
CODEC_INFO_SHOW(mfg, core.mfg);
|
|
CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name);
|
|
CODEC_INFO_STR_SHOW(chip_name, core.chip_name);
|
|
CODEC_INFO_STR_SHOW(modelname, modelname);
|
|
|
|
static ssize_t pin_configs_show(struct hda_codec *codec,
|
|
struct snd_array *list,
|
|
char *buf)
|
|
{
|
|
const struct hda_pincfg *pin;
|
|
int i, len = 0;
|
|
mutex_lock(&codec->user_mutex);
|
|
snd_array_for_each(list, i, pin) {
|
|
len += sprintf(buf + len, "0x%02x 0x%08x\n",
|
|
pin->nid, pin->cfg);
|
|
}
|
|
mutex_unlock(&codec->user_mutex);
|
|
return len;
|
|
}
|
|
|
|
static ssize_t init_pin_configs_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
return pin_configs_show(codec, &codec->init_pins, buf);
|
|
}
|
|
|
|
static ssize_t driver_pin_configs_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
return pin_configs_show(codec, &codec->driver_pins, buf);
|
|
}
|
|
|
|
#ifdef CONFIG_SND_HDA_RECONFIG
|
|
|
|
/*
|
|
* sysfs interface
|
|
*/
|
|
|
|
static int clear_codec(struct hda_codec *codec)
|
|
{
|
|
int err;
|
|
|
|
err = snd_hda_codec_reset(codec);
|
|
if (err < 0) {
|
|
codec_err(codec, "The codec is being used, can't free.\n");
|
|
return err;
|
|
}
|
|
snd_hda_sysfs_clear(codec);
|
|
return 0;
|
|
}
|
|
|
|
static int reconfig_codec(struct hda_codec *codec)
|
|
{
|
|
int err;
|
|
|
|
snd_hda_power_up(codec);
|
|
codec_info(codec, "hda-codec: reconfiguring\n");
|
|
err = snd_hda_codec_reset(codec);
|
|
if (err < 0) {
|
|
codec_err(codec,
|
|
"The codec is being used, can't reconfigure.\n");
|
|
goto error;
|
|
}
|
|
err = device_reprobe(hda_codec_dev(codec));
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_card_register(codec->card);
|
|
error:
|
|
snd_hda_power_down(codec);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* allocate a string at most len chars, and remove the trailing EOL
|
|
*/
|
|
static char *kstrndup_noeol(const char *src, size_t len)
|
|
{
|
|
char *s = kstrndup(src, len, GFP_KERNEL);
|
|
char *p;
|
|
if (!s)
|
|
return NULL;
|
|
p = strchr(s, '\n');
|
|
if (p)
|
|
*p = 0;
|
|
return s;
|
|
}
|
|
|
|
#define CODEC_INFO_STORE(type, field) \
|
|
static ssize_t type##_store(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
const char *buf, size_t count) \
|
|
{ \
|
|
struct hda_codec *codec = dev_get_drvdata(dev); \
|
|
unsigned long val; \
|
|
int err = kstrtoul(buf, 0, &val); \
|
|
if (err < 0) \
|
|
return err; \
|
|
codec->field = val; \
|
|
return count; \
|
|
}
|
|
|
|
#define CODEC_INFO_STR_STORE(type, field) \
|
|
static ssize_t type##_store(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
const char *buf, size_t count) \
|
|
{ \
|
|
struct hda_codec *codec = dev_get_drvdata(dev); \
|
|
char *s = kstrndup_noeol(buf, 64); \
|
|
if (!s) \
|
|
return -ENOMEM; \
|
|
kfree(codec->field); \
|
|
codec->field = s; \
|
|
return count; \
|
|
}
|
|
|
|
CODEC_INFO_STORE(vendor_id, core.vendor_id);
|
|
CODEC_INFO_STORE(subsystem_id, core.subsystem_id);
|
|
CODEC_INFO_STORE(revision_id, core.revision_id);
|
|
CODEC_INFO_STR_STORE(vendor_name, core.vendor_name);
|
|
CODEC_INFO_STR_STORE(chip_name, core.chip_name);
|
|
CODEC_INFO_STR_STORE(modelname, modelname);
|
|
|
|
#define CODEC_ACTION_STORE(type) \
|
|
static ssize_t type##_store(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
const char *buf, size_t count) \
|
|
{ \
|
|
struct hda_codec *codec = dev_get_drvdata(dev); \
|
|
int err = 0; \
|
|
if (*buf) \
|
|
err = type##_codec(codec); \
|
|
return err < 0 ? err : count; \
|
|
}
|
|
|
|
CODEC_ACTION_STORE(reconfig);
|
|
CODEC_ACTION_STORE(clear);
|
|
|
|
static ssize_t init_verbs_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
const struct hda_verb *v;
|
|
int i, len = 0;
|
|
mutex_lock(&codec->user_mutex);
|
|
snd_array_for_each(&codec->init_verbs, i, v) {
|
|
len += scnprintf(buf + len, PAGE_SIZE - len,
|
|
"0x%02x 0x%03x 0x%04x\n",
|
|
v->nid, v->verb, v->param);
|
|
}
|
|
mutex_unlock(&codec->user_mutex);
|
|
return len;
|
|
}
|
|
|
|
static int parse_init_verbs(struct hda_codec *codec, const char *buf)
|
|
{
|
|
struct hda_verb *v;
|
|
int nid, verb, param;
|
|
|
|
if (sscanf(buf, "%i %i %i", &nid, &verb, ¶m) != 3)
|
|
return -EINVAL;
|
|
if (!nid || !verb)
|
|
return -EINVAL;
|
|
mutex_lock(&codec->user_mutex);
|
|
v = snd_array_new(&codec->init_verbs);
|
|
if (!v) {
|
|
mutex_unlock(&codec->user_mutex);
|
|
return -ENOMEM;
|
|
}
|
|
v->nid = nid;
|
|
v->verb = verb;
|
|
v->param = param;
|
|
mutex_unlock(&codec->user_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t init_verbs_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
int err = parse_init_verbs(codec, buf);
|
|
if (err < 0)
|
|
return err;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hints_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
const struct hda_hint *hint;
|
|
int i, len = 0;
|
|
mutex_lock(&codec->user_mutex);
|
|
snd_array_for_each(&codec->hints, i, hint) {
|
|
len += scnprintf(buf + len, PAGE_SIZE - len,
|
|
"%s = %s\n", hint->key, hint->val);
|
|
}
|
|
mutex_unlock(&codec->user_mutex);
|
|
return len;
|
|
}
|
|
|
|
static struct hda_hint *get_hint(struct hda_codec *codec, const char *key)
|
|
{
|
|
struct hda_hint *hint;
|
|
int i;
|
|
|
|
snd_array_for_each(&codec->hints, i, hint) {
|
|
if (!strcmp(hint->key, key))
|
|
return hint;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void remove_trail_spaces(char *str)
|
|
{
|
|
char *p;
|
|
if (!*str)
|
|
return;
|
|
p = str + strlen(str) - 1;
|
|
for (; isspace(*p); p--) {
|
|
*p = 0;
|
|
if (p == str)
|
|
return;
|
|
}
|
|
}
|
|
|
|
#define MAX_HINTS 1024
|
|
|
|
static int parse_hints(struct hda_codec *codec, const char *buf)
|
|
{
|
|
char *key, *val;
|
|
struct hda_hint *hint;
|
|
int err = 0;
|
|
|
|
buf = skip_spaces(buf);
|
|
if (!*buf || *buf == '#' || *buf == '\n')
|
|
return 0;
|
|
if (*buf == '=')
|
|
return -EINVAL;
|
|
key = kstrndup_noeol(buf, 1024);
|
|
if (!key)
|
|
return -ENOMEM;
|
|
/* extract key and val */
|
|
val = strchr(key, '=');
|
|
if (!val) {
|
|
kfree(key);
|
|
return -EINVAL;
|
|
}
|
|
*val++ = 0;
|
|
val = skip_spaces(val);
|
|
remove_trail_spaces(key);
|
|
remove_trail_spaces(val);
|
|
mutex_lock(&codec->user_mutex);
|
|
hint = get_hint(codec, key);
|
|
if (hint) {
|
|
/* replace */
|
|
kfree(hint->key);
|
|
hint->key = key;
|
|
hint->val = val;
|
|
goto unlock;
|
|
}
|
|
/* allocate a new hint entry */
|
|
if (codec->hints.used >= MAX_HINTS)
|
|
hint = NULL;
|
|
else
|
|
hint = snd_array_new(&codec->hints);
|
|
if (hint) {
|
|
hint->key = key;
|
|
hint->val = val;
|
|
} else {
|
|
err = -ENOMEM;
|
|
}
|
|
unlock:
|
|
mutex_unlock(&codec->user_mutex);
|
|
if (err)
|
|
kfree(key);
|
|
return err;
|
|
}
|
|
|
|
static ssize_t hints_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
int err = parse_hints(codec, buf);
|
|
if (err < 0)
|
|
return err;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t user_pin_configs_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
return pin_configs_show(codec, &codec->user_pins, buf);
|
|
}
|
|
|
|
#define MAX_PIN_CONFIGS 32
|
|
|
|
static int parse_user_pin_configs(struct hda_codec *codec, const char *buf)
|
|
{
|
|
int nid, cfg, err;
|
|
|
|
if (sscanf(buf, "%i %i", &nid, &cfg) != 2)
|
|
return -EINVAL;
|
|
if (!nid)
|
|
return -EINVAL;
|
|
mutex_lock(&codec->user_mutex);
|
|
err = snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg);
|
|
mutex_unlock(&codec->user_mutex);
|
|
return err;
|
|
}
|
|
|
|
static ssize_t user_pin_configs_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct hda_codec *codec = dev_get_drvdata(dev);
|
|
int err = parse_user_pin_configs(codec, buf);
|
|
if (err < 0)
|
|
return err;
|
|
return count;
|
|
}
|
|
|
|
/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */
|
|
static DEVICE_ATTR_RW(init_verbs);
|
|
static DEVICE_ATTR_RW(hints);
|
|
static DEVICE_ATTR_RW(user_pin_configs);
|
|
static DEVICE_ATTR_WO(reconfig);
|
|
static DEVICE_ATTR_WO(clear);
|
|
|
|
/**
|
|
* snd_hda_get_hint - Look for hint string
|
|
* @codec: the HDA codec
|
|
* @key: the hint key string
|
|
*
|
|
* Look for a hint key/value pair matching with the given key string
|
|
* and returns the value string. If nothing found, returns NULL.
|
|
*/
|
|
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
|
|
{
|
|
struct hda_hint *hint = get_hint(codec, key);
|
|
return hint ? hint->val : NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hda_get_hint);
|
|
|
|
/**
|
|
* snd_hda_get_bool_hint - Get a boolean hint value
|
|
* @codec: the HDA codec
|
|
* @key: the hint key string
|
|
*
|
|
* Look for a hint key/value pair matching with the given key string
|
|
* and returns a boolean value parsed from the value. If no matching
|
|
* key is found, return a negative value.
|
|
*/
|
|
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
|
|
{
|
|
const char *p;
|
|
int ret;
|
|
|
|
mutex_lock(&codec->user_mutex);
|
|
p = snd_hda_get_hint(codec, key);
|
|
if (!p || !*p)
|
|
ret = -ENOENT;
|
|
else {
|
|
switch (toupper(*p)) {
|
|
case 'T': /* true */
|
|
case 'Y': /* yes */
|
|
case '1':
|
|
ret = 1;
|
|
break;
|
|
default:
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&codec->user_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint);
|
|
|
|
/**
|
|
* snd_hda_get_int_hint - Get an integer hint value
|
|
* @codec: the HDA codec
|
|
* @key: the hint key string
|
|
* @valp: pointer to store a value
|
|
*
|
|
* Look for a hint key/value pair matching with the given key string
|
|
* and stores the integer value to @valp. If no matching key is found,
|
|
* return a negative error code. Otherwise it returns zero.
|
|
*/
|
|
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
|
|
{
|
|
const char *p;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
mutex_lock(&codec->user_mutex);
|
|
p = snd_hda_get_hint(codec, key);
|
|
if (!p)
|
|
ret = -ENOENT;
|
|
else if (kstrtoul(p, 0, &val))
|
|
ret = -EINVAL;
|
|
else {
|
|
*valp = val;
|
|
ret = 0;
|
|
}
|
|
mutex_unlock(&codec->user_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hda_get_int_hint);
|
|
#endif /* CONFIG_SND_HDA_RECONFIG */
|
|
|
|
/*
|
|
* common sysfs attributes
|
|
*/
|
|
#ifdef CONFIG_SND_HDA_RECONFIG
|
|
#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name)
|
|
#else
|
|
#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name)
|
|
#endif
|
|
static RECONFIG_DEVICE_ATTR(vendor_id);
|
|
static RECONFIG_DEVICE_ATTR(subsystem_id);
|
|
static RECONFIG_DEVICE_ATTR(revision_id);
|
|
static DEVICE_ATTR_RO(afg);
|
|
static DEVICE_ATTR_RO(mfg);
|
|
static RECONFIG_DEVICE_ATTR(vendor_name);
|
|
static RECONFIG_DEVICE_ATTR(chip_name);
|
|
static RECONFIG_DEVICE_ATTR(modelname);
|
|
static DEVICE_ATTR_RO(init_pin_configs);
|
|
static DEVICE_ATTR_RO(driver_pin_configs);
|
|
|
|
|
|
#ifdef CONFIG_SND_HDA_PATCH_LOADER
|
|
|
|
/* parser mode */
|
|
enum {
|
|
LINE_MODE_NONE,
|
|
LINE_MODE_CODEC,
|
|
LINE_MODE_MODEL,
|
|
LINE_MODE_PINCFG,
|
|
LINE_MODE_VERB,
|
|
LINE_MODE_HINT,
|
|
LINE_MODE_VENDOR_ID,
|
|
LINE_MODE_SUBSYSTEM_ID,
|
|
LINE_MODE_REVISION_ID,
|
|
LINE_MODE_CHIP_NAME,
|
|
NUM_LINE_MODES,
|
|
};
|
|
|
|
static inline int strmatch(const char *a, const char *b)
|
|
{
|
|
return strncasecmp(a, b, strlen(b)) == 0;
|
|
}
|
|
|
|
/* parse the contents after the line "[codec]"
|
|
* accept only the line with three numbers, and assign the current codec
|
|
*/
|
|
static void parse_codec_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
int vendorid, subid, caddr;
|
|
struct hda_codec *codec;
|
|
|
|
*codecp = NULL;
|
|
if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) {
|
|
list_for_each_codec(codec, bus) {
|
|
if ((vendorid <= 0 || codec->core.vendor_id == vendorid) &&
|
|
(subid <= 0 || codec->core.subsystem_id == subid) &&
|
|
codec->core.addr == caddr) {
|
|
*codecp = codec;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* parse the contents after the other command tags, [pincfg], [verb],
|
|
* [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model]
|
|
* just pass to the sysfs helper (only when any codec was specified)
|
|
*/
|
|
static void parse_pincfg_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
parse_user_pin_configs(*codecp, buf);
|
|
}
|
|
|
|
static void parse_verb_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
parse_init_verbs(*codecp, buf);
|
|
}
|
|
|
|
static void parse_hint_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
parse_hints(*codecp, buf);
|
|
}
|
|
|
|
static void parse_model_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
kfree((*codecp)->modelname);
|
|
(*codecp)->modelname = kstrdup(buf, GFP_KERNEL);
|
|
}
|
|
|
|
static void parse_chip_name_mode(char *buf, struct hda_bus *bus,
|
|
struct hda_codec **codecp)
|
|
{
|
|
snd_hda_codec_set_name(*codecp, buf);
|
|
}
|
|
|
|
#define DEFINE_PARSE_ID_MODE(name) \
|
|
static void parse_##name##_mode(char *buf, struct hda_bus *bus, \
|
|
struct hda_codec **codecp) \
|
|
{ \
|
|
unsigned long val; \
|
|
if (!kstrtoul(buf, 0, &val)) \
|
|
(*codecp)->core.name = val; \
|
|
}
|
|
|
|
DEFINE_PARSE_ID_MODE(vendor_id);
|
|
DEFINE_PARSE_ID_MODE(subsystem_id);
|
|
DEFINE_PARSE_ID_MODE(revision_id);
|
|
|
|
|
|
struct hda_patch_item {
|
|
const char *tag;
|
|
const char *alias;
|
|
void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc);
|
|
};
|
|
|
|
static const struct hda_patch_item patch_items[NUM_LINE_MODES] = {
|
|
[LINE_MODE_CODEC] = {
|
|
.tag = "[codec]",
|
|
.parser = parse_codec_mode,
|
|
},
|
|
[LINE_MODE_MODEL] = {
|
|
.tag = "[model]",
|
|
.parser = parse_model_mode,
|
|
},
|
|
[LINE_MODE_VERB] = {
|
|
.tag = "[verb]",
|
|
.alias = "[init_verbs]",
|
|
.parser = parse_verb_mode,
|
|
},
|
|
[LINE_MODE_PINCFG] = {
|
|
.tag = "[pincfg]",
|
|
.alias = "[user_pin_configs]",
|
|
.parser = parse_pincfg_mode,
|
|
},
|
|
[LINE_MODE_HINT] = {
|
|
.tag = "[hint]",
|
|
.alias = "[hints]",
|
|
.parser = parse_hint_mode
|
|
},
|
|
[LINE_MODE_VENDOR_ID] = {
|
|
.tag = "[vendor_id]",
|
|
.parser = parse_vendor_id_mode,
|
|
},
|
|
[LINE_MODE_SUBSYSTEM_ID] = {
|
|
.tag = "[subsystem_id]",
|
|
.parser = parse_subsystem_id_mode,
|
|
},
|
|
[LINE_MODE_REVISION_ID] = {
|
|
.tag = "[revision_id]",
|
|
.parser = parse_revision_id_mode,
|
|
},
|
|
[LINE_MODE_CHIP_NAME] = {
|
|
.tag = "[chip_name]",
|
|
.parser = parse_chip_name_mode,
|
|
},
|
|
};
|
|
|
|
/* check the line starting with '[' -- change the parser mode accodingly */
|
|
static int parse_line_mode(char *buf, struct hda_bus *bus)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(patch_items); i++) {
|
|
if (!patch_items[i].tag)
|
|
continue;
|
|
if (strmatch(buf, patch_items[i].tag))
|
|
return i;
|
|
if (patch_items[i].alias && strmatch(buf, patch_items[i].alias))
|
|
return i;
|
|
}
|
|
return LINE_MODE_NONE;
|
|
}
|
|
|
|
/* copy one line from the buffer in fw, and update the fields in fw
|
|
* return zero if it reaches to the end of the buffer, or non-zero
|
|
* if successfully copied a line
|
|
*
|
|
* the spaces at the beginning and the end of the line are stripped
|
|
*/
|
|
static int get_line_from_fw(char *buf, int size, size_t *fw_size_p,
|
|
const void **fw_data_p)
|
|
{
|
|
int len;
|
|
size_t fw_size = *fw_size_p;
|
|
const char *p = *fw_data_p;
|
|
|
|
while (isspace(*p) && fw_size) {
|
|
p++;
|
|
fw_size--;
|
|
}
|
|
if (!fw_size)
|
|
return 0;
|
|
|
|
for (len = 0; len < fw_size; len++) {
|
|
if (!*p)
|
|
break;
|
|
if (*p == '\n') {
|
|
p++;
|
|
len++;
|
|
break;
|
|
}
|
|
if (len < size)
|
|
*buf++ = *p++;
|
|
}
|
|
*buf = 0;
|
|
*fw_size_p = fw_size - len;
|
|
*fw_data_p = p;
|
|
remove_trail_spaces(buf);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* snd_hda_load_patch - load a "patch" firmware file and parse it
|
|
* @bus: HD-audio bus
|
|
* @fw_size: the firmware byte size
|
|
* @fw_buf: the firmware data
|
|
*/
|
|
int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf)
|
|
{
|
|
char buf[128];
|
|
struct hda_codec *codec;
|
|
int line_mode;
|
|
|
|
line_mode = LINE_MODE_NONE;
|
|
codec = NULL;
|
|
while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) {
|
|
if (!*buf || *buf == '#' || *buf == '\n')
|
|
continue;
|
|
if (*buf == '[')
|
|
line_mode = parse_line_mode(buf, bus);
|
|
else if (patch_items[line_mode].parser &&
|
|
(codec || line_mode <= LINE_MODE_CODEC))
|
|
patch_items[line_mode].parser(buf, bus, &codec);
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hda_load_patch);
|
|
#endif /* CONFIG_SND_HDA_PATCH_LOADER */
|
|
|
|
/*
|
|
* sysfs entries
|
|
*/
|
|
static struct attribute *hda_dev_attrs[] = {
|
|
&dev_attr_vendor_id.attr,
|
|
&dev_attr_subsystem_id.attr,
|
|
&dev_attr_revision_id.attr,
|
|
&dev_attr_afg.attr,
|
|
&dev_attr_mfg.attr,
|
|
&dev_attr_vendor_name.attr,
|
|
&dev_attr_chip_name.attr,
|
|
&dev_attr_modelname.attr,
|
|
&dev_attr_init_pin_configs.attr,
|
|
&dev_attr_driver_pin_configs.attr,
|
|
#ifdef CONFIG_PM
|
|
&dev_attr_power_on_acct.attr,
|
|
&dev_attr_power_off_acct.attr,
|
|
#endif
|
|
#ifdef CONFIG_SND_HDA_RECONFIG
|
|
&dev_attr_init_verbs.attr,
|
|
&dev_attr_hints.attr,
|
|
&dev_attr_user_pin_configs.attr,
|
|
&dev_attr_reconfig.attr,
|
|
&dev_attr_clear.attr,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group hda_dev_attr_group = {
|
|
.attrs = hda_dev_attrs,
|
|
};
|
|
|
|
const struct attribute_group *snd_hda_dev_attr_groups[] = {
|
|
&hda_dev_attr_group,
|
|
NULL
|
|
};
|
|
|
|
void snd_hda_sysfs_init(struct hda_codec *codec)
|
|
{
|
|
mutex_init(&codec->user_mutex);
|
|
#ifdef CONFIG_SND_HDA_RECONFIG
|
|
snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32);
|
|
snd_array_init(&codec->hints, sizeof(struct hda_hint), 32);
|
|
snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16);
|
|
#endif
|
|
}
|
|
|
|
void snd_hda_sysfs_clear(struct hda_codec *codec)
|
|
{
|
|
#ifdef CONFIG_SND_HDA_RECONFIG
|
|
struct hda_hint *hint;
|
|
int i;
|
|
|
|
/* clear init verbs */
|
|
snd_array_free(&codec->init_verbs);
|
|
/* clear hints */
|
|
snd_array_for_each(&codec->hints, i, hint) {
|
|
kfree(hint->key); /* we don't need to free hint->val */
|
|
}
|
|
snd_array_free(&codec->hints);
|
|
snd_array_free(&codec->user_pins);
|
|
#endif
|
|
}
|