Merge branch 'rmobile-latest' of git://git.kernel.org/pub/scm/linux/kernel/git/lethal...
[pandora-kernel.git] / sound / soc / soc-core.c
index c3f6f1e..4dda589 100644 (file)
@@ -48,7 +48,8 @@ static DEFINE_MUTEX(pcm_mutex);
 static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
 
 #ifdef CONFIG_DEBUG_FS
-static struct dentry *debugfs_root;
+struct dentry *snd_soc_debugfs_root;
+EXPORT_SYMBOL_GPL(snd_soc_debugfs_root);
 #endif
 
 static DEFINE_MUTEX(client_mutex);
@@ -57,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);
 
 /*
@@ -70,10 +69,73 @@ static int pmdown_time = 5000;
 module_param(pmdown_time, int, 0);
 MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
 
+/* 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];
+
+       /* since tmpbuf is allocated on the stack, warn the callers if they
+        * try to abuse this function */
+       WARN_ON(len > 63);
+
+       /* +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);
+       }
+
+       /* 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;
@@ -81,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 = snd_soc_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);
@@ -168,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;
 }
@@ -203,12 +259,14 @@ static ssize_t codec_reg_write_file(struct file *file,
        while (*start == ' ')
                start++;
        reg = simple_strtoul(start, &start, 16);
-       if ((reg >= codec->driver->reg_cache_size) || (reg % step))
-               return -EINVAL;
        while (*start == ' ')
                start++;
        if (strict_strtoul(start, 16, &value))
                return -EINVAL;
+
+       /* Userspace has been fiddling around behind the kernel's back */
+       add_taint(TAINT_USER);
+
        snd_soc_write(codec, reg, value);
        return buf_size;
 }
@@ -232,6 +290,11 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
                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);
@@ -356,7 +419,7 @@ static const struct file_operations platform_list_fops = {
 static void soc_init_card_debugfs(struct snd_soc_card *card)
 {
        card->debugfs_card_root = debugfs_create_dir(card->name,
-                                                    debugfs_root);
+                                                    snd_soc_debugfs_root);
        if (!card->debugfs_card_root) {
                dev_warn(card->dev,
                         "ASoC: Failed to create codec debugfs directory\n");
@@ -435,20 +498,30 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
        struct snd_soc_dai *codec_dai = rtd->codec_dai;
        int ret;
 
-       if (codec_dai->driver->symmetric_rates || cpu_dai->driver->symmetric_rates ||
-                       rtd->dai_link->symmetric_rates) {
-               dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n",
-                               rtd->rate);
+       if (!codec_dai->driver->symmetric_rates &&
+           !cpu_dai->driver->symmetric_rates &&
+           !rtd->dai_link->symmetric_rates)
+               return 0;
 
-               ret = snd_pcm_hw_constraint_minmax(substream->runtime,
-                                                  SNDRV_PCM_HW_PARAM_RATE,
-                                                  rtd->rate,
-                                                  rtd->rate);
-               if (ret < 0) {
-                       dev_err(&rtd->dev,
-                               "Unable to apply rate symmetry constraint: %d\n", ret);
-                       return ret;
-               }
+       /* This can happen if multiple streams are starting simultaneously -
+        * the second can need to get its constraints before the first has
+        * picked a rate.  Complain and allow the application to carry on.
+        */
+       if (!rtd->rate) {
+               dev_warn(&rtd->dev,
+                        "Not enforcing symmetric_rates due to race\n");
+               return 0;
+       }
+
+       dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate);
+
+       ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+                                          SNDRV_PCM_HW_PARAM_RATE,
+                                          rtd->rate, rtd->rate);
+       if (ret < 0) {
+               dev_err(&rtd->dev,
+                       "Unable to apply rate symmetry constraint: %d\n", ret);
+               return ret;
        }
 
        return 0;
@@ -962,12 +1035,11 @@ 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;
 
@@ -1008,7 +1080,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;
@@ -1075,10 +1147,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
@@ -1087,7 +1160,6 @@ 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;
 
@@ -1101,7 +1173,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++) {
@@ -1176,7 +1248,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");
 
@@ -1185,10 +1257,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
@@ -1210,9 +1281,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 = {
@@ -1400,31 +1472,44 @@ static int soc_probe_codec(struct snd_soc_card *card,
                           struct snd_soc_codec *codec)
 {
        int ret = 0;
+       const struct snd_soc_codec_driver *driver = codec->driver;
 
        codec->card = card;
        codec->dapm.card = card;
        soc_set_name_prefix(card, codec);
 
-       if (codec->driver->probe) {
-               ret = codec->driver->probe(codec);
+       if (!try_module_get(codec->dev->driver->owner))
+               return -ENODEV;
+
+       if (driver->probe) {
+               ret = driver->probe(codec);
                if (ret < 0) {
                        dev_err(codec->dev,
                                "asoc: failed to probe CODEC %s: %d\n",
                                codec->name, ret);
-                       return ret;
+                       goto err_probe;
                }
        }
 
+       if (driver->dapm_widgets)
+               snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,
+                                         driver->num_dapm_widgets);
+       if (driver->dapm_routes)
+               snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes,
+                                       driver->num_dapm_routes);
+
        soc_init_codec_debugfs(codec);
 
        /* mark codec as probed and add to card codec list */
-       if (!try_module_get(codec->dev->driver->owner))
-               return -ENODEV;
-
        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;
 }
 
@@ -1468,7 +1553,6 @@ static int soc_post_component_init(struct snd_soc_card *card,
 
        /* Make sure all DAPM widgets are instantiated */
        snd_soc_dapm_new_widgets(&codec->dapm);
-       snd_soc_dapm_sync(&codec->dapm);
 
        /* register the rtd device */
        rtd->codec = codec;
@@ -1543,19 +1627,19 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
 
        /* 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;
                        }
                }
                /* mark platform as probed and add to card platform list */
-
-               if (!try_module_get(platform->dev->driver->owner))
-                       return -ENODEV;
-
                platform->probed = 1;
                list_add(&platform->card_list, &card->platform_dev_list);
        }
@@ -1713,7 +1797,6 @@ static int snd_soc_init_codec_cache(struct snd_soc_codec *codec,
 
 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;
@@ -1740,6 +1823,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
        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];
@@ -1750,18 +1835,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
                                        break;
                        }
                }
-               if (i == card->num_configs) {
-                       /* no need to override the compress_type so
-                        * go ahead and do the standard thing */
-                       ret = snd_soc_init_codec_cache(codec, 0);
-                       if (ret < 0) {
-                               mutex_unlock(&card->mutex);
-                               return;
-                       }
-                       continue;
-               }
-               /* override the compress_type with the one supplied in
-                * the machine driver */
                ret = snd_soc_init_codec_cache(codec, compress_type);
                if (ret < 0) {
                        mutex_unlock(&card->mutex);
@@ -1780,14 +1853,19 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
        }
        card->snd_card->dev = card->dev;
 
-#ifdef CONFIG_PM
+       card->dapm.bias_level = SND_SOC_BIAS_OFF;
+       card->dapm.dev = card->dev;
+       card->dapm.card = card;
+       list_add(&card->dapm.list, &card->dapm_list);
+
+#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;
        }
@@ -1810,11 +1888,37 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
                }
        }
 
+       if (card->dapm_widgets)
+               snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
+                                         card->num_dapm_widgets);
+       if (card->dapm_routes)
+               snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
+                                       card->num_dapm_routes);
+
+#ifdef CONFIG_DEBUG_FS
+       card->dapm.debugfs_dapm = debugfs_create_dir("dapm",
+                                                    card->debugfs_card_root);
+       if (!card->dapm.debugfs_dapm)
+               printk(KERN_WARNING
+                      "Failed to create card DAPM debugfs directory\n");
+
+       snd_soc_dapm_debugfs_init(&card->dapm);
+#endif
+
        snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
                 "%s",  card->name);
        snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
                 "%s", card->name);
 
+       if (card->late_probe) {
+               ret = card->late_probe(card);
+               if (ret < 0) {
+                       dev_err(card->dev, "%s late_probe() failed: %d\n",
+                               card->name, ret);
+                       goto probe_aux_dev_err;
+               }
+       }
+
        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
@@ -1848,7 +1952,7 @@ probe_dai_err:
 
 card_probe_error:
        if (card->remove)
-               card->remove(pdev);
+               card->remove(card);
 
        snd_card_free(card->snd_card);
 
@@ -1872,16 +1976,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);
-       INIT_LIST_HEAD(&card->widgets);
-       INIT_LIST_HEAD(&card->paths);
-       INIT_LIST_HEAD(&card->dapm_list);
-
-       soc_init_card_debugfs(card);
 
        ret = snd_soc_register_card(card);
        if (ret != 0) {
@@ -1892,45 +1995,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];
-                       flush_delayed_work_sync(&rtd->delayed_work);
-               }
+       /* remove auxiliary devices */
+       for (i = 0; i < card->num_aux_devs; i++)
+               soc_remove_aux_dev(card, i);
 
-               /* 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);
+       soc_cleanup_card_debugfs(card);
 
-               soc_cleanup_card_debugfs(card);
+       /* remove the card */
+       if (card->remove)
+               card->remove(card);
+
+       kfree(card->rtd);
+       snd_card_free(card->snd_card);
+       return 0;
 
-               /* remove the card */
-               if (card->remove)
-                       card->remove(pdev);
+}
+
+/* removes a socdev */
+static int soc_remove(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = platform_get_drvdata(pdev);
 
-               kfree(card->rtd);
-               snd_card_free(card->snd_card);
-       }
        snd_soc_unregister_card(card);
        return 0;
 }
 
-static int soc_poweroff(struct device *dev)
+int snd_soc_poweroff(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;
 
        if (!card->instantiated)
@@ -1947,11 +2053,12 @@ static int soc_poweroff(struct device *dev)
 
        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 */
@@ -1959,7 +2066,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,
@@ -2029,10 +2136,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;
 }
@@ -2129,19 +2237,27 @@ EXPORT_SYMBOL_GPL(snd_soc_write);
  *
  * 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;
 }
@@ -2226,22 +2342,45 @@ EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams);
  * @_template: control template
  * @data: control private data
  * @long_name: control long name
+ * @prefix: control name prefix
  *
  * Create a new mixer control from a template control.
  *
  * Returns 0 for success, else error.
  */
 struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
-       void *data, char *long_name)
+                                 void *data, char *long_name,
+                                 const char *prefix)
 {
        struct snd_kcontrol_new template;
+       struct snd_kcontrol *kcontrol;
+       char *name = NULL;
+       int name_len;
 
        memcpy(&template, _template, sizeof(template));
-       if (long_name)
-               template.name = long_name;
        template.index = 0;
 
-       return snd_ctl_new1(&template, data);
+       if (!long_name)
+               long_name = template.name;
+
+       if (prefix) {
+               name_len = strlen(long_name) + strlen(prefix) + 2;
+               name = kmalloc(name_len, GFP_ATOMIC);
+               if (!name)
+                       return NULL;
+
+               snprintf(name, name_len, "%s %s", prefix, long_name);
+
+               template.name = name;
+       } else {
+               template.name = long_name;
+       }
+
+       kcontrol = snd_ctl_new1(&template, data);
+
+       kfree(name);
+
+       return kcontrol;
 }
 EXPORT_SYMBOL_GPL(snd_soc_cnew);
 
@@ -2260,22 +2399,16 @@ 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];
-               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));
+               err = snd_ctl_add(card, snd_soc_cnew(control, codec,
+                                                    control->name,
+                                                    codec->name_prefix));
                if (err < 0) {
                        dev_err(codec->dev, "%s: Failed to add %s: %d\n",
-                               codec->name, name, err);
+                               codec->name, control->name, err);
                        return err;
                }
        }
@@ -2956,11 +3089,33 @@ int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
 {
        if (dai->driver && dai->driver->ops->set_sysclk)
                return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
+       else if (dai->codec && dai->codec->driver->set_sysclk)
+               return dai->codec->driver->set_sysclk(dai->codec, clk_id,
+                                                     freq, dir);
        else
                return -EINVAL;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
 
+/**
+ * snd_soc_codec_set_sysclk - configure CODEC system or master clock.
+ * @codec: CODEC
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the CODEC master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id,
+       unsigned int freq, int dir)
+{
+       if (codec->driver->set_sysclk)
+               return codec->driver->set_sysclk(codec, clk_id, freq, dir);
+       else
+               return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_sysclk);
+
 /**
  * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
  * @dai: DAI
@@ -2997,11 +3152,35 @@ int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
        if (dai->driver && dai->driver->ops->set_pll)
                return dai->driver->ops->set_pll(dai, pll_id, source,
                                         freq_in, freq_out);
+       else if (dai->codec && dai->codec->driver->set_pll)
+               return dai->codec->driver->set_pll(dai->codec, pll_id, source,
+                                                  freq_in, freq_out);
        else
                return -EINVAL;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
 
+/*
+ * snd_soc_codec_set_pll - configure codec PLL.
+ * @codec: CODEC
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
+                         unsigned int freq_in, unsigned int freq_out)
+{
+       if (codec->driver->set_pll)
+               return codec->driver->set_pll(codec, pll_id, source,
+                                             freq_in, freq_out);
+       else
+               return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_pll);
+
 /**
  * snd_soc_dai_set_fmt - configure DAI hardware audio format.
  * @dai: DAI
@@ -3101,17 +3280,18 @@ 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;
 
+       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);
@@ -3135,18 +3315,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);
@@ -3154,6 +3334,7 @@ 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
@@ -3483,9 +3664,12 @@ int snd_soc_register_codec(struct device *dev,
 
        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;
@@ -3494,20 +3678,30 @@ int snd_soc_register_codec(struct device *dev,
        /* allocate CODEC register cache */
        if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
                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.
                 */
-               codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
-                                             reg_size, GFP_KERNEL);
-               if (!codec->reg_def_copy) {
-                       ret = -ENOMEM;
-                       goto fail;
+               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;
+                       }
                }
        }
 
+       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);
                fixup_codec_formats(&dai_drv[i].capture);
@@ -3574,22 +3768,22 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
 static int __init snd_soc_init(void)
 {
 #ifdef CONFIG_DEBUG_FS
-       debugfs_root = debugfs_create_dir("asoc", NULL);
-       if (IS_ERR(debugfs_root) || !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");
-               debugfs_root = NULL;
+               snd_soc_debugfs_root = NULL;
        }
 
-       if (!debugfs_create_file("codecs", 0444, 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, 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, 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
@@ -3601,7 +3795,7 @@ module_init(snd_soc_init);
 static void __exit snd_soc_exit(void)
 {
 #ifdef CONFIG_DEBUG_FS
-       debugfs_remove_recursive(debugfs_root);
+       debugfs_remove_recursive(snd_soc_debugfs_root);
 #endif
        platform_driver_unregister(&soc_driver);
 }