2016-03-02 13:50:31 -03:00
/*
* media . c - Media Controller specific ALSA driver code
*
* Copyright ( c ) 2016 Shuah Khan < shuahkh @ osg . samsung . com >
* Copyright ( c ) 2016 Samsung Electronics Co . , Ltd .
*
* This file is released under the GPLv2 .
*/
/*
* This file adds Media Controller support to ALSA driver
* to use the Media Controller API to share tuner with DVB
* and V4L2 drivers that control media device . Media device
* is created based on existing quirks framework . Using this
* approach , the media controller API usage can be added for
* a specific device .
*/
# include <linux/init.h>
# include <linux/list.h>
# include <linux/mutex.h>
# include <linux/slab.h>
# include <linux/usb.h>
# include <sound/pcm.h>
# include <sound/core.h>
# include "usbaudio.h"
# include "card.h"
# include "mixer.h"
# include "media.h"
static int media_snd_enable_source ( struct media_ctl * mctl )
{
if ( mctl & & mctl - > media_dev - > enable_source )
return mctl - > media_dev - > enable_source ( & mctl - > media_entity ,
& mctl - > media_pipe ) ;
return 0 ;
}
static void media_snd_disable_source ( struct media_ctl * mctl )
{
if ( mctl & & mctl - > media_dev - > disable_source )
mctl - > media_dev - > disable_source ( & mctl - > media_entity ) ;
}
int media_snd_stream_init ( struct snd_usb_substream * subs , struct snd_pcm * pcm ,
int stream )
{
struct media_device * mdev ;
struct media_ctl * mctl ;
struct device * pcm_dev = & pcm - > streams [ stream ] . dev ;
u32 intf_type ;
int ret = 0 ;
u16 mixer_pad ;
struct media_entity * entity ;
mdev = subs - > stream - > chip - > media_dev ;
if ( ! mdev )
return - ENODEV ;
if ( subs - > media_ctl )
return 0 ;
/* allocate media_ctl */
mctl = kzalloc ( sizeof ( * mctl ) , GFP_KERNEL ) ;
if ( ! mctl )
return - ENOMEM ;
mctl - > media_dev = mdev ;
if ( stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
intf_type = MEDIA_INTF_T_ALSA_PCM_PLAYBACK ;
mctl - > media_entity . function = MEDIA_ENT_F_AUDIO_PLAYBACK ;
mctl - > media_pad . flags = MEDIA_PAD_FL_SOURCE ;
mixer_pad = 1 ;
} else {
intf_type = MEDIA_INTF_T_ALSA_PCM_CAPTURE ;
mctl - > media_entity . function = MEDIA_ENT_F_AUDIO_CAPTURE ;
mctl - > media_pad . flags = MEDIA_PAD_FL_SINK ;
mixer_pad = 2 ;
}
mctl - > media_entity . name = pcm - > name ;
media_entity_pads_init ( & mctl - > media_entity , 1 , & mctl - > media_pad ) ;
ret = media_device_register_entity ( mctl - > media_dev ,
& mctl - > media_entity ) ;
if ( ret )
2016-03-03 13:51:26 -03:00
goto free_mctl ;
2016-03-02 13:50:31 -03:00
mctl - > intf_devnode = media_devnode_create ( mdev , intf_type , 0 ,
MAJOR ( pcm_dev - > devt ) ,
MINOR ( pcm_dev - > devt ) ) ;
if ( ! mctl - > intf_devnode ) {
ret = - ENOMEM ;
2016-03-03 13:51:26 -03:00
goto unregister_entity ;
2016-03-02 13:50:31 -03:00
}
mctl - > intf_link = media_create_intf_link ( & mctl - > media_entity ,
& mctl - > intf_devnode - > intf ,
MEDIA_LNK_FL_ENABLED ) ;
if ( ! mctl - > intf_link ) {
ret = - ENOMEM ;
2016-03-03 13:51:26 -03:00
goto devnode_remove ;
2016-03-02 13:50:31 -03:00
}
/* create link between mixer and audio */
media_device_for_each_entity ( entity , mdev ) {
switch ( entity - > function ) {
case MEDIA_ENT_F_AUDIO_MIXER :
ret = media_create_pad_link ( entity , mixer_pad ,
& mctl - > media_entity , 0 ,
MEDIA_LNK_FL_ENABLED ) ;
if ( ret )
2016-03-03 13:51:26 -03:00
goto remove_intf_link ;
2016-03-02 13:50:31 -03:00
break ;
}
}
subs - > media_ctl = mctl ;
return 0 ;
2016-03-03 13:51:26 -03:00
remove_intf_link :
2016-03-02 13:50:31 -03:00
media_remove_intf_link ( mctl - > intf_link ) ;
2016-03-03 13:51:26 -03:00
devnode_remove :
2016-03-02 13:50:31 -03:00
media_devnode_remove ( mctl - > intf_devnode ) ;
2016-03-03 13:51:26 -03:00
unregister_entity :
2016-03-02 13:50:31 -03:00
media_device_unregister_entity ( & mctl - > media_entity ) ;
2016-03-03 13:51:26 -03:00
free_mctl :
2016-03-02 13:50:31 -03:00
kfree ( mctl ) ;
return ret ;
}
void media_snd_stream_delete ( struct snd_usb_substream * subs )
{
struct media_ctl * mctl = subs - > media_ctl ;
if ( mctl & & mctl - > media_dev ) {
struct media_device * mdev ;
mdev = subs - > stream - > chip - > media_dev ;
if ( mdev & & media_devnode_is_registered ( & mdev - > devnode ) ) {
media_devnode_remove ( mctl - > intf_devnode ) ;
media_device_unregister_entity ( & mctl - > media_entity ) ;
media_entity_cleanup ( & mctl - > media_entity ) ;
}
kfree ( mctl ) ;
subs - > media_ctl = NULL ;
}
}
int media_snd_start_pipeline ( struct snd_usb_substream * subs )
{
struct media_ctl * mctl = subs - > media_ctl ;
if ( mctl )
return media_snd_enable_source ( mctl ) ;
return 0 ;
}
void media_snd_stop_pipeline ( struct snd_usb_substream * subs )
{
struct media_ctl * mctl = subs - > media_ctl ;
if ( mctl )
media_snd_disable_source ( mctl ) ;
}
int media_snd_mixer_init ( struct snd_usb_audio * chip )
{
struct device * ctl_dev = & chip - > card - > ctl_dev ;
struct media_intf_devnode * ctl_intf ;
struct usb_mixer_interface * mixer ;
struct media_device * mdev = chip - > media_dev ;
struct media_mixer_ctl * mctl ;
u32 intf_type = MEDIA_INTF_T_ALSA_CONTROL ;
int ret ;
if ( ! mdev )
return - ENODEV ;
ctl_intf = chip - > ctl_intf_media_devnode ;
if ( ! ctl_intf ) {
ctl_intf = media_devnode_create ( mdev , intf_type , 0 ,
MAJOR ( ctl_dev - > devt ) ,
MINOR ( ctl_dev - > devt ) ) ;
if ( ! ctl_intf )
return - ENOMEM ;
chip - > ctl_intf_media_devnode = ctl_intf ;
}
list_for_each_entry ( mixer , & chip - > mixer_list , list ) {
if ( mixer - > media_mixer_ctl )
continue ;
/* allocate media_mixer_ctl */
mctl = kzalloc ( sizeof ( * mctl ) , GFP_KERNEL ) ;
if ( ! mctl )
return - ENOMEM ;
mctl - > media_dev = mdev ;
mctl - > media_entity . function = MEDIA_ENT_F_AUDIO_MIXER ;
mctl - > media_entity . name = chip - > card - > mixername ;
mctl - > media_pad [ 0 ] . flags = MEDIA_PAD_FL_SINK ;
mctl - > media_pad [ 1 ] . flags = MEDIA_PAD_FL_SOURCE ;
mctl - > media_pad [ 2 ] . flags = MEDIA_PAD_FL_SOURCE ;
media_entity_pads_init ( & mctl - > media_entity , MEDIA_MIXER_PAD_MAX ,
mctl - > media_pad ) ;
ret = media_device_register_entity ( mctl - > media_dev ,
& mctl - > media_entity ) ;
if ( ret ) {
kfree ( mctl ) ;
return ret ;
}
mctl - > intf_link = media_create_intf_link ( & mctl - > media_entity ,
& ctl_intf - > intf ,
MEDIA_LNK_FL_ENABLED ) ;
if ( ! mctl - > intf_link ) {
media_device_unregister_entity ( & mctl - > media_entity ) ;
media_entity_cleanup ( & mctl - > media_entity ) ;
kfree ( mctl ) ;
return - ENOMEM ;
}
mctl - > intf_devnode = ctl_intf ;
mixer - > media_mixer_ctl = mctl ;
}
return 0 ;
}
static void media_snd_mixer_delete ( struct snd_usb_audio * chip )
{
struct usb_mixer_interface * mixer ;
struct media_device * mdev = chip - > media_dev ;
if ( ! mdev )
return ;
list_for_each_entry ( mixer , & chip - > mixer_list , list ) {
struct media_mixer_ctl * mctl ;
mctl = mixer - > media_mixer_ctl ;
if ( ! mixer - > media_mixer_ctl )
continue ;
if ( media_devnode_is_registered ( & mdev - > devnode ) ) {
media_device_unregister_entity ( & mctl - > media_entity ) ;
media_entity_cleanup ( & mctl - > media_entity ) ;
}
kfree ( mctl ) ;
mixer - > media_mixer_ctl = NULL ;
}
if ( media_devnode_is_registered ( & mdev - > devnode ) )
media_devnode_remove ( chip - > ctl_intf_media_devnode ) ;
chip - > ctl_intf_media_devnode = NULL ;
}
int media_snd_device_create ( struct snd_usb_audio * chip ,
struct usb_interface * iface )
{
struct media_device * mdev ;
struct usb_device * usbdev = interface_to_usbdev ( iface ) ;
int ret ;
mdev = media_device_get_devres ( & usbdev - > dev ) ;
if ( ! mdev )
return - ENOMEM ;
if ( ! mdev - > dev ) {
/* register media device */
mdev - > dev = & usbdev - > dev ;
if ( usbdev - > product )
strlcpy ( mdev - > model , usbdev - > product ,
sizeof ( mdev - > model ) ) ;
if ( usbdev - > serial )
strlcpy ( mdev - > serial , usbdev - > serial ,
sizeof ( mdev - > serial ) ) ;
strcpy ( mdev - > bus_info , usbdev - > devpath ) ;
mdev - > hw_revision = le16_to_cpu ( usbdev - > descriptor . bcdDevice ) ;
media_device_init ( mdev ) ;
}
if ( ! media_devnode_is_registered ( & mdev - > devnode ) ) {
ret = media_device_register ( mdev ) ;
if ( ret ) {
dev_err ( & usbdev - > dev ,
" Couldn't register media device. Error: %d \n " ,
ret ) ;
return ret ;
}
}
/* save media device - avoid lookups */
chip - > media_dev = mdev ;
/* Create media entities for mixer and control dev */
ret = media_snd_mixer_init ( chip ) ;
if ( ret ) {
dev_err ( & usbdev - > dev ,
" Couldn't create media mixer entities. Error: %d \n " ,
ret ) ;
/* clear saved media_dev */
chip - > media_dev = NULL ;
return ret ;
}
return 0 ;
}
void media_snd_device_delete ( struct snd_usb_audio * chip )
{
struct media_device * mdev = chip - > media_dev ;
media_snd_mixer_delete ( chip ) ;
if ( mdev ) {
if ( media_devnode_is_registered ( & mdev - > devnode ) )
media_device_unregister ( mdev ) ;
chip - > media_dev = NULL ;
}
}