| Index: sound/soc/soc-core.c
|
| diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
|
| index c085e55d44864b31a29f648d4545c6b570055be6..64befac3f9c3a276f3f75f321c074a1e3d960e49 100644
|
| --- a/sound/soc/soc-core.c
|
| +++ b/sound/soc/soc-core.c
|
| @@ -33,20 +33,23 @@
|
| #include <linux/slab.h>
|
| #include <sound/ac97_codec.h>
|
| #include <sound/core.h>
|
| +#include <sound/jack.h>
|
| #include <sound/pcm.h>
|
| #include <sound/pcm_params.h>
|
| #include <sound/soc.h>
|
| -#include <sound/soc-dapm.h>
|
| #include <sound/initval.h>
|
|
|
| +#define CREATE_TRACE_POINTS
|
| +#include <trace/events/asoc.h>
|
| +
|
| #define NAME_SIZE 32
|
|
|
| static DEFINE_MUTEX(pcm_mutex);
|
| static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
|
|
|
| #ifdef CONFIG_DEBUG_FS
|
| -struct dentry *asoc_debugfs_root;
|
| -EXPORT_SYMBOL_GPL(asoc_debugfs_root);
|
| +struct dentry *snd_soc_debugfs_root;
|
| +EXPORT_SYMBOL_GPL(snd_soc_debugfs_root);
|
| #endif
|
|
|
| static DEFINE_MUTEX(client_mutex);
|
| @@ -55,8 +58,6 @@ static LIST_HEAD(dai_list);
|
| static LIST_HEAD(platform_list);
|
| static LIST_HEAD(codec_list);
|
|
|
| -static int snd_soc_register_card(struct snd_soc_card *card);
|
| -static int snd_soc_unregister_card(struct snd_soc_card *card);
|
| static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
|
|
|
| /*
|
| @@ -68,29 +69,73 @@ static int pmdown_time = 5000;
|
| module_param(pmdown_time, int, 0);
|
| MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
|
|
|
| -/*
|
| - * This function forces any delayed work to be queued and run.
|
| - */
|
| -static int run_delayed_work(struct delayed_work *dwork)
|
| +/* returns the minimum number of bytes needed to represent
|
| + * a particular given value */
|
| +static int min_bytes_needed(unsigned long val)
|
| +{
|
| + int c = 0;
|
| + int i;
|
| +
|
| + for (i = (sizeof val * 8) - 1; i >= 0; --i, ++c)
|
| + if (val & (1UL << i))
|
| + break;
|
| + c = (sizeof val * 8) - c;
|
| + if (!c || (c % 8))
|
| + c = (c + 8) / 8;
|
| + else
|
| + c /= 8;
|
| + return c;
|
| +}
|
| +
|
| +/* fill buf which is 'len' bytes with a formatted
|
| + * string of the form 'reg: value\n' */
|
| +static int format_register_str(struct snd_soc_codec *codec,
|
| + unsigned int reg, char *buf, size_t len)
|
| {
|
| + int wordsize = codec->driver->reg_word_size * 2;
|
| + int regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
|
| int ret;
|
| + char tmpbuf[len + 1];
|
| + char regbuf[regsize + 1];
|
|
|
| - /* cancel any work waiting to be queued. */
|
| - ret = cancel_delayed_work(dwork);
|
| + /* since tmpbuf is allocated on the stack, warn the callers if they
|
| + * try to abuse this function */
|
| + WARN_ON(len > 63);
|
|
|
| - /* if there was any work waiting then we run it now and
|
| - * wait for it's completion */
|
| - if (ret) {
|
| - schedule_delayed_work(dwork, 0);
|
| - flush_scheduled_work();
|
| + /* +2 for ': ' and + 1 for '\n' */
|
| + if (wordsize + regsize + 2 + 1 != len)
|
| + return -EINVAL;
|
| +
|
| + ret = snd_soc_read(codec , reg);
|
| + if (ret < 0) {
|
| + memset(regbuf, 'X', regsize);
|
| + regbuf[regsize] = '\0';
|
| + } else {
|
| + snprintf(regbuf, regsize + 1, "%.*x", regsize, ret);
|
| }
|
| - return ret;
|
| +
|
| + /* prepare the buffer */
|
| + snprintf(tmpbuf, len + 1, "%.*x: %s\n", wordsize, reg, regbuf);
|
| + /* copy it back to the caller without the '\0' */
|
| + memcpy(buf, tmpbuf, len);
|
| +
|
| + return 0;
|
| }
|
|
|
| /* codec register dump */
|
| -static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
|
| +static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf,
|
| + size_t count, loff_t pos)
|
| {
|
| - int ret, i, step = 1, count = 0;
|
| + int i, step = 1;
|
| + int wordsize, regsize;
|
| + int len;
|
| + size_t total = 0;
|
| + loff_t p = 0;
|
| +
|
| + wordsize = codec->driver->reg_word_size * 2;
|
| + regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
|
| +
|
| + len = wordsize + regsize + 2 + 1;
|
|
|
| if (!codec->driver->reg_cache_size)
|
| return 0;
|
| @@ -98,55 +143,37 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
|
| if (codec->driver->reg_cache_step)
|
| step = codec->driver->reg_cache_step;
|
|
|
| - count += sprintf(buf, "%s registers\n", codec->name);
|
| for (i = 0; i < codec->driver->reg_cache_size; i += step) {
|
| - if (codec->driver->readable_register && !codec->driver->readable_register(i))
|
| + if (codec->readable_register && !codec->readable_register(codec, i))
|
| continue;
|
| -
|
| - count += sprintf(buf + count, "%2x: ", i);
|
| - if (count >= PAGE_SIZE - 1)
|
| - break;
|
| -
|
| if (codec->driver->display_register) {
|
| count += codec->driver->display_register(codec, buf + count,
|
| PAGE_SIZE - count, i);
|
| } else {
|
| - /* If the read fails it's almost certainly due to
|
| - * the register being volatile and the device being
|
| - * powered off.
|
| - */
|
| - ret = codec->driver->read(codec, i);
|
| - if (ret >= 0)
|
| - count += snprintf(buf + count,
|
| - PAGE_SIZE - count,
|
| - "%4x", ret);
|
| - else
|
| - count += snprintf(buf + count,
|
| - PAGE_SIZE - count,
|
| - "<no data: %d>", ret);
|
| + /* only support larger than PAGE_SIZE bytes debugfs
|
| + * entries for the default case */
|
| + if (p >= pos) {
|
| + if (total + len >= count - 1)
|
| + break;
|
| + format_register_str(codec, i, buf + total, len);
|
| + total += len;
|
| + }
|
| + p += len;
|
| }
|
| -
|
| - if (count >= PAGE_SIZE - 1)
|
| - break;
|
| -
|
| - count += snprintf(buf + count, PAGE_SIZE - count, "\n");
|
| - if (count >= PAGE_SIZE - 1)
|
| - break;
|
| }
|
|
|
| - /* Truncate count; min() would cause a warning */
|
| - if (count >= PAGE_SIZE)
|
| - count = PAGE_SIZE - 1;
|
| + total = min(total, count - 1);
|
|
|
| - return count;
|
| + return total;
|
| }
|
| +
|
| static ssize_t codec_reg_show(struct device *dev,
|
| struct device_attribute *attr, char *buf)
|
| {
|
| struct snd_soc_pcm_runtime *rtd =
|
| container_of(dev, struct snd_soc_pcm_runtime, dev);
|
|
|
| - return soc_codec_reg_show(rtd->codec, buf);
|
| + return soc_codec_reg_show(rtd->codec, buf, PAGE_SIZE, 0);
|
| }
|
|
|
| static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
|
| @@ -185,16 +212,28 @@ static int codec_reg_open_file(struct inode *inode, struct file *file)
|
| }
|
|
|
| static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf,
|
| - size_t count, loff_t *ppos)
|
| + size_t count, loff_t *ppos)
|
| {
|
| ssize_t ret;
|
| struct snd_soc_codec *codec = file->private_data;
|
| - char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
| + char *buf;
|
| +
|
| + if (*ppos < 0 || !count)
|
| + return -EINVAL;
|
| +
|
| + buf = kmalloc(count, GFP_KERNEL);
|
| if (!buf)
|
| return -ENOMEM;
|
| - ret = soc_codec_reg_show(codec, buf);
|
| - if (ret >= 0)
|
| - ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
| +
|
| + ret = soc_codec_reg_show(codec, buf, count, *ppos);
|
| + if (ret >= 0) {
|
| + if (copy_to_user(user_buf, buf, ret)) {
|
| + kfree(buf);
|
| + return -EFAULT;
|
| + }
|
| + *ppos += ret;
|
| + }
|
| +
|
| kfree(buf);
|
| return ret;
|
| }
|
| @@ -226,7 +265,11 @@ static ssize_t codec_reg_write_file(struct file *file,
|
| start++;
|
| if (strict_strtoul(start, 16, &value))
|
| return -EINVAL;
|
| - codec->driver->write(codec, reg, value);
|
| +
|
| + /* Userspace has been fiddling around behind the kernel's back */
|
| + add_taint(TAINT_USER);
|
| +
|
| + snd_soc_write(codec, reg, value);
|
| return buf_size;
|
| }
|
|
|
| @@ -239,14 +282,21 @@ static const struct file_operations codec_reg_fops = {
|
|
|
| static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
|
| {
|
| - codec->debugfs_codec_root = debugfs_create_dir(codec->name ,
|
| - asoc_debugfs_root);
|
| + struct dentry *debugfs_card_root = codec->card->debugfs_card_root;
|
| +
|
| + codec->debugfs_codec_root = debugfs_create_dir(codec->name,
|
| + debugfs_card_root);
|
| if (!codec->debugfs_codec_root) {
|
| printk(KERN_WARNING
|
| "ASoC: Failed to create codec debugfs directory\n");
|
| return;
|
| }
|
|
|
| + debugfs_create_bool("cache_sync", 0444, codec->debugfs_codec_root,
|
| + &codec->cache_sync);
|
| + debugfs_create_bool("cache_only", 0444, codec->debugfs_codec_root,
|
| + &codec->cache_only);
|
| +
|
| codec->debugfs_reg = debugfs_create_file("codec_reg", 0644,
|
| codec->debugfs_codec_root,
|
| codec, &codec_reg_fops);
|
| @@ -254,20 +304,13 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
|
| printk(KERN_WARNING
|
| "ASoC: Failed to create codec register debugfs file\n");
|
|
|
| - codec->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644,
|
| - codec->debugfs_codec_root,
|
| - &codec->pop_time);
|
| - if (!codec->debugfs_pop_time)
|
| - printk(KERN_WARNING
|
| - "Failed to create pop time debugfs file\n");
|
| -
|
| - codec->debugfs_dapm = debugfs_create_dir("dapm",
|
| + codec->dapm.debugfs_dapm = debugfs_create_dir("dapm",
|
| codec->debugfs_codec_root);
|
| - if (!codec->debugfs_dapm)
|
| + if (!codec->dapm.debugfs_dapm)
|
| printk(KERN_WARNING
|
| "Failed to create DAPM debugfs directory\n");
|
|
|
| - snd_soc_dapm_debugfs_init(codec);
|
| + snd_soc_dapm_debugfs_init(&codec->dapm);
|
| }
|
|
|
| static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
|
| @@ -375,6 +418,29 @@ static const struct file_operations platform_list_fops = {
|
| .llseek = default_llseek,/* read accesses f_pos */
|
| };
|
|
|
| +static void soc_init_card_debugfs(struct snd_soc_card *card)
|
| +{
|
| + card->debugfs_card_root = debugfs_create_dir(card->name,
|
| + snd_soc_debugfs_root);
|
| + if (!card->debugfs_card_root) {
|
| + dev_warn(card->dev,
|
| + "ASoC: Failed to create codec debugfs directory\n");
|
| + return;
|
| + }
|
| +
|
| + card->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644,
|
| + card->debugfs_card_root,
|
| + &card->pop_time);
|
| + if (!card->debugfs_pop_time)
|
| + dev_warn(card->dev,
|
| + "Failed to create pop time debugfs file\n");
|
| +}
|
| +
|
| +static void soc_cleanup_card_debugfs(struct snd_soc_card *card)
|
| +{
|
| + debugfs_remove_recursive(card->debugfs_card_root);
|
| +}
|
| +
|
| #else
|
|
|
| static inline void soc_init_codec_debugfs(struct snd_soc_codec *codec)
|
| @@ -384,6 +450,14 @@ static inline void soc_init_codec_debugfs(struct snd_soc_codec *codec)
|
| static inline void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
|
| {
|
| }
|
| +
|
| +static inline void soc_init_card_debugfs(struct snd_soc_card *card)
|
| +{
|
| +}
|
| +
|
| +static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card)
|
| +{
|
| +}
|
| #endif
|
|
|
| #ifdef CONFIG_SND_SOC_AC97_BUS
|
| @@ -498,7 +572,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream)
|
| }
|
| }
|
|
|
| - /* Check that the codec and cpu DAI's are compatible */
|
| + /* Check that the codec and cpu DAIs are compatible */
|
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
| runtime->hw.rate_min =
|
| max(codec_dai_drv->playback.rate_min,
|
| @@ -847,7 +921,7 @@ codec_err:
|
| }
|
|
|
| /*
|
| - * Free's resources allocated by hw_params, can be called multiple times
|
| + * Frees resources allocated by hw_params, can be called multiple times
|
| */
|
| static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
|
| {
|
| @@ -871,7 +945,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
|
| if (platform->driver->ops->hw_free)
|
| platform->driver->ops->hw_free(substream);
|
|
|
| - /* now free hw params for the DAI's */
|
| + /* now free hw params for the DAIs */
|
| if (codec_dai->driver->ops->hw_free)
|
| codec_dai->driver->ops->hw_free(substream, codec_dai);
|
|
|
| @@ -953,12 +1027,12 @@ static struct snd_pcm_ops soc_pcm_ops = {
|
| .pointer = soc_pcm_pointer,
|
| };
|
|
|
| -#ifdef CONFIG_PM
|
| +#ifdef CONFIG_PM_SLEEP
|
| /* powers down audio subsystem for suspend */
|
| -static int soc_suspend(struct device *dev)
|
| +int snd_soc_suspend(struct device *dev)
|
| {
|
| - struct platform_device *pdev = to_platform_device(dev);
|
| - struct snd_soc_card *card = platform_get_drvdata(pdev);
|
| + struct snd_soc_card *card = dev_get_drvdata(dev);
|
| + struct snd_soc_codec *codec;
|
| int i;
|
|
|
| /* If the initialization of this soc device failed, there is no codec
|
| @@ -977,7 +1051,7 @@ static int soc_suspend(struct device *dev)
|
| /* we're going to block userspace touching us until resume completes */
|
| snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot);
|
|
|
| - /* mute any active DAC's */
|
| + /* mute any active DACs */
|
| for (i = 0; i < card->num_rtd; i++) {
|
| struct snd_soc_dai *dai = card->rtd[i].codec_dai;
|
| struct snd_soc_dai_driver *drv = dai->driver;
|
| @@ -998,7 +1072,7 @@ static int soc_suspend(struct device *dev)
|
| }
|
|
|
| if (card->suspend_pre)
|
| - card->suspend_pre(pdev, PMSG_SUSPEND);
|
| + card->suspend_pre(card);
|
|
|
| for (i = 0; i < card->num_rtd; i++) {
|
| struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
|
| @@ -1017,8 +1091,8 @@ static int soc_suspend(struct device *dev)
|
|
|
| /* close any waiting streams and save state */
|
| for (i = 0; i < card->num_rtd; i++) {
|
| - run_delayed_work(&card->rtd[i].delayed_work);
|
| - card->rtd[i].codec->suspend_bias_level = card->rtd[i].codec->bias_level;
|
| + flush_delayed_work_sync(&card->rtd[i].delayed_work);
|
| + card->rtd[i].codec->dapm.suspend_bias_level = card->rtd[i].codec->dapm.bias_level;
|
| }
|
|
|
| for (i = 0; i < card->num_rtd; i++) {
|
| @@ -1037,12 +1111,11 @@ static int soc_suspend(struct device *dev)
|
| }
|
|
|
| /* suspend all CODECs */
|
| - for (i = 0; i < card->num_rtd; i++) {
|
| - struct snd_soc_codec *codec = card->rtd[i].codec;
|
| + list_for_each_entry(codec, &card->codec_dev_list, card_list) {
|
| /* If there are paths active then the CODEC will be held with
|
| * bias _ON and should not be suspended. */
|
| if (!codec->suspended && codec->driver->suspend) {
|
| - switch (codec->bias_level) {
|
| + switch (codec->dapm.bias_level) {
|
| case SND_SOC_BIAS_STANDBY:
|
| case SND_SOC_BIAS_OFF:
|
| codec->driver->suspend(codec, PMSG_SUSPEND);
|
| @@ -1066,10 +1139,11 @@ static int soc_suspend(struct device *dev)
|
| }
|
|
|
| if (card->suspend_post)
|
| - card->suspend_post(pdev, PMSG_SUSPEND);
|
| + card->suspend_post(card);
|
|
|
| return 0;
|
| }
|
| +EXPORT_SYMBOL_GPL(snd_soc_suspend);
|
|
|
| /* deferred resume work, so resume can complete before we finished
|
| * setting our codec back up, which can be very slow on I2C
|
| @@ -1078,7 +1152,7 @@ static void soc_resume_deferred(struct work_struct *work)
|
| {
|
| struct snd_soc_card *card =
|
| container_of(work, struct snd_soc_card, deferred_resume_work);
|
| - struct platform_device *pdev = to_platform_device(card->dev);
|
| + struct snd_soc_codec *codec;
|
| int i;
|
|
|
| /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
|
| @@ -1091,7 +1165,7 @@ static void soc_resume_deferred(struct work_struct *work)
|
| snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2);
|
|
|
| if (card->resume_pre)
|
| - card->resume_pre(pdev);
|
| + card->resume_pre(card);
|
|
|
| /* resume AC97 DAIs */
|
| for (i = 0; i < card->num_rtd; i++) {
|
| @@ -1104,14 +1178,13 @@ static void soc_resume_deferred(struct work_struct *work)
|
| cpu_dai->driver->resume(cpu_dai);
|
| }
|
|
|
| - for (i = 0; i < card->num_rtd; i++) {
|
| - struct snd_soc_codec *codec = card->rtd[i].codec;
|
| + list_for_each_entry(codec, &card->codec_dev_list, card_list) {
|
| /* If the CODEC was idle over suspend then it will have been
|
| * left with bias OFF or STANDBY and suspended so we must now
|
| * resume. Otherwise the suspend was suppressed.
|
| */
|
| if (codec->driver->resume && codec->suspended) {
|
| - switch (codec->bias_level) {
|
| + switch (codec->dapm.bias_level) {
|
| case SND_SOC_BIAS_STANDBY:
|
| case SND_SOC_BIAS_OFF:
|
| codec->driver->resume(codec);
|
| @@ -1167,7 +1240,7 @@ static void soc_resume_deferred(struct work_struct *work)
|
| }
|
|
|
| if (card->resume_post)
|
| - card->resume_post(pdev);
|
| + card->resume_post(card);
|
|
|
| dev_dbg(card->dev, "resume work completed\n");
|
|
|
| @@ -1176,10 +1249,9 @@ static void soc_resume_deferred(struct work_struct *work)
|
| }
|
|
|
| /* powers up audio subsystem after a suspend */
|
| -static int soc_resume(struct device *dev)
|
| +int snd_soc_resume(struct device *dev)
|
| {
|
| - struct platform_device *pdev = to_platform_device(dev);
|
| - struct snd_soc_card *card = platform_get_drvdata(pdev);
|
| + struct snd_soc_card *card = dev_get_drvdata(dev);
|
| int i;
|
|
|
| /* AC97 devices might have other drivers hanging off them so
|
| @@ -1201,9 +1273,10 @@ static int soc_resume(struct device *dev)
|
|
|
| return 0;
|
| }
|
| +EXPORT_SYMBOL_GPL(snd_soc_resume);
|
| #else
|
| -#define soc_suspend NULL
|
| -#define soc_resume NULL
|
| +#define snd_soc_suspend NULL
|
| +#define snd_soc_resume NULL
|
| #endif
|
|
|
| static struct snd_soc_dai_ops null_dai_ops = {
|
| @@ -1250,9 +1323,6 @@ find_codec:
|
| if (!strcmp(codec->name, dai_link->codec_name)) {
|
| rtd->codec = codec;
|
|
|
| - if (!try_module_get(codec->dev->driver->owner))
|
| - return -ENODEV;
|
| -
|
| /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
|
| list_for_each_entry(codec_dai, &dai_list, list) {
|
| if (codec->dev == codec_dai->dev &&
|
| @@ -1278,10 +1348,6 @@ find_platform:
|
| /* no, then find CPU DAI from registered DAIs*/
|
| list_for_each_entry(platform, &platform_list, list) {
|
| if (!strcmp(platform->name, dai_link->platform_name)) {
|
| -
|
| - if (!try_module_get(platform->dev->driver->owner))
|
| - return -ENODEV;
|
| -
|
| rtd->platform = platform;
|
| goto out;
|
| }
|
| @@ -1300,6 +1366,27 @@ out:
|
| return 1;
|
| }
|
|
|
| +static void soc_remove_codec(struct snd_soc_codec *codec)
|
| +{
|
| + int err;
|
| +
|
| + if (codec->driver->remove) {
|
| + err = codec->driver->remove(codec);
|
| + if (err < 0)
|
| + dev_err(codec->dev,
|
| + "asoc: failed to remove %s: %d\n",
|
| + codec->name, err);
|
| + }
|
| +
|
| + /* Make sure all DAPM widgets are freed */
|
| + snd_soc_dapm_free(&codec->dapm);
|
| +
|
| + soc_cleanup_codec_debugfs(codec);
|
| + codec->probed = 0;
|
| + list_del(&codec->card_list);
|
| + module_put(codec->dev->driver->owner);
|
| +}
|
| +
|
| static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
| {
|
| struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
|
| @@ -1311,6 +1398,7 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
| /* unregister the rtd device */
|
| if (rtd->dev_registered) {
|
| device_remove_file(&rtd->dev, &dev_attr_pmdown_time);
|
| + device_remove_file(&rtd->dev, &dev_attr_codec_reg);
|
| device_unregister(&rtd->dev);
|
| rtd->dev_registered = 0;
|
| }
|
| @@ -1339,22 +1427,8 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
| }
|
|
|
| /* remove the CODEC */
|
| - if (codec && codec->probed) {
|
| - if (codec->driver->remove) {
|
| - err = codec->driver->remove(codec);
|
| - if (err < 0)
|
| - printk(KERN_ERR "asoc: failed to remove %s\n", codec->name);
|
| - }
|
| -
|
| - /* Make sure all DAPM widgets are freed */
|
| - snd_soc_dapm_free(codec);
|
| -
|
| - soc_cleanup_codec_debugfs(codec);
|
| - device_remove_file(&rtd->dev, &dev_attr_codec_reg);
|
| - codec->probed = 0;
|
| - list_del(&codec->card_list);
|
| - module_put(codec->dev->driver->owner);
|
| - }
|
| + if (codec && codec->probed)
|
| + soc_remove_codec(codec);
|
|
|
| /* remove the cpu_dai */
|
| if (cpu_dai && cpu_dai->probed) {
|
| @@ -1369,8 +1443,130 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num)
|
| }
|
| }
|
|
|
| +static void soc_set_name_prefix(struct snd_soc_card *card,
|
| + struct snd_soc_codec *codec)
|
| +{
|
| + int i;
|
| +
|
| + if (card->codec_conf == NULL)
|
| + return;
|
| +
|
| + for (i = 0; i < card->num_configs; i++) {
|
| + struct snd_soc_codec_conf *map = &card->codec_conf[i];
|
| + if (map->dev_name && !strcmp(codec->name, map->dev_name)) {
|
| + codec->name_prefix = map->name_prefix;
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +static int soc_probe_codec(struct snd_soc_card *card,
|
| + struct snd_soc_codec *codec)
|
| +{
|
| + int ret = 0;
|
| +
|
| + codec->card = card;
|
| + codec->dapm.card = card;
|
| + soc_set_name_prefix(card, codec);
|
| +
|
| + if (!try_module_get(codec->dev->driver->owner))
|
| + return -ENODEV;
|
| +
|
| + if (codec->driver->probe) {
|
| + ret = codec->driver->probe(codec);
|
| + if (ret < 0) {
|
| + dev_err(codec->dev,
|
| + "asoc: failed to probe CODEC %s: %d\n",
|
| + codec->name, ret);
|
| + goto err_probe;
|
| + }
|
| + }
|
| +
|
| + soc_init_codec_debugfs(codec);
|
| +
|
| + /* mark codec as probed and add to card codec list */
|
| + codec->probed = 1;
|
| + list_add(&codec->card_list, &card->codec_dev_list);
|
| + list_add(&codec->dapm.list, &card->dapm_list);
|
| +
|
| + return 0;
|
| +
|
| +err_probe:
|
| + module_put(codec->dev->driver->owner);
|
| +
|
| + return ret;
|
| +}
|
| +
|
| static void rtd_release(struct device *dev) {}
|
|
|
| +static int soc_post_component_init(struct snd_soc_card *card,
|
| + struct snd_soc_codec *codec,
|
| + int num, int dailess)
|
| +{
|
| + struct snd_soc_dai_link *dai_link = NULL;
|
| + struct snd_soc_aux_dev *aux_dev = NULL;
|
| + struct snd_soc_pcm_runtime *rtd;
|
| + const char *temp, *name;
|
| + int ret = 0;
|
| +
|
| + if (!dailess) {
|
| + dai_link = &card->dai_link[num];
|
| + rtd = &card->rtd[num];
|
| + name = dai_link->name;
|
| + } else {
|
| + aux_dev = &card->aux_dev[num];
|
| + rtd = &card->rtd_aux[num];
|
| + name = aux_dev->name;
|
| + }
|
| + rtd->card = card;
|
| +
|
| + /* machine controls, routes and widgets are not prefixed */
|
| + temp = codec->name_prefix;
|
| + codec->name_prefix = NULL;
|
| +
|
| + /* do machine specific initialization */
|
| + if (!dailess && dai_link->init)
|
| + ret = dai_link->init(rtd);
|
| + else if (dailess && aux_dev->init)
|
| + ret = aux_dev->init(&codec->dapm);
|
| + if (ret < 0) {
|
| + dev_err(card->dev, "asoc: failed to init %s: %d\n", name, ret);
|
| + return ret;
|
| + }
|
| + codec->name_prefix = temp;
|
| +
|
| + /* Make sure all DAPM widgets are instantiated */
|
| + snd_soc_dapm_new_widgets(&codec->dapm);
|
| +
|
| + /* register the rtd device */
|
| + rtd->codec = codec;
|
| + rtd->dev.parent = card->dev;
|
| + rtd->dev.release = rtd_release;
|
| + rtd->dev.init_name = name;
|
| + ret = device_register(&rtd->dev);
|
| + if (ret < 0) {
|
| + dev_err(card->dev,
|
| + "asoc: failed to register runtime device: %d\n", ret);
|
| + return ret;
|
| + }
|
| + rtd->dev_registered = 1;
|
| +
|
| + /* add DAPM sysfs entries for this codec */
|
| + ret = snd_soc_dapm_sys_add(&rtd->dev);
|
| + if (ret < 0)
|
| + dev_err(codec->dev,
|
| + "asoc: failed to add codec dapm sysfs entries: %d\n",
|
| + ret);
|
| +
|
| + /* add codec sysfs entries */
|
| + ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
|
| + if (ret < 0)
|
| + dev_err(codec->dev,
|
| + "asoc: failed to add codec sysfs files: %d\n", ret);
|
| +
|
| + return 0;
|
| +}
|
| +
|
| static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
| {
|
| struct snd_soc_dai_link *dai_link = &card->dai_link[num];
|
| @@ -1384,10 +1580,7 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
|
|
| /* config components */
|
| codec_dai->codec = codec;
|
| - codec->card = card;
|
| cpu_dai->platform = platform;
|
| - rtd->card = card;
|
| - rtd->dev.parent = card->dev;
|
| codec_dai->card = card;
|
| cpu_dai->card = card;
|
|
|
| @@ -1411,29 +1604,22 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
|
|
| /* probe the CODEC */
|
| if (!codec->probed) {
|
| - if (codec->driver->probe) {
|
| - ret = codec->driver->probe(codec);
|
| - if (ret < 0) {
|
| - printk(KERN_ERR "asoc: failed to probe CODEC %s\n",
|
| - codec->name);
|
| - return ret;
|
| - }
|
| - }
|
| -
|
| - soc_init_codec_debugfs(codec);
|
| -
|
| - /* mark codec as probed and add to card codec list */
|
| - codec->probed = 1;
|
| - list_add(&codec->card_list, &card->codec_dev_list);
|
| + ret = soc_probe_codec(card, codec);
|
| + if (ret < 0)
|
| + return ret;
|
| }
|
|
|
| /* probe the platform */
|
| if (!platform->probed) {
|
| + if (!try_module_get(platform->dev->driver->owner))
|
| + return -ENODEV;
|
| +
|
| if (platform->driver->probe) {
|
| ret = platform->driver->probe(platform);
|
| if (ret < 0) {
|
| printk(KERN_ERR "asoc: failed to probe platform %s\n",
|
| platform->name);
|
| + module_put(platform->dev->driver->owner);
|
| return ret;
|
| }
|
| }
|
| @@ -1461,43 +1647,14 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
|
| /* DAPM dai link stream work */
|
| INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
|
|
|
| - /* now that all clients have probed, initialise the DAI link */
|
| - if (dai_link->init) {
|
| - ret = dai_link->init(rtd);
|
| - if (ret < 0) {
|
| - printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
|
| - return ret;
|
| - }
|
| - }
|
| -
|
| - /* Make sure all DAPM widgets are instantiated */
|
| - snd_soc_dapm_new_widgets(codec);
|
| - snd_soc_dapm_sync(codec);
|
| -
|
| - /* register the rtd device */
|
| - rtd->dev.release = rtd_release;
|
| - rtd->dev.init_name = dai_link->name;
|
| - ret = device_register(&rtd->dev);
|
| - if (ret < 0) {
|
| - printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
|
| + ret = soc_post_component_init(card, codec, num, 0);
|
| + if (ret)
|
| return ret;
|
| - }
|
|
|
| - rtd->dev_registered = 1;
|
| ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
|
| if (ret < 0)
|
| printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
|
|
|
| - /* add DAPM sysfs entries for this codec */
|
| - ret = snd_soc_dapm_sys_add(&rtd->dev);
|
| - if (ret < 0)
|
| - printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");
|
| -
|
| - /* add codec sysfs entries */
|
| - ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
|
| - if (ret < 0)
|
| - printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");
|
| -
|
| /* create the pcm */
|
| ret = soc_new_pcm(rtd, num);
|
| if (ret < 0) {
|
| @@ -1552,9 +1709,81 @@ static void soc_unregister_ac97_dai_link(struct snd_soc_codec *codec)
|
| }
|
| #endif
|
|
|
| +static int soc_probe_aux_dev(struct snd_soc_card *card, int num)
|
| +{
|
| + struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num];
|
| + struct snd_soc_codec *codec;
|
| + int ret = -ENODEV;
|
| +
|
| + /* find CODEC from registered CODECs*/
|
| + list_for_each_entry(codec, &codec_list, list) {
|
| + if (!strcmp(codec->name, aux_dev->codec_name)) {
|
| + if (codec->probed) {
|
| + dev_err(codec->dev,
|
| + "asoc: codec already probed");
|
| + ret = -EBUSY;
|
| + goto out;
|
| + }
|
| + goto found;
|
| + }
|
| + }
|
| + /* codec not found */
|
| + dev_err(card->dev, "asoc: codec %s not found", aux_dev->codec_name);
|
| + goto out;
|
| +
|
| +found:
|
| + ret = soc_probe_codec(card, codec);
|
| + if (ret < 0)
|
| + return ret;
|
| +
|
| + ret = soc_post_component_init(card, codec, num, 1);
|
| +
|
| +out:
|
| + return ret;
|
| +}
|
| +
|
| +static void soc_remove_aux_dev(struct snd_soc_card *card, int num)
|
| +{
|
| + struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num];
|
| + struct snd_soc_codec *codec = rtd->codec;
|
| +
|
| + /* unregister the rtd device */
|
| + if (rtd->dev_registered) {
|
| + device_remove_file(&rtd->dev, &dev_attr_codec_reg);
|
| + device_unregister(&rtd->dev);
|
| + rtd->dev_registered = 0;
|
| + }
|
| +
|
| + if (codec && codec->probed)
|
| + soc_remove_codec(codec);
|
| +}
|
| +
|
| +static int snd_soc_init_codec_cache(struct snd_soc_codec *codec,
|
| + enum snd_soc_compress_type compress_type)
|
| +{
|
| + int ret;
|
| +
|
| + if (codec->cache_init)
|
| + return 0;
|
| +
|
| + /* override the compress_type if necessary */
|
| + if (compress_type && codec->compress_type != compress_type)
|
| + codec->compress_type = compress_type;
|
| + ret = snd_soc_cache_init(codec);
|
| + if (ret < 0) {
|
| + dev_err(codec->dev, "Failed to set cache compression type: %d\n",
|
| + ret);
|
| + return ret;
|
| + }
|
| + codec->cache_init = 1;
|
| + return 0;
|
| +}
|
| +
|
| static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| {
|
| - struct platform_device *pdev = to_platform_device(card->dev);
|
| + struct snd_soc_codec *codec;
|
| + struct snd_soc_codec_conf *codec_conf;
|
| + enum snd_soc_compress_type compress_type;
|
| int ret, i;
|
|
|
| mutex_lock(&card->mutex);
|
| @@ -1574,6 +1803,29 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| return;
|
| }
|
|
|
| + /* initialize the register cache for each available codec */
|
| + list_for_each_entry(codec, &codec_list, list) {
|
| + if (codec->cache_init)
|
| + continue;
|
| + /* by default we don't override the compress_type */
|
| + compress_type = 0;
|
| + /* check to see if we need to override the compress_type */
|
| + for (i = 0; i < card->num_configs; ++i) {
|
| + codec_conf = &card->codec_conf[i];
|
| + if (!strcmp(codec->name, codec_conf->dev_name)) {
|
| + compress_type = codec_conf->compress_type;
|
| + if (compress_type && compress_type
|
| + != codec->compress_type)
|
| + break;
|
| + }
|
| + }
|
| + ret = snd_soc_init_codec_cache(codec, compress_type);
|
| + if (ret < 0) {
|
| + mutex_unlock(&card->mutex);
|
| + return;
|
| + }
|
| + }
|
| +
|
| /* card bind complete so register a sound card */
|
| ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
| card->owner, 0, &card->snd_card);
|
| @@ -1585,14 +1837,14 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| }
|
| card->snd_card->dev = card->dev;
|
|
|
| -#ifdef CONFIG_PM
|
| +#ifdef CONFIG_PM_SLEEP
|
| /* deferred resume work */
|
| INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
|
| #endif
|
|
|
| /* initialise the sound card only once */
|
| if (card->probe) {
|
| - ret = card->probe(pdev);
|
| + ret = card->probe(card);
|
| if (ret < 0)
|
| goto card_probe_error;
|
| }
|
| @@ -1606,6 +1858,15 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| }
|
| }
|
|
|
| + for (i = 0; i < card->num_aux_devs; i++) {
|
| + ret = soc_probe_aux_dev(card, i);
|
| + if (ret < 0) {
|
| + pr_err("asoc: failed to add auxiliary devices %s: %d\n",
|
| + card->name, ret);
|
| + goto probe_aux_dev_err;
|
| + }
|
| + }
|
| +
|
| snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
|
| "%s", card->name);
|
| snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
|
| @@ -1614,7 +1875,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| ret = snd_card_register(card->snd_card);
|
| if (ret < 0) {
|
| printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
|
| - goto probe_dai_err;
|
| + goto probe_aux_dev_err;
|
| }
|
|
|
| #ifdef CONFIG_SND_SOC_AC97_BUS
|
| @@ -1624,8 +1885,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| if (ret < 0) {
|
| printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
|
| while (--i >= 0)
|
| - soc_unregister_ac97_dai_link(&card->rtd[i]);
|
| - goto probe_dai_err;
|
| + soc_unregister_ac97_dai_link(card->rtd[i].codec);
|
| + goto probe_aux_dev_err;
|
| }
|
| }
|
| #endif
|
| @@ -1634,13 +1895,17 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
|
| mutex_unlock(&card->mutex);
|
| return;
|
|
|
| +probe_aux_dev_err:
|
| + for (i = 0; i < card->num_aux_devs; i++)
|
| + soc_remove_aux_dev(card, i);
|
| +
|
| probe_dai_err:
|
| for (i = 0; i < card->num_links; i++)
|
| soc_remove_dai_link(card, i);
|
|
|
| card_probe_error:
|
| if (card->remove)
|
| - card->remove(pdev);
|
| + card->remove(card);
|
|
|
| snd_card_free(card->snd_card);
|
|
|
| @@ -1664,11 +1929,15 @@ static int soc_probe(struct platform_device *pdev)
|
| struct snd_soc_card *card = platform_get_drvdata(pdev);
|
| int ret = 0;
|
|
|
| + /*
|
| + * no card, so machine driver should be registering card
|
| + * we should not be here in that case so ret error
|
| + */
|
| + if (!card)
|
| + return -EINVAL;
|
| +
|
| /* Bodge while we unpick instantiation */
|
| card->dev = &pdev->dev;
|
| - INIT_LIST_HEAD(&card->dai_dev_list);
|
| - INIT_LIST_HEAD(&card->codec_dev_list);
|
| - INIT_LIST_HEAD(&card->platform_dev_list);
|
|
|
| ret = snd_soc_register_card(card);
|
| if (ret != 0) {
|
| @@ -1679,39 +1948,48 @@ static int soc_probe(struct platform_device *pdev)
|
| return 0;
|
| }
|
|
|
| -/* removes a socdev */
|
| -static int soc_remove(struct platform_device *pdev)
|
| +static int soc_cleanup_card_resources(struct snd_soc_card *card)
|
| {
|
| - struct snd_soc_card *card = platform_get_drvdata(pdev);
|
| int i;
|
|
|
| - if (card->instantiated) {
|
| + /* make sure any delayed work runs */
|
| + for (i = 0; i < card->num_rtd; i++) {
|
| + struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
|
| + flush_delayed_work_sync(&rtd->delayed_work);
|
| + }
|
|
|
| - /* make sure any delayed work runs */
|
| - for (i = 0; i < card->num_rtd; i++) {
|
| - struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
|
| - run_delayed_work(&rtd->delayed_work);
|
| - }
|
| + /* remove auxiliary devices */
|
| + for (i = 0; i < card->num_aux_devs; i++)
|
| + soc_remove_aux_dev(card, i);
|
|
|
| - /* remove and free each DAI */
|
| - for (i = 0; i < card->num_rtd; i++)
|
| - soc_remove_dai_link(card, i);
|
| + /* remove and free each DAI */
|
| + for (i = 0; i < card->num_rtd; i++)
|
| + soc_remove_dai_link(card, i);
|
|
|
| - /* remove the card */
|
| - if (card->remove)
|
| - card->remove(pdev);
|
| + soc_cleanup_card_debugfs(card);
|
|
|
| - kfree(card->rtd);
|
| - snd_card_free(card->snd_card);
|
| - }
|
| - snd_soc_unregister_card(card);
|
| + /* remove the card */
|
| + if (card->remove)
|
| + card->remove(card);
|
| +
|
| + kfree(card->rtd);
|
| + snd_card_free(card->snd_card);
|
| return 0;
|
| +
|
| }
|
|
|
| -static int soc_poweroff(struct device *dev)
|
| +/* removes a socdev */
|
| +static int soc_remove(struct platform_device *pdev)
|
| {
|
| - struct platform_device *pdev = to_platform_device(dev);
|
| struct snd_soc_card *card = platform_get_drvdata(pdev);
|
| +
|
| + snd_soc_unregister_card(card);
|
| + return 0;
|
| +}
|
| +
|
| +int snd_soc_poweroff(struct device *dev)
|
| +{
|
| + struct snd_soc_card *card = dev_get_drvdata(dev);
|
| int i;
|
|
|
| if (!card->instantiated)
|
| @@ -1721,18 +1999,19 @@ static int soc_poweroff(struct device *dev)
|
| * now, we're shutting down so no imminent restart. */
|
| for (i = 0; i < card->num_rtd; i++) {
|
| struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
|
| - run_delayed_work(&rtd->delayed_work);
|
| + flush_delayed_work_sync(&rtd->delayed_work);
|
| }
|
|
|
| snd_soc_dapm_shutdown(card);
|
|
|
| return 0;
|
| }
|
| +EXPORT_SYMBOL_GPL(snd_soc_poweroff);
|
|
|
| -static const struct dev_pm_ops soc_pm_ops = {
|
| - .suspend = soc_suspend,
|
| - .resume = soc_resume,
|
| - .poweroff = soc_poweroff,
|
| +const struct dev_pm_ops snd_soc_pm_ops = {
|
| + .suspend = snd_soc_suspend,
|
| + .resume = snd_soc_resume,
|
| + .poweroff = snd_soc_poweroff,
|
| };
|
|
|
| /* ASoC platform driver */
|
| @@ -1740,7 +2019,7 @@ static struct platform_driver soc_driver = {
|
| .driver = {
|
| .name = "soc-audio",
|
| .owner = THIS_MODULE,
|
| - .pm = &soc_pm_ops,
|
| + .pm = &snd_soc_pm_ops,
|
| },
|
| .probe = soc_probe,
|
| .remove = soc_remove,
|
| @@ -1810,10 +2089,11 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
|
| *
|
| * Boolean function indiciating if a CODEC register is volatile.
|
| */
|
| -int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg)
|
| +int snd_soc_codec_volatile_register(struct snd_soc_codec *codec,
|
| + unsigned int reg)
|
| {
|
| - if (codec->driver->volatile_register)
|
| - return codec->driver->volatile_register(reg);
|
| + if (codec->volatile_register)
|
| + return codec->volatile_register(codec, reg);
|
| else
|
| return 0;
|
| }
|
| @@ -1880,6 +2160,27 @@ void snd_soc_free_ac97_codec(struct snd_soc_codec *codec)
|
| }
|
| EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec);
|
|
|
| +unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg)
|
| +{
|
| + unsigned int ret;
|
| +
|
| + ret = codec->read(codec, reg);
|
| + dev_dbg(codec->dev, "read %x => %x\n", reg, ret);
|
| + trace_snd_soc_reg_read(codec, reg, ret);
|
| +
|
| + return ret;
|
| +}
|
| +EXPORT_SYMBOL_GPL(snd_soc_read);
|
| +
|
| +unsigned int snd_soc_write(struct snd_soc_codec *codec,
|
| + unsigned int reg, unsigned int val)
|
| +{
|
| + dev_dbg(codec->dev, "write %x = %x\n", reg, val);
|
| + trace_snd_soc_reg_write(codec, reg, val);
|
| + return codec->write(codec, reg, val);
|
| +}
|
| +EXPORT_SYMBOL_GPL(snd_soc_write);
|
| +
|
| /**
|
| * snd_soc_update_bits - update codec register bits
|
| * @codec: audio codec
|
| @@ -1889,19 +2190,27 @@ EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec);
|
| *
|
| * Writes new register value.
|
| *
|
| - * Returns 1 for change else 0.
|
| + * Returns 1 for change, 0 for no change, or negative error code.
|
| */
|
| int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
|
| unsigned int mask, unsigned int value)
|
| {
|
| int change;
|
| unsigned int old, new;
|
| + int ret;
|
|
|
| - old = snd_soc_read(codec, reg);
|
| + ret = snd_soc_read(codec, reg);
|
| + if (ret < 0)
|
| + return ret;
|
| +
|
| + old = ret;
|
| new = (old & ~mask) | value;
|
| change = old != new;
|
| - if (change)
|
| - snd_soc_write(codec, reg, new);
|
| + if (change) {
|
| + ret = snd_soc_write(codec, reg, new);
|
| + if (ret < 0)
|
| + return ret;
|
| + }
|
|
|
| return change;
|
| }
|
| @@ -2020,14 +2329,22 @@ int snd_soc_add_controls(struct snd_soc_codec *codec,
|
| const struct snd_kcontrol_new *controls, int num_controls)
|
| {
|
| struct snd_card *card = codec->card->snd_card;
|
| + char prefixed_name[44], *name;
|
| int err, i;
|
|
|
| for (i = 0; i < num_controls; i++) {
|
| const struct snd_kcontrol_new *control = &controls[i];
|
| - err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
|
| + if (codec->name_prefix) {
|
| + snprintf(prefixed_name, sizeof(prefixed_name), "%s %s",
|
| + codec->name_prefix, control->name);
|
| + name = prefixed_name;
|
| + } else {
|
| + name = control->name;
|
| + }
|
| + err = snd_ctl_add(card, snd_soc_cnew(control, codec, name));
|
| if (err < 0) {
|
| dev_err(codec->dev, "%s: Failed to add %s: %d\n",
|
| - codec->name, control->name, err);
|
| + codec->name, name, err);
|
| return err;
|
| }
|
| }
|
| @@ -2853,21 +3170,24 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
|
| *
|
| * @card: Card to register
|
| *
|
| - * Note that currently this is an internal only function: it will be
|
| - * exposed to machine drivers after further backporting of ASoC v2
|
| - * registration APIs.
|
| */
|
| -static int snd_soc_register_card(struct snd_soc_card *card)
|
| +int snd_soc_register_card(struct snd_soc_card *card)
|
| {
|
| int i;
|
|
|
| if (!card->name || !card->dev)
|
| return -EINVAL;
|
|
|
| - card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,
|
| - GFP_KERNEL);
|
| + snd_soc_initialize_card_lists(card);
|
| +
|
| + soc_init_card_debugfs(card);
|
| +
|
| + card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
|
| + (card->num_links + card->num_aux_devs),
|
| + GFP_KERNEL);
|
| if (card->rtd == NULL)
|
| return -ENOMEM;
|
| + card->rtd_aux = &card->rtd[card->num_links];
|
|
|
| for (i = 0; i < card->num_links; i++)
|
| card->rtd[i].dai_link = &card->dai_link[i];
|
| @@ -2885,18 +3205,18 @@ static int snd_soc_register_card(struct snd_soc_card *card)
|
|
|
| return 0;
|
| }
|
| +EXPORT_SYMBOL_GPL(snd_soc_register_card);
|
|
|
| /**
|
| * snd_soc_unregister_card - Unregister a card with the ASoC core
|
| *
|
| * @card: Card to unregister
|
| *
|
| - * Note that currently this is an internal only function: it will be
|
| - * exposed to machine drivers after further backporting of ASoC v2
|
| - * registration APIs.
|
| */
|
| -static int snd_soc_unregister_card(struct snd_soc_card *card)
|
| +int snd_soc_unregister_card(struct snd_soc_card *card)
|
| {
|
| + if (card->instantiated)
|
| + soc_cleanup_card_resources(card);
|
| mutex_lock(&client_mutex);
|
| list_del(&card->list);
|
| mutex_unlock(&client_mutex);
|
| @@ -2904,12 +3224,13 @@ static int snd_soc_unregister_card(struct snd_soc_card *card)
|
|
|
| return 0;
|
| }
|
| +EXPORT_SYMBOL_GPL(snd_soc_unregister_card);
|
|
|
| /*
|
| * Simplify DAI link configuration by removing ".-1" from device names
|
| * and sanitizing names.
|
| */
|
| -static inline char *fmt_single_name(struct device *dev, int *id)
|
| +static char *fmt_single_name(struct device *dev, int *id)
|
| {
|
| char *found, name[NAME_SIZE];
|
| int id1, id2;
|
| @@ -2917,7 +3238,7 @@ static inline char *fmt_single_name(struct device *dev, int *id)
|
| if (dev_name(dev) == NULL)
|
| return NULL;
|
|
|
| - strncpy(name, dev_name(dev), NAME_SIZE);
|
| + strlcpy(name, dev_name(dev), NAME_SIZE);
|
|
|
| /* are we a "%s.%d" name (platform and SPI components) */
|
| found = strstr(name, dev->driver->name);
|
| @@ -2940,7 +3261,7 @@ static inline char *fmt_single_name(struct device *dev, int *id)
|
|
|
| /* sanitize component name for DAI link creation */
|
| snprintf(tmp, NAME_SIZE, "%s.%s", dev->driver->name, name);
|
| - strncpy(name, tmp, NAME_SIZE);
|
| + strlcpy(name, tmp, NAME_SIZE);
|
| } else
|
| *id = 0;
|
| }
|
| @@ -3205,9 +3526,11 @@ static void fixup_codec_formats(struct snd_soc_pcm_stream *stream)
|
| * @codec: codec to register
|
| */
|
| int snd_soc_register_codec(struct device *dev,
|
| - struct snd_soc_codec_driver *codec_drv,
|
| - struct snd_soc_dai_driver *dai_drv, int num_dai)
|
| + const struct snd_soc_codec_driver *codec_drv,
|
| + struct snd_soc_dai_driver *dai_drv,
|
| + int num_dai)
|
| {
|
| + size_t reg_size;
|
| struct snd_soc_codec *codec;
|
| int ret, i;
|
|
|
| @@ -3224,30 +3547,50 @@ int snd_soc_register_codec(struct device *dev,
|
| return -ENOMEM;
|
| }
|
|
|
| + if (codec_drv->compress_type)
|
| + codec->compress_type = codec_drv->compress_type;
|
| + else
|
| + codec->compress_type = SND_SOC_FLAT_COMPRESSION;
|
| +
|
| + codec->write = codec_drv->write;
|
| + codec->read = codec_drv->read;
|
| + codec->volatile_register = codec_drv->volatile_register;
|
| + codec->readable_register = codec_drv->readable_register;
|
| + codec->dapm.bias_level = SND_SOC_BIAS_OFF;
|
| + codec->dapm.dev = dev;
|
| + codec->dapm.codec = codec;
|
| + codec->dapm.seq_notifier = codec_drv->seq_notifier;
|
| + codec->dev = dev;
|
| + codec->driver = codec_drv;
|
| + codec->num_dai = num_dai;
|
| + mutex_init(&codec->mutex);
|
| +
|
| /* allocate CODEC register cache */
|
| if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
|
| -
|
| - if (codec_drv->reg_cache_default)
|
| - codec->reg_cache = kmemdup(codec_drv->reg_cache_default,
|
| - codec_drv->reg_cache_size * codec_drv->reg_word_size, GFP_KERNEL);
|
| - else
|
| - codec->reg_cache = kzalloc(codec_drv->reg_cache_size *
|
| - codec_drv->reg_word_size, GFP_KERNEL);
|
| -
|
| - if (codec->reg_cache == NULL) {
|
| - kfree(codec->name);
|
| - kfree(codec);
|
| - return -ENOMEM;
|
| + reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
|
| + codec->reg_size = reg_size;
|
| + /* it is necessary to make a copy of the default register cache
|
| + * because in the case of using a compression type that requires
|
| + * the default register cache to be marked as __devinitconst the
|
| + * kernel might have freed the array by the time we initialize
|
| + * the cache.
|
| + */
|
| + if (codec_drv->reg_cache_default) {
|
| + codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
|
| + reg_size, GFP_KERNEL);
|
| + if (!codec->reg_def_copy) {
|
| + ret = -ENOMEM;
|
| + goto fail;
|
| + }
|
| }
|
| }
|
|
|
| - codec->dev = dev;
|
| - codec->driver = codec_drv;
|
| - codec->bias_level = SND_SOC_BIAS_OFF;
|
| - codec->num_dai = num_dai;
|
| - mutex_init(&codec->mutex);
|
| - INIT_LIST_HEAD(&codec->dapm_widgets);
|
| - INIT_LIST_HEAD(&codec->dapm_paths);
|
| + if (codec_drv->reg_access_size && codec_drv->reg_access_default) {
|
| + if (!codec->volatile_register)
|
| + codec->volatile_register = snd_soc_default_volatile_register;
|
| + if (!codec->readable_register)
|
| + codec->readable_register = snd_soc_default_readable_register;
|
| + }
|
|
|
| for (i = 0; i < num_dai; i++) {
|
| fixup_codec_formats(&dai_drv[i].playback);
|
| @@ -3258,7 +3601,7 @@ int snd_soc_register_codec(struct device *dev,
|
| if (num_dai) {
|
| ret = snd_soc_register_dais(dev, dai_drv, num_dai);
|
| if (ret < 0)
|
| - goto error;
|
| + goto fail;
|
| }
|
|
|
| mutex_lock(&client_mutex);
|
| @@ -3269,9 +3612,9 @@ int snd_soc_register_codec(struct device *dev,
|
| pr_debug("Registered codec '%s'\n", codec->name);
|
| return 0;
|
|
|
| -error:
|
| - if (codec->reg_cache)
|
| - kfree(codec->reg_cache);
|
| +fail:
|
| + kfree(codec->reg_def_copy);
|
| + codec->reg_def_copy = NULL;
|
| kfree(codec->name);
|
| kfree(codec);
|
| return ret;
|
| @@ -3305,8 +3648,8 @@ found:
|
|
|
| pr_debug("Unregistered codec '%s'\n", codec->name);
|
|
|
| - if (codec->reg_cache)
|
| - kfree(codec->reg_cache);
|
| + snd_soc_cache_exit(codec);
|
| + kfree(codec->reg_def_copy);
|
| kfree(codec->name);
|
| kfree(codec);
|
| }
|
| @@ -3315,22 +3658,22 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
|
| static int __init snd_soc_init(void)
|
| {
|
| #ifdef CONFIG_DEBUG_FS
|
| - asoc_debugfs_root = debugfs_create_dir("asoc", NULL);
|
| - if (IS_ERR(asoc_debugfs_root) || !asoc_debugfs_root) {
|
| + snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL);
|
| + if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) {
|
| printk(KERN_WARNING
|
| "ASoC: Failed to create debugfs directory\n");
|
| - asoc_debugfs_root = NULL;
|
| + snd_soc_debugfs_root = NULL;
|
| }
|
|
|
| - if (!debugfs_create_file("codecs", 0444, asoc_debugfs_root, NULL,
|
| + if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL,
|
| &codec_list_fops))
|
| pr_warn("ASoC: Failed to create CODEC list debugfs file\n");
|
|
|
| - if (!debugfs_create_file("dais", 0444, asoc_debugfs_root, NULL,
|
| + if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,
|
| &dai_list_fops))
|
| pr_warn("ASoC: Failed to create DAI list debugfs file\n");
|
|
|
| - if (!debugfs_create_file("platforms", 0444, asoc_debugfs_root, NULL,
|
| + if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL,
|
| &platform_list_fops))
|
| pr_warn("ASoC: Failed to create platform list debugfs file\n");
|
| #endif
|
| @@ -3342,7 +3685,7 @@ module_init(snd_soc_init);
|
| static void __exit snd_soc_exit(void)
|
| {
|
| #ifdef CONFIG_DEBUG_FS
|
| - debugfs_remove_recursive(asoc_debugfs_root);
|
| + debugfs_remove_recursive(snd_soc_debugfs_root);
|
| #endif
|
| platform_driver_unregister(&soc_driver);
|
| }
|
|
|