18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * media.c - Media Controller specific ALSA driver code 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2019 Shuah Khan <shuah@kernel.org> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * This file adds Media Controller support to the ALSA driver 118c2ecf20Sopenharmony_ci * to use the Media Controller API to share the tuner with DVB 128c2ecf20Sopenharmony_ci * and V4L2 drivers that control the media device. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * The media device is created based on the existing quirks framework. 158c2ecf20Sopenharmony_ci * Using this approach, the media controller API usage can be added for 168c2ecf20Sopenharmony_ci * a specific device. 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/init.h> 208c2ecf20Sopenharmony_ci#include <linux/list.h> 218c2ecf20Sopenharmony_ci#include <linux/mutex.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci#include <linux/usb.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <sound/pcm.h> 268c2ecf20Sopenharmony_ci#include <sound/core.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#include "usbaudio.h" 298c2ecf20Sopenharmony_ci#include "card.h" 308c2ecf20Sopenharmony_ci#include "mixer.h" 318c2ecf20Sopenharmony_ci#include "media.h" 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ciint snd_media_stream_init(struct snd_usb_substream *subs, struct snd_pcm *pcm, 348c2ecf20Sopenharmony_ci int stream) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci struct media_device *mdev; 378c2ecf20Sopenharmony_ci struct media_ctl *mctl; 388c2ecf20Sopenharmony_ci struct device *pcm_dev = &pcm->streams[stream].dev; 398c2ecf20Sopenharmony_ci u32 intf_type; 408c2ecf20Sopenharmony_ci int ret = 0; 418c2ecf20Sopenharmony_ci u16 mixer_pad; 428c2ecf20Sopenharmony_ci struct media_entity *entity; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci mdev = subs->stream->chip->media_dev; 458c2ecf20Sopenharmony_ci if (!mdev) 468c2ecf20Sopenharmony_ci return 0; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (subs->media_ctl) 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci /* allocate media_ctl */ 528c2ecf20Sopenharmony_ci mctl = kzalloc(sizeof(*mctl), GFP_KERNEL); 538c2ecf20Sopenharmony_ci if (!mctl) 548c2ecf20Sopenharmony_ci return -ENOMEM; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci mctl->media_dev = mdev; 578c2ecf20Sopenharmony_ci if (stream == SNDRV_PCM_STREAM_PLAYBACK) { 588c2ecf20Sopenharmony_ci intf_type = MEDIA_INTF_T_ALSA_PCM_PLAYBACK; 598c2ecf20Sopenharmony_ci mctl->media_entity.function = MEDIA_ENT_F_AUDIO_PLAYBACK; 608c2ecf20Sopenharmony_ci mctl->media_pad.flags = MEDIA_PAD_FL_SOURCE; 618c2ecf20Sopenharmony_ci mixer_pad = 1; 628c2ecf20Sopenharmony_ci } else { 638c2ecf20Sopenharmony_ci intf_type = MEDIA_INTF_T_ALSA_PCM_CAPTURE; 648c2ecf20Sopenharmony_ci mctl->media_entity.function = MEDIA_ENT_F_AUDIO_CAPTURE; 658c2ecf20Sopenharmony_ci mctl->media_pad.flags = MEDIA_PAD_FL_SINK; 668c2ecf20Sopenharmony_ci mixer_pad = 2; 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci mctl->media_entity.name = pcm->name; 698c2ecf20Sopenharmony_ci media_entity_pads_init(&mctl->media_entity, 1, &mctl->media_pad); 708c2ecf20Sopenharmony_ci ret = media_device_register_entity(mctl->media_dev, 718c2ecf20Sopenharmony_ci &mctl->media_entity); 728c2ecf20Sopenharmony_ci if (ret) 738c2ecf20Sopenharmony_ci goto free_mctl; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci mctl->intf_devnode = media_devnode_create(mdev, intf_type, 0, 768c2ecf20Sopenharmony_ci MAJOR(pcm_dev->devt), 778c2ecf20Sopenharmony_ci MINOR(pcm_dev->devt)); 788c2ecf20Sopenharmony_ci if (!mctl->intf_devnode) { 798c2ecf20Sopenharmony_ci ret = -ENOMEM; 808c2ecf20Sopenharmony_ci goto unregister_entity; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci mctl->intf_link = media_create_intf_link(&mctl->media_entity, 838c2ecf20Sopenharmony_ci &mctl->intf_devnode->intf, 848c2ecf20Sopenharmony_ci MEDIA_LNK_FL_ENABLED); 858c2ecf20Sopenharmony_ci if (!mctl->intf_link) { 868c2ecf20Sopenharmony_ci ret = -ENOMEM; 878c2ecf20Sopenharmony_ci goto devnode_remove; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* create link between mixer and audio */ 918c2ecf20Sopenharmony_ci media_device_for_each_entity(entity, mdev) { 928c2ecf20Sopenharmony_ci switch (entity->function) { 938c2ecf20Sopenharmony_ci case MEDIA_ENT_F_AUDIO_MIXER: 948c2ecf20Sopenharmony_ci ret = media_create_pad_link(entity, mixer_pad, 958c2ecf20Sopenharmony_ci &mctl->media_entity, 0, 968c2ecf20Sopenharmony_ci MEDIA_LNK_FL_ENABLED); 978c2ecf20Sopenharmony_ci if (ret) 988c2ecf20Sopenharmony_ci goto remove_intf_link; 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci subs->media_ctl = mctl; 1048c2ecf20Sopenharmony_ci return 0; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ciremove_intf_link: 1078c2ecf20Sopenharmony_ci media_remove_intf_link(mctl->intf_link); 1088c2ecf20Sopenharmony_cidevnode_remove: 1098c2ecf20Sopenharmony_ci media_devnode_remove(mctl->intf_devnode); 1108c2ecf20Sopenharmony_ciunregister_entity: 1118c2ecf20Sopenharmony_ci media_device_unregister_entity(&mctl->media_entity); 1128c2ecf20Sopenharmony_cifree_mctl: 1138c2ecf20Sopenharmony_ci kfree(mctl); 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_civoid snd_media_stream_delete(struct snd_usb_substream *subs) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct media_ctl *mctl = subs->media_ctl; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (mctl) { 1228c2ecf20Sopenharmony_ci struct media_device *mdev; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci mdev = mctl->media_dev; 1258c2ecf20Sopenharmony_ci if (mdev && media_devnode_is_registered(mdev->devnode)) { 1268c2ecf20Sopenharmony_ci media_devnode_remove(mctl->intf_devnode); 1278c2ecf20Sopenharmony_ci media_device_unregister_entity(&mctl->media_entity); 1288c2ecf20Sopenharmony_ci media_entity_cleanup(&mctl->media_entity); 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci kfree(mctl); 1318c2ecf20Sopenharmony_ci subs->media_ctl = NULL; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ciint snd_media_start_pipeline(struct snd_usb_substream *subs) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct media_ctl *mctl = subs->media_ctl; 1388c2ecf20Sopenharmony_ci int ret = 0; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (!mctl) 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci mutex_lock(&mctl->media_dev->graph_mutex); 1448c2ecf20Sopenharmony_ci if (mctl->media_dev->enable_source) 1458c2ecf20Sopenharmony_ci ret = mctl->media_dev->enable_source(&mctl->media_entity, 1468c2ecf20Sopenharmony_ci &mctl->media_pipe); 1478c2ecf20Sopenharmony_ci mutex_unlock(&mctl->media_dev->graph_mutex); 1488c2ecf20Sopenharmony_ci return ret; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_civoid snd_media_stop_pipeline(struct snd_usb_substream *subs) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct media_ctl *mctl = subs->media_ctl; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (!mctl) 1568c2ecf20Sopenharmony_ci return; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci mutex_lock(&mctl->media_dev->graph_mutex); 1598c2ecf20Sopenharmony_ci if (mctl->media_dev->disable_source) 1608c2ecf20Sopenharmony_ci mctl->media_dev->disable_source(&mctl->media_entity); 1618c2ecf20Sopenharmony_ci mutex_unlock(&mctl->media_dev->graph_mutex); 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int snd_media_mixer_init(struct snd_usb_audio *chip) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct device *ctl_dev = &chip->card->ctl_dev; 1678c2ecf20Sopenharmony_ci struct media_intf_devnode *ctl_intf; 1688c2ecf20Sopenharmony_ci struct usb_mixer_interface *mixer; 1698c2ecf20Sopenharmony_ci struct media_device *mdev = chip->media_dev; 1708c2ecf20Sopenharmony_ci struct media_mixer_ctl *mctl; 1718c2ecf20Sopenharmony_ci u32 intf_type = MEDIA_INTF_T_ALSA_CONTROL; 1728c2ecf20Sopenharmony_ci int ret; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (!mdev) 1758c2ecf20Sopenharmony_ci return -ENODEV; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci ctl_intf = chip->ctl_intf_media_devnode; 1788c2ecf20Sopenharmony_ci if (!ctl_intf) { 1798c2ecf20Sopenharmony_ci ctl_intf = media_devnode_create(mdev, intf_type, 0, 1808c2ecf20Sopenharmony_ci MAJOR(ctl_dev->devt), 1818c2ecf20Sopenharmony_ci MINOR(ctl_dev->devt)); 1828c2ecf20Sopenharmony_ci if (!ctl_intf) 1838c2ecf20Sopenharmony_ci return -ENOMEM; 1848c2ecf20Sopenharmony_ci chip->ctl_intf_media_devnode = ctl_intf; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci list_for_each_entry(mixer, &chip->mixer_list, list) { 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (mixer->media_mixer_ctl) 1908c2ecf20Sopenharmony_ci continue; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* allocate media_mixer_ctl */ 1938c2ecf20Sopenharmony_ci mctl = kzalloc(sizeof(*mctl), GFP_KERNEL); 1948c2ecf20Sopenharmony_ci if (!mctl) 1958c2ecf20Sopenharmony_ci return -ENOMEM; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci mctl->media_dev = mdev; 1988c2ecf20Sopenharmony_ci mctl->media_entity.function = MEDIA_ENT_F_AUDIO_MIXER; 1998c2ecf20Sopenharmony_ci mctl->media_entity.name = chip->card->mixername; 2008c2ecf20Sopenharmony_ci mctl->media_pad[0].flags = MEDIA_PAD_FL_SINK; 2018c2ecf20Sopenharmony_ci mctl->media_pad[1].flags = MEDIA_PAD_FL_SOURCE; 2028c2ecf20Sopenharmony_ci mctl->media_pad[2].flags = MEDIA_PAD_FL_SOURCE; 2038c2ecf20Sopenharmony_ci media_entity_pads_init(&mctl->media_entity, MEDIA_MIXER_PAD_MAX, 2048c2ecf20Sopenharmony_ci mctl->media_pad); 2058c2ecf20Sopenharmony_ci ret = media_device_register_entity(mctl->media_dev, 2068c2ecf20Sopenharmony_ci &mctl->media_entity); 2078c2ecf20Sopenharmony_ci if (ret) { 2088c2ecf20Sopenharmony_ci kfree(mctl); 2098c2ecf20Sopenharmony_ci return ret; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci mctl->intf_link = media_create_intf_link(&mctl->media_entity, 2138c2ecf20Sopenharmony_ci &ctl_intf->intf, 2148c2ecf20Sopenharmony_ci MEDIA_LNK_FL_ENABLED); 2158c2ecf20Sopenharmony_ci if (!mctl->intf_link) { 2168c2ecf20Sopenharmony_ci media_device_unregister_entity(&mctl->media_entity); 2178c2ecf20Sopenharmony_ci media_entity_cleanup(&mctl->media_entity); 2188c2ecf20Sopenharmony_ci kfree(mctl); 2198c2ecf20Sopenharmony_ci return -ENOMEM; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci mctl->intf_devnode = ctl_intf; 2228c2ecf20Sopenharmony_ci mixer->media_mixer_ctl = mctl; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic void snd_media_mixer_delete(struct snd_usb_audio *chip) 2288c2ecf20Sopenharmony_ci{ 2298c2ecf20Sopenharmony_ci struct usb_mixer_interface *mixer; 2308c2ecf20Sopenharmony_ci struct media_device *mdev = chip->media_dev; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (!mdev) 2338c2ecf20Sopenharmony_ci return; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci list_for_each_entry(mixer, &chip->mixer_list, list) { 2368c2ecf20Sopenharmony_ci struct media_mixer_ctl *mctl; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci mctl = mixer->media_mixer_ctl; 2398c2ecf20Sopenharmony_ci if (!mixer->media_mixer_ctl) 2408c2ecf20Sopenharmony_ci continue; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (media_devnode_is_registered(mdev->devnode)) { 2438c2ecf20Sopenharmony_ci media_device_unregister_entity(&mctl->media_entity); 2448c2ecf20Sopenharmony_ci media_entity_cleanup(&mctl->media_entity); 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci kfree(mctl); 2478c2ecf20Sopenharmony_ci mixer->media_mixer_ctl = NULL; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci if (media_devnode_is_registered(mdev->devnode)) 2508c2ecf20Sopenharmony_ci media_devnode_remove(chip->ctl_intf_media_devnode); 2518c2ecf20Sopenharmony_ci chip->ctl_intf_media_devnode = NULL; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ciint snd_media_device_create(struct snd_usb_audio *chip, 2558c2ecf20Sopenharmony_ci struct usb_interface *iface) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci struct media_device *mdev; 2588c2ecf20Sopenharmony_ci struct usb_device *usbdev = interface_to_usbdev(iface); 2598c2ecf20Sopenharmony_ci int ret = 0; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci /* usb-audio driver is probed for each usb interface, and 2628c2ecf20Sopenharmony_ci * there are multiple interfaces per device. Avoid calling 2638c2ecf20Sopenharmony_ci * media_device_usb_allocate() each time usb_audio_probe() 2648c2ecf20Sopenharmony_ci * is called. Do it only once. 2658c2ecf20Sopenharmony_ci */ 2668c2ecf20Sopenharmony_ci if (chip->media_dev) { 2678c2ecf20Sopenharmony_ci mdev = chip->media_dev; 2688c2ecf20Sopenharmony_ci goto snd_mixer_init; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci mdev = media_device_usb_allocate(usbdev, KBUILD_MODNAME, THIS_MODULE); 2728c2ecf20Sopenharmony_ci if (IS_ERR(mdev)) 2738c2ecf20Sopenharmony_ci return -ENOMEM; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* save media device - avoid lookups */ 2768c2ecf20Sopenharmony_ci chip->media_dev = mdev; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_cisnd_mixer_init: 2798c2ecf20Sopenharmony_ci /* Create media entities for mixer and control dev */ 2808c2ecf20Sopenharmony_ci ret = snd_media_mixer_init(chip); 2818c2ecf20Sopenharmony_ci /* media_device might be registered, print error and continue */ 2828c2ecf20Sopenharmony_ci if (ret) 2838c2ecf20Sopenharmony_ci dev_err(&usbdev->dev, 2848c2ecf20Sopenharmony_ci "Couldn't create media mixer entities. Error: %d\n", 2858c2ecf20Sopenharmony_ci ret); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (!media_devnode_is_registered(mdev->devnode)) { 2888c2ecf20Sopenharmony_ci /* dont'register if snd_media_mixer_init() failed */ 2898c2ecf20Sopenharmony_ci if (ret) 2908c2ecf20Sopenharmony_ci goto create_fail; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci /* register media_device */ 2938c2ecf20Sopenharmony_ci ret = media_device_register(mdev); 2948c2ecf20Sopenharmony_cicreate_fail: 2958c2ecf20Sopenharmony_ci if (ret) { 2968c2ecf20Sopenharmony_ci snd_media_mixer_delete(chip); 2978c2ecf20Sopenharmony_ci media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); 2988c2ecf20Sopenharmony_ci /* clear saved media_dev */ 2998c2ecf20Sopenharmony_ci chip->media_dev = NULL; 3008c2ecf20Sopenharmony_ci dev_err(&usbdev->dev, 3018c2ecf20Sopenharmony_ci "Couldn't register media device. Error: %d\n", 3028c2ecf20Sopenharmony_ci ret); 3038c2ecf20Sopenharmony_ci return ret; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci } 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return ret; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_civoid snd_media_device_delete(struct snd_usb_audio *chip) 3118c2ecf20Sopenharmony_ci{ 3128c2ecf20Sopenharmony_ci struct media_device *mdev = chip->media_dev; 3138c2ecf20Sopenharmony_ci struct snd_usb_stream *stream; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci /* release resources */ 3168c2ecf20Sopenharmony_ci list_for_each_entry(stream, &chip->pcm_list, list) { 3178c2ecf20Sopenharmony_ci snd_media_stream_delete(&stream->substream[0]); 3188c2ecf20Sopenharmony_ci snd_media_stream_delete(&stream->substream[1]); 3198c2ecf20Sopenharmony_ci } 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci snd_media_mixer_delete(chip); 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci if (mdev) { 3248c2ecf20Sopenharmony_ci media_device_delete(mdev, KBUILD_MODNAME, THIS_MODULE); 3258c2ecf20Sopenharmony_ci chip->media_dev = NULL; 3268c2ecf20Sopenharmony_ci } 3278c2ecf20Sopenharmony_ci} 328