diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c index 5b03102c5f18..1fae169bc066 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c @@ -340,3 +340,89 @@ int snd_bcm2835_new_ctl(struct bcm2835_chip *chip) } 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; +} + diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c index 059f54a7534b..8bd69b93d78d 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c @@ -479,7 +479,7 @@ static struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = { }; /* create a pcm device */ -int snd_bcm2835_new_pcm(struct bcm2835_chip *chip) +int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, u32 numchannels) { struct snd_pcm *pcm; int err; @@ -490,7 +490,7 @@ int snd_bcm2835_new_pcm(struct bcm2835_chip *chip) audio_error("Interrupted whilst waiting for lock\n"); return -EINTR; } - err = snd_pcm_new(chip->card, "bcm2835 ALSA", 0, MAX_SUBSTREAMS, 0, &pcm); + err = snd_pcm_new(chip->card, "bcm2835 ALSA", 0, numchannels, 0, &pcm); if (err < 0) goto out; pcm->private_data = chip; @@ -549,3 +549,39 @@ out: return 0; } + +int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels) +{ + struct snd_pcm *pcm; + int err; + + mutex_init(&chip->audio_mutex); + + err = snd_pcm_new(chip->card, name, 0, numchannels, + 0, &pcm); + if (err) + return err; + + pcm->private_data = chip; + strcpy(pcm->name, name); + chip->pcm = pcm; + chip->dest = route; + chip->volume = alsa2chip(0); + chip->mute = CTRL_VOL_UNMUTE; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_bcm2835_playback_ops); + + snd_pcm_lib_preallocate_pages_for_all( + pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + snd_bcm2835_playback_hw.buffer_bytes_max, + snd_bcm2835_playback_hw.buffer_bytes_max); + + return 0; +} + diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c index 3a5e528e0ec6..8f2d508183b2 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c @@ -21,15 +21,70 @@ #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; -static struct snd_card *g_card; -static struct bcm2835_chip *g_chip; +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; + + 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) { @@ -49,7 +104,6 @@ static int snd_bcm2835_dev_free(struct snd_device *device) * (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; @@ -67,7 +121,7 @@ static int snd_bcm2835_create(struct snd_card *card, chip->card = card; err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); - if (err < 0) { + if (err) { snd_bcm2835_free(chip); return err; } @@ -76,13 +130,268 @@ static int snd_bcm2835_create(struct snd_card *card, return 0; } +static void snd_devm_card_free(struct device *dev, void *res) +{ + 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; + int ret; + + dr = devres_alloc(snd_devm_card_free, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + ret = snd_card_new(dev, -1, NULL, THIS_MODULE, 0, &card); + if (ret) { + devres_free(dr); + return ERR_PTR(ret); + } + + *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; + + 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, + }, +}; + +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); + } + + card = snd_devm_card_new(child); + if (IS_ERR(card)) { + dev_err(child, "Failed to create card"); + return PTR_ERR(card); + } + + 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; + } + + 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; + } + + 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(child, "Failed to register card, error %d\n", err); + return err; + } + + 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; + } + + 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; + } + + return 0; +} + static int snd_bcm2835_alsa_probe_dt(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct bcm2835_chip *chip; - struct snd_card *card; u32 numchans; - int err, i; + int err; err = of_property_read_u32(dev->of_node, "brcm,pwm-channels", &numchans); @@ -93,100 +402,14 @@ static int snd_bcm2835_alsa_probe_dt(struct platform_device *pdev) if (numchans == 0 || numchans > MAX_SUBSTREAMS) { numchans = MAX_SUBSTREAMS; - dev_warn(dev, "Illegal 'brcm,pwm-channels' value, will use %u\n", + dev_warn(dev, + "Illegal 'brcm,pwm-channels' value, will use %u\n", numchans); } - err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card); - if (err) { - dev_err(dev, "Failed to create soundcard structure\n"); + err = snd_add_child_devices(dev, numchans); + 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_create(card, pdev, &chip); - if (err < 0) { - dev_err(dev, "Failed to create bcm2835 chip\n"); - goto err_free; - } - - err = snd_bcm2835_new_pcm(chip); - if (err < 0) { - dev_err(dev, "Failed to create new bcm2835 pcm device\n"); - goto err_free; - } - - 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; - } - - err = snd_bcm2835_new_ctl(chip); - if (err < 0) { - dev_err(dev, "Failed to create new bcm2835 ctl\n"); - goto err_free; - } - - for (i = 0; i < numchans; i++) { - chip->avail_substreams |= (1 << i); - chip->pdev[i] = pdev; - } - - err = snd_card_register(card); - if (err) { - dev_err(dev, "Failed to register bcm2835 ALSA card\n"); - goto err_free; - } - - g_card = card; - g_chip = chip; - platform_set_drvdata(pdev, card); - audio_info("bcm2835 ALSA card created with %u channels\n", numchans); - - return 0; - -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); - } - } - - platform_set_drvdata(pdev, NULL); return 0; } @@ -214,13 +437,12 @@ MODULE_DEVICE_TABLE(of, snd_bcm2835_of_match_table); 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, }, @@ -232,7 +454,7 @@ static int bcm2835_alsa_device_init(void) 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; } diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h index c53c2bdd8494..379604d3554e 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h @@ -104,7 +104,7 @@ struct bcm2835_chip { struct snd_pcm *pcm_spdif; /* Bitmat for valid reg_base and irq numbers */ unsigned int avail_substreams; - struct platform_device *pdev[MAX_SUBSTREAMS]; + struct device *dev; struct bcm2835_alsa_stream *alsa_stream[MAX_SUBSTREAMS]; int volume; @@ -145,8 +145,15 @@ struct bcm2835_alsa_stream { }; int snd_bcm2835_new_ctl(struct bcm2835_chip *chip); -int snd_bcm2835_new_pcm(struct bcm2835_chip *chip); +int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, u32 numchannels); int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip); +int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels); + +int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip); +int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip); int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream); int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream);