提交 648a8d27 编写于 作者: T Takashi Iwai

ALSA: hda - Add sysfs to codec object, too

We have currently sysfs attributes for each hwdep, but basically these
should belong to the codec itself, per se.  Let's add them to the
codec object while keeping them for hwdep as is for compatibility.

While we are at it, split the sysfs-related stuff into a separate
source file, hda_sysfs.c, and keep only the stuff necessary for hwdep
in hda_hwdep.c.
Signed-off-by: NTakashi Iwai <tiwai@suse.de>
上级 13aeaf68
......@@ -41,7 +41,6 @@ config SND_HDA_HWDEP
config SND_HDA_RECONFIG
bool "Allow dynamic codec reconfiguration"
depends on SND_HDA_HWDEP
help
Say Y here to enable the HD-audio codec re-configuration feature.
This adds the sysfs interfaces to allow user to clear the whole
......@@ -76,7 +75,6 @@ config SND_HDA_INPUT_JACK
config SND_HDA_PATCH_LOADER
bool "Support initialization patch loading for HD-audio"
select FW_LOADER
select SND_HDA_HWDEP
select SND_HDA_RECONFIG
help
Say Y here to allow the HD-audio driver to load a pseudo
......@@ -84,8 +82,6 @@ config SND_HDA_PATCH_LOADER
start up. The "patch" file can be specified via patch module
option, such as patch=hda-init.
This option turns on hwdep and reconfig features automatically.
config SND_HDA_CODEC_REALTEK
tristate "Build Realtek HD-audio codec support"
select SND_HDA_GENERIC
......
......@@ -2,7 +2,7 @@ snd-hda-intel-objs := hda_intel.o
# for haswell power well
snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o
snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o
snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
snd-hda-codec-$(CONFIG_PROC_FS) += hda_proc.o
snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o
......
......@@ -1170,7 +1170,7 @@ unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_pincfg *pin;
#ifdef CONFIG_SND_HDA_HWDEP
#ifdef CONFIG_SND_HDA_RECONFIG
{
unsigned int cfg = 0;
mutex_lock(&codec->user_mutex);
......@@ -1285,7 +1285,7 @@ static void free_hda_cache(struct hda_cache_rec *cache);
static void free_init_pincfgs(struct hda_codec *codec)
{
snd_array_free(&codec->driver_pins);
#ifdef CONFIG_SND_HDA_HWDEP
#ifdef CONFIG_SND_HDA_RECONFIG
snd_array_free(&codec->user_pins);
#endif
snd_array_free(&codec->init_pins);
......@@ -1359,6 +1359,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
if (codec->patch_ops.free)
codec->patch_ops.free(codec);
hda_call_pm_notify(codec, false); /* cancel leftover refcounts */
snd_hda_sysfs_clear(codec);
unload_parser(codec);
module_put(codec->owner);
free_hda_cache(&codec->amp_cache);
......@@ -1447,8 +1448,10 @@ int snd_hda_codec_new(struct hda_bus *bus,
codec->dev.parent = &bus->card->card_dev;
codec->dev.class = sound_class;
codec->dev.release = snd_hda_codec_dev_release;
codec->dev.groups = snd_hda_dev_attr_groups;
dev_set_name(&codec->dev, "hdaudioC%dD%d", bus->card->number,
codec_addr);
dev_set_drvdata(&codec->dev, codec); /* for sysfs */
codec->bus = bus;
codec->addr = codec_addr;
......@@ -1480,6 +1483,8 @@ int snd_hda_codec_new(struct hda_bus *bus,
hda_keep_power_on(codec);
#endif
snd_hda_sysfs_init(codec);
if (codec->bus->modelname) {
codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
if (!codec->modelname) {
......@@ -4038,7 +4043,7 @@ static void sync_power_up_states(struct hda_codec *codec)
}
}
#ifdef CONFIG_SND_HDA_HWDEP
#ifdef CONFIG_SND_HDA_RECONFIG
/* execute additional init verbs */
static void hda_exec_init_verbs(struct hda_codec *codec)
{
......
......@@ -333,14 +333,17 @@ struct hda_codec {
struct snd_array driver_pins; /* pin configs set by codec parser */
struct snd_array cvt_setups; /* audio convert setups */
#ifdef CONFIG_SND_HDA_HWDEP
#ifdef CONFIG_SND_HDA_RECONFIG
struct mutex user_mutex;
struct snd_hwdep *hwdep; /* assigned hwdep device */
struct snd_array init_verbs; /* additional init verbs */
struct snd_array hints; /* additional hints */
struct snd_array user_pins; /* default pin configs to override */
#endif
#ifdef CONFIG_SND_HDA_HWDEP
struct snd_hwdep *hwdep; /* assigned hwdep device */
#endif
/* misc flags */
unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each
* status change
......
......@@ -21,22 +21,12 @@
#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 "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 */
};
/*
* write/read an out-of-bound verb
*/
......@@ -104,28 +94,6 @@ static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
return 0;
}
static void clear_hwdep_elements(struct hda_codec *codec)
{
int i;
/* clear init verbs */
snd_array_free(&codec->init_verbs);
/* clear hints */
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
kfree(hint->key); /* we don't need to free hint->val */
}
snd_array_free(&codec->hints);
snd_array_free(&codec->user_pins);
}
static void hwdep_free(struct snd_hwdep *hwdep)
{
clear_hwdep_elements(hwdep->private_data);
}
static const struct attribute_group *snd_hda_dev_attr_groups[];
int snd_hda_create_hwdep(struct hda_codec *codec)
{
char hwname[16];
......@@ -140,7 +108,6 @@ int snd_hda_create_hwdep(struct hda_codec *codec)
sprintf(hwdep->name, "HDA Codec %d", codec->addr);
hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
hwdep->private_data = codec;
hwdep->private_free = hwdep_free;
hwdep->exclusive = 1;
hwdep->groups = snd_hda_dev_attr_groups;
......@@ -150,729 +117,8 @@ int snd_hda_create_hwdep(struct hda_codec *codec)
hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
#endif
mutex_init(&codec->user_mutex);
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);
/* link to codec */
hwdep->dev = &codec->dev;
return 0;
}
#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 */
#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) {
snd_printk(KERN_ERR "The codec is being used, can't free.\n");
return err;
}
clear_hwdep_elements(codec);
return 0;
}
static int reconfig_codec(struct hda_codec *codec)
{
int err;
snd_hda_power_up(codec);
snd_printk(KERN_INFO "hda-codec: reconfiguring\n");
err = snd_hda_codec_reset(codec);
if (err < 0) {
snd_printk(KERN_ERR
"The codec is being used, can't reconfigure.\n");
goto error;
}
err = snd_hda_codec_configure(codec);
if (err < 0)
goto error;
/* rebuild PCMs */
err = snd_hda_codec_build_pcms(codec);
if (err < 0)
goto error;
/* rebuild mixers */
err = snd_hda_codec_build_controls(codec);
if (err < 0)
goto error;
err = snd_card_register(codec->bus->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_SHOW(type) \
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->type); \
}
#define CODEC_INFO_STR_SHOW(type) \
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->type ? codec->type : ""); \
}
CODEC_INFO_SHOW(vendor_id);
CODEC_INFO_SHOW(subsystem_id);
CODEC_INFO_SHOW(revision_id);
CODEC_INFO_SHOW(afg);
CODEC_INFO_SHOW(mfg);
CODEC_INFO_STR_SHOW(vendor_name);
CODEC_INFO_STR_SHOW(chip_name);
CODEC_INFO_STR_SHOW(modelname);
#define CODEC_INFO_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); \
unsigned long val; \
int err = kstrtoul(buf, 0, &val); \
if (err < 0) \
return err; \
codec->type = val; \
return count; \
}
#define CODEC_INFO_STR_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); \
char *s = kstrndup_noeol(buf, 64); \
if (!s) \
return -ENOMEM; \
kfree(codec->type); \
codec->type = s; \
return count; \
}
CODEC_INFO_STORE(vendor_id);
CODEC_INFO_STORE(subsystem_id);
CODEC_INFO_STORE(revision_id);
CODEC_INFO_STR_STORE(vendor_name);
CODEC_INFO_STR_STORE(chip_name);
CODEC_INFO_STR_STORE(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);
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < codec->init_verbs.used; i++) {
struct hda_verb *v = snd_array_elem(&codec->init_verbs, i);
len += snprintf(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, &param) != 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);
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
len += snprintf(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)
{
int i;
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
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 pin_configs_show(struct hda_codec *codec,
struct snd_array *list,
char *buf)
{
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < list->used; i++) {
struct hda_pincfg *pin = snd_array_elem(list, i);
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 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);
}
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);
}
#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;
}
static DEVICE_ATTR_RW(vendor_id);
static DEVICE_ATTR_RW(subsystem_id);
static DEVICE_ATTR_RW(revision_id);
static DEVICE_ATTR_RO(afg);
static DEVICE_ATTR_RO(mfg);
static DEVICE_ATTR_RW(vendor_name);
static DEVICE_ATTR_RW(chip_name);
static DEVICE_ATTR_RW(modelname);
static DEVICE_ATTR_RW(init_verbs);
static DEVICE_ATTR_RW(hints);
static DEVICE_ATTR_RO(init_pin_configs);
static DEVICE_ATTR_RW(user_pin_configs);
static DEVICE_ATTR_RO(driver_pin_configs);
static DEVICE_ATTR_WO(reconfig);
static DEVICE_ATTR_WO(clear);
/*
* Look for hint string
*/
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);
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);
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 */
#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 strnicmp(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_entry(codec, &bus->codec_list, list) {
if ((vendorid <= 0 || codec->vendor_id == vendorid) &&
(subid <= 0 || codec->subsystem_id == subid) &&
codec->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)
{
kfree((*codecp)->chip_name);
(*codecp)->chip_name = kstrdup(buf, GFP_KERNEL);
}
#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)->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 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;
}
/*
* load a "patch" firmware file and parse it
*/
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[] = {
#ifdef CONFIG_PM
&dev_attr_power_on_acct.attr,
&dev_attr_power_off_acct.attr,
#endif
#ifdef CONFIG_SND_HDA_RECONFIG
&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_verbs.attr,
&dev_attr_hints.attr,
&dev_attr_init_pin_configs.attr,
&dev_attr_user_pin_configs.attr,
&dev_attr_driver_pin_configs.attr,
&dev_attr_reconfig.attr,
&dev_attr_clear.attr,
#endif
NULL
};
static struct attribute_group hda_dev_attr_group = {
.attrs = hda_dev_attrs,
};
static const struct attribute_group *snd_hda_dev_attr_groups[] = {
&hda_dev_attr_group,
NULL
};
......@@ -597,6 +597,11 @@ int snd_hda_create_hwdep(struct hda_codec *codec);
static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
#endif
void snd_hda_sysfs_init(struct hda_codec *codec);
void snd_hda_sysfs_clear(struct hda_codec *codec);
extern const struct attribute_group *snd_hda_dev_attr_groups[];
#ifdef CONFIG_SND_HDA_RECONFIG
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
......
/*
* 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 "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 */
#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) {
snd_printk(KERN_ERR "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);
snd_printk(KERN_INFO "hda-codec: reconfiguring\n");
err = snd_hda_codec_reset(codec);
if (err < 0) {
snd_printk(KERN_ERR
"The codec is being used, can't reconfigure.\n");
goto error;
}
err = snd_hda_codec_configure(codec);
if (err < 0)
goto error;
/* rebuild PCMs */
err = snd_hda_codec_build_pcms(codec);
if (err < 0)
goto error;
/* rebuild mixers */
err = snd_hda_codec_build_controls(codec);
if (err < 0)
goto error;
err = snd_card_register(codec->bus->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_SHOW(type) \
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->type); \
}
#define CODEC_INFO_STR_SHOW(type) \
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->type ? codec->type : ""); \
}
CODEC_INFO_SHOW(vendor_id);
CODEC_INFO_SHOW(subsystem_id);
CODEC_INFO_SHOW(revision_id);
CODEC_INFO_SHOW(afg);
CODEC_INFO_SHOW(mfg);
CODEC_INFO_STR_SHOW(vendor_name);
CODEC_INFO_STR_SHOW(chip_name);
CODEC_INFO_STR_SHOW(modelname);
#define CODEC_INFO_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); \
unsigned long val; \
int err = kstrtoul(buf, 0, &val); \
if (err < 0) \
return err; \
codec->type = val; \
return count; \
}
#define CODEC_INFO_STR_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); \
char *s = kstrndup_noeol(buf, 64); \
if (!s) \
return -ENOMEM; \
kfree(codec->type); \
codec->type = s; \
return count; \
}
CODEC_INFO_STORE(vendor_id);
CODEC_INFO_STORE(subsystem_id);
CODEC_INFO_STORE(revision_id);
CODEC_INFO_STR_STORE(vendor_name);
CODEC_INFO_STR_STORE(chip_name);
CODEC_INFO_STR_STORE(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);
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < codec->init_verbs.used; i++) {
struct hda_verb *v = snd_array_elem(&codec->init_verbs, i);
len += snprintf(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, &param) != 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);
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
len += snprintf(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)
{
int i;
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
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 pin_configs_show(struct hda_codec *codec,
struct snd_array *list,
char *buf)
{
int i, len = 0;
mutex_lock(&codec->user_mutex);
for (i = 0; i < list->used; i++) {
struct hda_pincfg *pin = snd_array_elem(list, i);
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 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);
}
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);
}
#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;
}
static DEVICE_ATTR_RW(vendor_id);
static DEVICE_ATTR_RW(subsystem_id);
static DEVICE_ATTR_RW(revision_id);
static DEVICE_ATTR_RO(afg);
static DEVICE_ATTR_RO(mfg);
static DEVICE_ATTR_RW(vendor_name);
static DEVICE_ATTR_RW(chip_name);
static DEVICE_ATTR_RW(modelname);
static DEVICE_ATTR_RW(init_verbs);
static DEVICE_ATTR_RW(hints);
static DEVICE_ATTR_RO(init_pin_configs);
static DEVICE_ATTR_RW(user_pin_configs);
static DEVICE_ATTR_RO(driver_pin_configs);
static DEVICE_ATTR_WO(reconfig);
static DEVICE_ATTR_WO(clear);
/*
* Look for hint string
*/
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);
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);
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 */
#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 strnicmp(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_entry(codec, &bus->codec_list, list) {
if ((vendorid <= 0 || codec->vendor_id == vendorid) &&
(subid <= 0 || codec->subsystem_id == subid) &&
codec->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)
{
kfree((*codecp)->chip_name);
(*codecp)->chip_name = kstrdup(buf, GFP_KERNEL);
}
#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)->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 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;
}
/*
* load a "patch" firmware file and parse it
*/
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[] = {
#ifdef CONFIG_PM
&dev_attr_power_on_acct.attr,
&dev_attr_power_off_acct.attr,
#endif
#ifdef CONFIG_SND_HDA_RECONFIG
&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_verbs.attr,
&dev_attr_hints.attr,
&dev_attr_init_pin_configs.attr,
&dev_attr_user_pin_configs.attr,
&dev_attr_driver_pin_configs.attr,
&dev_attr_reconfig.attr,
&dev_attr_clear.attr,
#endif
NULL
};
static 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)
{
#ifdef CONFIG_SND_HDA_RECONFIG
mutex_init(&codec->user_mutex);
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
int i;
/* clear init verbs */
snd_array_free(&codec->init_verbs);
/* clear hints */
for (i = 0; i < codec->hints.used; i++) {
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
kfree(hint->key); /* we don't need to free hint->val */
}
snd_array_free(&codec->hints);
snd_array_free(&codec->user_pins);
#endif
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册