}
        return 0;
 }
+
+static struct snd_kcontrol_new snd_bcm2835_headphones_ctl[] = {
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Headphone Playback Volume",
+               .index = 0,
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+                         SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+               .private_value = PCM_PLAYBACK_VOLUME,
+               .info = snd_bcm2835_ctl_info,
+               .get = snd_bcm2835_ctl_get,
+               .put = snd_bcm2835_ctl_put,
+               .count = 1,
+               .tlv = {.p = snd_bcm2835_db_scale}
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Headphone Playback Switch",
+               .index = 0,
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+               .private_value = PCM_PLAYBACK_MUTE,
+               .info = snd_bcm2835_ctl_info,
+               .get = snd_bcm2835_ctl_get,
+               .put = snd_bcm2835_ctl_put,
+               .count = 1,
+       }
+};
+
+int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip)
+{
+       int err;
+       unsigned int idx;
+
+       strcpy(chip->card->mixername, "Broadcom Mixer");
+       for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_headphones_ctl); idx++) {
+               err = snd_ctl_add(chip->card,
+                                 snd_ctl_new1(&snd_bcm2835_headphones_ctl[idx],
+                                              chip));
+               if (err)
+                       return err;
+       }
+       return 0;
+}
+
+static struct snd_kcontrol_new snd_bcm2835_hdmi[] = {
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "HDMI Playback Volume",
+               .index = 0,
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+                         SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+               .private_value = PCM_PLAYBACK_VOLUME,
+               .info = snd_bcm2835_ctl_info,
+               .get = snd_bcm2835_ctl_get,
+               .put = snd_bcm2835_ctl_put,
+               .count = 1,
+               .tlv = {.p = snd_bcm2835_db_scale}
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "HDMI Playback Switch",
+               .index = 0,
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+               .private_value = PCM_PLAYBACK_MUTE,
+               .info = snd_bcm2835_ctl_info,
+               .get = snd_bcm2835_ctl_get,
+               .put = snd_bcm2835_ctl_put,
+               .count = 1,
+       }
+};
+
+int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip)
+{
+       int err;
+       unsigned int idx;
+
+       strcpy(chip->card->mixername, "Broadcom Mixer");
+       for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_hdmi); idx++) {
+               err = snd_ctl_add(chip->card,
+                                 snd_ctl_new1(&snd_bcm2835_hdmi[idx], chip));
+               if (err)
+                       return err;
+       }
+       return 0;
+}
+
 
 
 #include "bcm2835.h"
 
-/* HACKY global pointers needed for successive probes to work : ssp
- * But compared against the changes we will have to do in VC audio_ipc code
- * to export 8 audio_ipc devices as a single IPC device and then monitor all
- * four devices in a thread, this gets things done quickly and should be easier
- * to debug if we run into issues
- */
+static bool enable_hdmi;
+static bool enable_headphones;
+static bool enable_compat_alsa = true;
+
+module_param(enable_hdmi, bool, 0444);
+MODULE_PARM_DESC(enable_hdmi, "Enables HDMI virtual audio device");
+module_param(enable_headphones, bool, 0444);
+MODULE_PARM_DESC(enable_headphones, "Enables Headphones virtual audio device");
+module_param(enable_compat_alsa, bool, 0444);
+MODULE_PARM_DESC(enable_compat_alsa,
+                "Enables ALSA compatibility virtual audio device");
+
+static void snd_devm_unregister_child(struct device *dev, void *res)
+{
+       struct device *childdev = *(struct device **)res;
+
+       device_unregister(childdev);
+}
+
+static int snd_devm_add_child(struct device *dev, struct device *child)
+{
+       struct device **dr;
+       int ret;
+
+       dr = devres_alloc(snd_devm_unregister_child, sizeof(*dr), GFP_KERNEL);
+       if (!dr)
+               return -ENOMEM;
+
+       ret = device_add(child);
+       if (ret) {
+               devres_free(dr);
+               return ret;
+       }
+
+       *dr = child;
+       devres_add(dev, dr);
+
+       return 0;
+}
+
+static struct device *
+snd_create_device(struct device *parent,
+                 struct device_driver *driver,
+                 const char *name)
+{
+       struct device *device;
+       int ret;
 
-static struct snd_card *g_card;
-static struct bcm2835_chip *g_chip;
+       device = devm_kzalloc(parent, sizeof(*device), GFP_KERNEL);
+       if (!device)
+               return ERR_PTR(-ENOMEM);
+
+       device_initialize(device);
+       device->parent = parent;
+       device->driver = driver;
+
+       dev_set_name(device, "%s", name);
+
+       ret = snd_devm_add_child(parent, device);
+       if (ret)
+               return ERR_PTR(ret);
+
+       return device;
+}
 
 static int snd_bcm2835_free(struct bcm2835_chip *chip)
 {
  * (see "Management of Cards and Components")
  */
 static int snd_bcm2835_create(struct snd_card *card,
-                             struct platform_device *pdev,
                              struct bcm2835_chip **rchip)
 {
        struct bcm2835_chip *chip;
        chip->card = card;
 
        err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
-       if (err < 0) {
+       if (err) {
                snd_bcm2835_free(chip);
                return err;
        }
        return 0;
 }
 
-static int snd_bcm2835_alsa_probe_dt(struct platform_device *pdev)
+static void snd_devm_card_free(struct device *dev, void *res)
 {
-       struct device *dev = &pdev->dev;
-       struct bcm2835_chip *chip;
+       struct snd_card *snd_card = *(struct snd_card **)res;
+
+       snd_card_free(snd_card);
+}
+
+static struct snd_card *snd_devm_card_new(struct device *dev)
+{
+       struct snd_card **dr;
        struct snd_card *card;
-       u32 numchans;
-       int err, i;
+       int ret;
 
-       err = of_property_read_u32(dev->of_node, "brcm,pwm-channels",
-                                  &numchans);
-       if (err) {
-               dev_err(dev, "Failed to get DT property 'brcm,pwm-channels'");
-               return err;
-       }
+       dr = devres_alloc(snd_devm_card_free, sizeof(*dr), GFP_KERNEL);
+       if (!dr)
+               return ERR_PTR(-ENOMEM);
 
-       if (numchans == 0 || numchans > MAX_SUBSTREAMS) {
-               numchans = MAX_SUBSTREAMS;
-               dev_warn(dev, "Illegal 'brcm,pwm-channels' value, will use %u\n",
-                        numchans);
+       ret = snd_card_new(dev, -1, NULL, THIS_MODULE, 0, &card);
+       if (ret) {
+               devres_free(dr);
+               return ERR_PTR(ret);
        }
 
-       err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
-       if (err) {
-               dev_err(dev, "Failed to create soundcard structure\n");
+       *dr = card;
+       devres_add(dev, dr);
+
+       return card;
+}
+
+typedef int (*bcm2835_audio_newpcm_func)(struct bcm2835_chip *chip,
+                                        const char *name,
+                                        enum snd_bcm2835_route route,
+                                        u32 numchannels);
+
+typedef int (*bcm2835_audio_newctl_func)(struct bcm2835_chip *chip);
+
+struct bcm2835_audio_driver {
+       struct device_driver driver;
+       const char *shortname;
+       const char *longname;
+       int minchannels;
+       bcm2835_audio_newpcm_func newpcm;
+       bcm2835_audio_newctl_func newctl;
+       enum snd_bcm2835_route route;
+};
+
+static int bcm2835_audio_alsa_newpcm(struct bcm2835_chip *chip,
+                                    const char *name,
+                                    enum snd_bcm2835_route route,
+                                    u32 numchannels)
+{
+       int err;
+
+       err = snd_bcm2835_new_pcm(chip, numchannels - 1);
+       if (err)
                return err;
-       }
 
-       snd_card_set_dev(card, dev);
-       strcpy(card->driver, "bcm2835");
-       strcpy(card->shortname, "bcm2835 ALSA");
-       sprintf(card->longname, "%s", card->shortname);
+       err = snd_bcm2835_new_spdif_pcm(chip);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static struct bcm2835_audio_driver bcm2835_audio_alsa = {
+       .driver = {
+               .name = "bcm2835_alsa",
+               .owner = THIS_MODULE,
+       },
+       .shortname = "bcm2835 ALSA",
+       .longname  = "bcm2835 ALSA",
+       .minchannels = 2,
+       .newpcm = bcm2835_audio_alsa_newpcm,
+       .newctl = snd_bcm2835_new_ctl,
+};
+
+static struct bcm2835_audio_driver bcm2835_audio_hdmi = {
+       .driver = {
+               .name = "bcm2835_hdmi",
+               .owner = THIS_MODULE,
+       },
+       .shortname = "bcm2835 HDMI",
+       .longname  = "bcm2835 HDMI",
+       .minchannels = 1,
+       .newpcm = snd_bcm2835_new_simple_pcm,
+       .newctl = snd_bcm2835_new_hdmi_ctl,
+       .route = AUDIO_DEST_HDMI
+};
+
+static struct bcm2835_audio_driver bcm2835_audio_headphones = {
+       .driver = {
+               .name = "bcm2835_headphones",
+               .owner = THIS_MODULE,
+       },
+       .shortname = "bcm2835 Headphones",
+       .longname  = "bcm2835 Headphones",
+       .minchannels = 1,
+       .newpcm = snd_bcm2835_new_simple_pcm,
+       .newctl = snd_bcm2835_new_headphones_ctl,
+       .route = AUDIO_DEST_HEADPHONES
+};
+
+struct bcm2835_audio_drivers {
+       struct bcm2835_audio_driver *audio_driver;
+       const bool *is_enabled;
+};
+
+static struct bcm2835_audio_drivers children_devices[] = {
+       {
+               .audio_driver = &bcm2835_audio_alsa,
+               .is_enabled = &enable_compat_alsa,
+       },
+       {
+               .audio_driver = &bcm2835_audio_hdmi,
+               .is_enabled = &enable_hdmi,
+       },
+       {
+               .audio_driver = &bcm2835_audio_headphones,
+               .is_enabled = &enable_headphones,
+       },
+};
 
-       err = snd_bcm2835_create(card, pdev, &chip);
-       if (err < 0) {
-               dev_err(dev, "Failed to create bcm2835 chip\n");
-               goto err_free;
+static int snd_add_child_device(struct device *device,
+                               struct bcm2835_audio_driver *audio_driver,
+                               u32 numchans)
+{
+       struct snd_card *card;
+       struct device *child;
+       struct bcm2835_chip *chip;
+       int err, i;
+
+       child = snd_create_device(device, &audio_driver->driver,
+                                 audio_driver->driver.name);
+       if (IS_ERR(child)) {
+               dev_err(device,
+                       "Unable to create child device %p, error %ld",
+                       audio_driver->driver.name,
+                       PTR_ERR(child));
+               return PTR_ERR(child);
        }
 
-       err = snd_bcm2835_new_pcm(chip);
-       if (err < 0) {
-               dev_err(dev, "Failed to create new bcm2835 pcm device\n");
-               goto err_free;
+       card = snd_devm_card_new(child);
+       if (IS_ERR(card)) {
+               dev_err(child, "Failed to create card");
+               return PTR_ERR(card);
        }
 
-       err = snd_bcm2835_new_spdif_pcm(chip);
-       if (err < 0) {
-               dev_err(dev, "Failed to create new bcm2835 spdif pcm device\n");
-               goto err_free;
+       snd_card_set_dev(card, child);
+       strcpy(card->driver, audio_driver->driver.name);
+       strcpy(card->shortname, audio_driver->shortname);
+       strcpy(card->longname, audio_driver->longname);
+
+       err = snd_bcm2835_create(card, &chip);
+       if (err) {
+               dev_err(child, "Failed to create chip, error %d\n", err);
+               return err;
        }
 
-       err = snd_bcm2835_new_ctl(chip);
-       if (err < 0) {
-               dev_err(dev, "Failed to create new bcm2835 ctl\n");
-               goto err_free;
+       chip->dev = child;
+
+       err = audio_driver->newpcm(chip, audio_driver->shortname,
+               audio_driver->route,
+               numchans);
+       if (err) {
+               dev_err(child, "Failed to create pcm, error %d\n", err);
+               return err;
        }
 
-       for (i = 0; i < numchans; i++) {
-               chip->avail_substreams |= (1 << i);
-               chip->pdev[i] = pdev;
+       err = audio_driver->newctl(chip);
+       if (err) {
+               dev_err(child, "Failed to create controls, error %d\n", err);
+               return err;
        }
 
+       for (i = 0; i < numchans; i++)
+               chip->avail_substreams |= (1 << i);
+
        err = snd_card_register(card);
        if (err) {
-               dev_err(dev, "Failed to register bcm2835 ALSA card\n");
-               goto err_free;
+               dev_err(child, "Failed to register card, error %d\n", err);
+               return err;
        }
 
-       g_card = card;
-       g_chip = chip;
-       platform_set_drvdata(pdev, card);
-       audio_info("bcm2835 ALSA card created with %u channels\n", numchans);
+       dev_set_drvdata(child, card);
+       dev_info(child, "card created with %d channels\n", numchans);
 
        return 0;
+}
+
+static int snd_add_child_devices(struct device *device, u32 numchans)
+{
+       int i;
+       int count_devices = 0;
+       int minchannels = 0;
+       int extrachannels = 0;
+       int extrachannels_per_driver = 0;
+       int extrachannels_remainder = 0;
+
+       for (i = 0; i < ARRAY_SIZE(children_devices); i++)
+               if (*children_devices[i].is_enabled)
+                       count_devices++;
+
+       if (!count_devices)
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(children_devices); i++)
+               if (*children_devices[i].is_enabled)
+                       minchannels +=
+                               children_devices[i].audio_driver->minchannels;
+
+       if (minchannels < numchans) {
+               extrachannels = numchans - minchannels;
+               extrachannels_per_driver = extrachannels / count_devices;
+               extrachannels_remainder = extrachannels % count_devices;
+       }
 
-err_free:
-       snd_card_free(card);
-
-       return err;
-}
-
-static int snd_bcm2835_alsa_remove(struct platform_device *pdev)
-{
-       int idx;
-       void *drv_data;
-
-       drv_data = platform_get_drvdata(pdev);
-
-       if (drv_data == (void *)g_card) {
-               /* This is the card device */
-               snd_card_free((struct snd_card *)drv_data);
-               g_card = NULL;
-               g_chip = NULL;
-       } else {
-               idx = (int)(long)drv_data;
-               if (g_card) {
-                       BUG_ON(!g_chip);
-                       /* We pass chip device numbers in audio ipc devices
-                        * other than the one we registered our card with
-                        */
-                       idx = (int)(long)drv_data;
-                       BUG_ON(!idx || idx > MAX_SUBSTREAMS);
-                       g_chip->avail_substreams &= ~(1 << idx);
-                       /* There should be atleast one substream registered
-                        * after we are done here, as it wil be removed when
-                        * the *remove* is called for the card device
-                        */
-                       BUG_ON(!g_chip->avail_substreams);
+       dev_dbg(device, "minchannels %d\n", minchannels);
+       dev_dbg(device, "extrachannels %d\n", extrachannels);
+       dev_dbg(device, "extrachannels_per_driver %d\n",
+               extrachannels_per_driver);
+       dev_dbg(device, "extrachannels_remainder %d\n",
+               extrachannels_remainder);
+
+       for (i = 0; i < ARRAY_SIZE(children_devices); i++) {
+               int err;
+               int numchannels_this_device;
+               struct bcm2835_audio_driver *audio_driver;
+
+               if (!*children_devices[i].is_enabled)
+                       continue;
+
+               audio_driver = children_devices[i].audio_driver;
+
+               if (audio_driver->minchannels > numchans) {
+                       dev_err(device,
+                               "Out of channels, needed %d but only %d left\n",
+                               audio_driver->minchannels,
+                               numchans);
+                       continue;
                }
+
+               numchannels_this_device =
+                       audio_driver->minchannels + extrachannels_per_driver +
+                       extrachannels_remainder;
+               extrachannels_remainder = 0;
+
+               numchans -= numchannels_this_device;
+
+               err = snd_add_child_device(device, audio_driver,
+                                          numchannels_this_device);
+               if (err)
+                       return err;
        }
 
-       platform_set_drvdata(pdev, NULL);
+       return 0;
+}
+
+static int snd_bcm2835_alsa_probe_dt(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       u32 numchans;
+       int err;
+
+       err = of_property_read_u32(dev->of_node, "brcm,pwm-channels",
+                                  &numchans);
+       if (err) {
+               dev_err(dev, "Failed to get DT property 'brcm,pwm-channels'");
+               return err;
+       }
+
+       if (numchans == 0 || numchans > MAX_SUBSTREAMS) {
+               numchans = MAX_SUBSTREAMS;
+               dev_warn(dev,
+                        "Illegal 'brcm,pwm-channels' value, will use %u\n",
+                        numchans);
+       }
+
+       err = snd_add_child_devices(dev, numchans);
+       if (err)
+               return err;
 
        return 0;
 }
 
 static struct platform_driver bcm2835_alsa0_driver = {
        .probe = snd_bcm2835_alsa_probe_dt,
-       .remove = snd_bcm2835_alsa_remove,
 #ifdef CONFIG_PM
        .suspend = snd_bcm2835_alsa_suspend,
        .resume = snd_bcm2835_alsa_resume,
 #endif
        .driver = {
-               .name = "bcm2835_AUD0",
+               .name = "bcm2835_audio",
                .owner = THIS_MODULE,
                .of_match_table = snd_bcm2835_of_match_table,
        },
 
        retval = platform_driver_register(&bcm2835_alsa0_driver);
        if (retval)
-               pr_err("Error registering bcm2835_alsa0_driver %d .\n", retval);
+               pr_err("Error registering bcm2835_audio driver %d .\n", retval);
 
        return retval;
 }