2012-11-12 02:56:37 -03:00
/*
* Driver for the MasterKit MA901 USB FM radio . This device plugs
* into the USB port and an analog audio input or headphones , so this thing
* only deals with initialization , frequency setting , volume .
*
* Copyright ( c ) 2012 Alexey Klimov < klimov . linux @ gmail . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/input.h>
# include <linux/videodev2.h>
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
# include <media/v4l2-ctrls.h>
# include <media/v4l2-event.h>
# include <linux/usb.h>
# include <linux/mutex.h>
# define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>"
# define DRIVER_DESC "Masterkit MA901 USB FM radio driver"
# define DRIVER_VERSION "0.0.1"
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRIVER_VERSION ) ;
# define USB_MA901_VENDOR 0x16c0
# define USB_MA901_PRODUCT 0x05df
/* dev_warn macro with driver name */
# define MA901_DRIVER_NAME "radio-ma901"
# define ma901radio_dev_warn(dev, fmt, arg...) \
dev_warn ( dev , MA901_DRIVER_NAME " - " fmt , # # arg )
# define ma901radio_dev_err(dev, fmt, arg...) \
dev_err ( dev , MA901_DRIVER_NAME " - " fmt , # # arg )
/* Probably USB_TIMEOUT should be modified in module parameter */
# define BUFFER_LENGTH 8
# define USB_TIMEOUT 500
# define FREQ_MIN 87.5
# define FREQ_MAX 108.0
# define FREQ_MUL 16000
# define MA901_VOLUME_MAX 16
# define MA901_VOLUME_MIN 0
/* Commands that device should understand
* List isn ' t full and will be updated with implementation of new functions
*/
# define MA901_RADIO_SET_FREQ 0x03
# define MA901_RADIO_SET_VOLUME 0x04
# define MA901_RADIO_SET_MONO_STEREO 0x05
/* Comfortable defines for ma901radio_set_stereo */
# define MA901_WANT_STEREO 0x50
# define MA901_WANT_MONO 0xd0
/* module parameter */
static int radio_nr = - 1 ;
module_param ( radio_nr , int , 0 ) ;
MODULE_PARM_DESC ( radio_nr , " Radio file number " ) ;
/* Data for one (physical) device */
struct ma901radio_device {
/* reference to USB and video device */
struct usb_device * usbdev ;
struct usb_interface * intf ;
struct video_device vdev ;
struct v4l2_device v4l2_dev ;
struct v4l2_ctrl_handler hdl ;
u8 * buffer ;
struct mutex lock ; /* buffer locking */
int curfreq ;
u16 volume ;
int stereo ;
bool muted ;
} ;
static inline struct ma901radio_device * to_ma901radio_dev ( struct v4l2_device * v4l2_dev )
{
return container_of ( v4l2_dev , struct ma901radio_device , v4l2_dev ) ;
}
/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
static int ma901radio_set_freq ( struct ma901radio_device * radio , int freq )
{
unsigned int freq_send = 0x300 + ( freq > > 5 ) / 25 ;
int retval ;
radio - > buffer [ 0 ] = 0x0a ;
radio - > buffer [ 1 ] = MA901_RADIO_SET_FREQ ;
radio - > buffer [ 2 ] = ( ( freq_send > > 8 ) & 0xff ) + 0x80 ;
radio - > buffer [ 3 ] = freq_send & 0xff ;
radio - > buffer [ 4 ] = 0x00 ;
radio - > buffer [ 5 ] = 0x00 ;
radio - > buffer [ 6 ] = 0x00 ;
radio - > buffer [ 7 ] = 0x00 ;
retval = usb_control_msg ( radio - > usbdev , usb_sndctrlpipe ( radio - > usbdev , 0 ) ,
9 , 0x21 , 0x0300 , 0 ,
radio - > buffer , BUFFER_LENGTH , USB_TIMEOUT ) ;
if ( retval < 0 )
return retval ;
radio - > curfreq = freq ;
return 0 ;
}
static int ma901radio_set_volume ( struct ma901radio_device * radio , u16 vol_to_set )
{
int retval ;
radio - > buffer [ 0 ] = 0x0a ;
radio - > buffer [ 1 ] = MA901_RADIO_SET_VOLUME ;
radio - > buffer [ 2 ] = 0xc2 ;
radio - > buffer [ 3 ] = vol_to_set + 0x20 ;
radio - > buffer [ 4 ] = 0x00 ;
radio - > buffer [ 5 ] = 0x00 ;
radio - > buffer [ 6 ] = 0x00 ;
radio - > buffer [ 7 ] = 0x00 ;
retval = usb_control_msg ( radio - > usbdev , usb_sndctrlpipe ( radio - > usbdev , 0 ) ,
9 , 0x21 , 0x0300 , 0 ,
radio - > buffer , BUFFER_LENGTH , USB_TIMEOUT ) ;
if ( retval < 0 )
return retval ;
radio - > volume = vol_to_set ;
return retval ;
}
static int ma901_set_stereo ( struct ma901radio_device * radio , u8 stereo )
{
int retval ;
radio - > buffer [ 0 ] = 0x0a ;
radio - > buffer [ 1 ] = MA901_RADIO_SET_MONO_STEREO ;
radio - > buffer [ 2 ] = stereo ;
radio - > buffer [ 3 ] = 0x00 ;
radio - > buffer [ 4 ] = 0x00 ;
radio - > buffer [ 5 ] = 0x00 ;
radio - > buffer [ 6 ] = 0x00 ;
radio - > buffer [ 7 ] = 0x00 ;
retval = usb_control_msg ( radio - > usbdev , usb_sndctrlpipe ( radio - > usbdev , 0 ) ,
9 , 0x21 , 0x0300 , 0 ,
radio - > buffer , BUFFER_LENGTH , USB_TIMEOUT ) ;
if ( retval < 0 )
return retval ;
if ( stereo = = MA901_WANT_STEREO )
radio - > stereo = V4L2_TUNER_MODE_STEREO ;
else
radio - > stereo = V4L2_TUNER_MODE_MONO ;
return retval ;
}
/* Handle unplugging the device.
* We call video_unregister_device in any case .
* The last function called in this procedure is
* usb_ma901radio_device_release .
*/
static void usb_ma901radio_disconnect ( struct usb_interface * intf )
{
struct ma901radio_device * radio = to_ma901radio_dev ( usb_get_intfdata ( intf ) ) ;
mutex_lock ( & radio - > lock ) ;
video_unregister_device ( & radio - > vdev ) ;
usb_set_intfdata ( intf , NULL ) ;
v4l2_device_disconnect ( & radio - > v4l2_dev ) ;
mutex_unlock ( & radio - > lock ) ;
v4l2_device_put ( & radio - > v4l2_dev ) ;
}
/* vidioc_querycap - query device capabilities */
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
struct ma901radio_device * radio = video_drvdata ( file ) ;
strlcpy ( v - > driver , " radio-ma901 " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " Masterkit MA901 USB FM Radio " , sizeof ( v - > card ) ) ;
usb_make_path ( radio - > usbdev , v - > bus_info , sizeof ( v - > bus_info ) ) ;
v - > device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER ;
v - > capabilities = v - > device_caps | V4L2_CAP_DEVICE_CAPS ;
return 0 ;
}
/* vidioc_g_tuner - get tuner attributes */
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
struct ma901radio_device * radio = video_drvdata ( file ) ;
if ( v - > index > 0 )
return - EINVAL ;
v - > signal = 0 ;
/* TODO: the same words like in _probe() goes here.
* When receiving of stats will be implemented then we can call
* ma901radio_get_stat ( ) .
* retval = ma901radio_get_stat ( radio , & is_stereo , & v - > signal ) ;
*/
strcpy ( v - > name , " FM " ) ;
v - > type = V4L2_TUNER_RADIO ;
v - > rangelow = FREQ_MIN * FREQ_MUL ;
v - > rangehigh = FREQ_MAX * FREQ_MUL ;
v - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO ;
/* v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; */
v - > audmode = radio - > stereo ?
V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO ;
return 0 ;
}
/* vidioc_s_tuner - set tuner attributes */
static int vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 06:10:06 -03:00
const struct v4l2_tuner * v )
2012-11-12 02:56:37 -03:00
{
struct ma901radio_device * radio = video_drvdata ( file ) ;
if ( v - > index > 0 )
return - EINVAL ;
/* mono/stereo selector */
switch ( v - > audmode ) {
case V4L2_TUNER_MODE_MONO :
return ma901_set_stereo ( radio , MA901_WANT_MONO ) ;
default :
return ma901_set_stereo ( radio , MA901_WANT_STEREO ) ;
}
}
/* vidioc_s_frequency - set tuner radio frequency */
static int vidioc_s_frequency ( struct file * file , void * priv ,
2013-03-19 04:09:26 -03:00
const struct v4l2_frequency * f )
2012-11-12 02:56:37 -03:00
{
struct ma901radio_device * radio = video_drvdata ( file ) ;
if ( f - > tuner ! = 0 )
return - EINVAL ;
return ma901radio_set_freq ( radio , clamp_t ( unsigned , f - > frequency ,
FREQ_MIN * FREQ_MUL , FREQ_MAX * FREQ_MUL ) ) ;
}
/* vidioc_g_frequency - get tuner radio frequency */
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
struct ma901radio_device * radio = video_drvdata ( file ) ;
if ( f - > tuner ! = 0 )
return - EINVAL ;
f - > frequency = radio - > curfreq ;
return 0 ;
}
static int usb_ma901radio_s_ctrl ( struct v4l2_ctrl * ctrl )
{
struct ma901radio_device * radio =
container_of ( ctrl - > handler , struct ma901radio_device , hdl ) ;
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_VOLUME : /* set volume */
return ma901radio_set_volume ( radio , ( u16 ) ctrl - > val ) ;
}
return - EINVAL ;
}
/* TODO: Should we really need to implement suspend and resume functions?
* Radio has it ' s own memory and will continue playing if power is present
* on usb port and on resume it will start to play again based on freq , volume
* values in device memory .
*/
static int usb_ma901radio_suspend ( struct usb_interface * intf , pm_message_t message )
{
return 0 ;
}
static int usb_ma901radio_resume ( struct usb_interface * intf )
{
return 0 ;
}
static const struct v4l2_ctrl_ops usb_ma901radio_ctrl_ops = {
. s_ctrl = usb_ma901radio_s_ctrl ,
} ;
/* File system interface */
static const struct v4l2_file_operations usb_ma901radio_fops = {
. owner = THIS_MODULE ,
. open = v4l2_fh_open ,
. release = v4l2_fh_release ,
. poll = v4l2_ctrl_poll ,
. unlocked_ioctl = video_ioctl2 ,
} ;
static const struct v4l2_ioctl_ops usb_ma901radio_ioctl_ops = {
. vidioc_querycap = vidioc_querycap ,
. vidioc_g_tuner = vidioc_g_tuner ,
. vidioc_s_tuner = vidioc_s_tuner ,
. vidioc_g_frequency = vidioc_g_frequency ,
. vidioc_s_frequency = vidioc_s_frequency ,
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
} ;
static void usb_ma901radio_release ( struct v4l2_device * v4l2_dev )
{
struct ma901radio_device * radio = to_ma901radio_dev ( v4l2_dev ) ;
v4l2_ctrl_handler_free ( & radio - > hdl ) ;
v4l2_device_unregister ( & radio - > v4l2_dev ) ;
kfree ( radio - > buffer ) ;
kfree ( radio ) ;
}
/* check if the device is present and register with v4l and usb if it is */
static int usb_ma901radio_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
{
2013-03-28 00:08:19 +04:00
struct usb_device * dev = interface_to_usbdev ( intf ) ;
2012-11-12 02:56:37 -03:00
struct ma901radio_device * radio ;
int retval = 0 ;
2013-03-28 00:08:19 +04:00
/* Masterkit MA901 usb radio has the same USB ID as many others
* Atmel V - USB devices . Let ' s make additional checks to be sure
* that this is our device .
*/
if ( dev - > product & & dev - > manufacturer & &
( strncmp ( dev - > product , " MA901 " , 5 ) ! = 0
| | strncmp ( dev - > manufacturer , " www.masterkit.ru " , 16 ) ! = 0 ) )
return - ENODEV ;
2012-11-12 02:56:37 -03:00
radio = kzalloc ( sizeof ( struct ma901radio_device ) , GFP_KERNEL ) ;
if ( ! radio ) {
dev_err ( & intf - > dev , " kzalloc for ma901radio_device failed \n " ) ;
retval = - ENOMEM ;
goto err ;
}
radio - > buffer = kmalloc ( BUFFER_LENGTH , GFP_KERNEL ) ;
if ( ! radio - > buffer ) {
dev_err ( & intf - > dev , " kmalloc for radio->buffer failed \n " ) ;
retval = - ENOMEM ;
goto err_nobuf ;
}
retval = v4l2_device_register ( & intf - > dev , & radio - > v4l2_dev ) ;
if ( retval < 0 ) {
dev_err ( & intf - > dev , " couldn't register v4l2_device \n " ) ;
goto err_v4l2 ;
}
v4l2_ctrl_handler_init ( & radio - > hdl , 1 ) ;
/* TODO:It looks like this radio doesn't have mute/unmute control
* and windows program just emulate it using volume control .
* Let ' s plan to do the same in this driver .
*
* v4l2_ctrl_new_std ( & radio - > hdl , & usb_ma901radio_ctrl_ops ,
* V4L2_CID_AUDIO_MUTE , 0 , 1 , 1 , 1 ) ;
*/
v4l2_ctrl_new_std ( & radio - > hdl , & usb_ma901radio_ctrl_ops ,
V4L2_CID_AUDIO_VOLUME , MA901_VOLUME_MIN ,
MA901_VOLUME_MAX , 1 , MA901_VOLUME_MAX ) ;
if ( radio - > hdl . error ) {
retval = radio - > hdl . error ;
dev_err ( & intf - > dev , " couldn't register control \n " ) ;
goto err_ctrl ;
}
mutex_init ( & radio - > lock ) ;
radio - > v4l2_dev . ctrl_handler = & radio - > hdl ;
radio - > v4l2_dev . release = usb_ma901radio_release ;
strlcpy ( radio - > vdev . name , radio - > v4l2_dev . name ,
sizeof ( radio - > vdev . name ) ) ;
radio - > vdev . v4l2_dev = & radio - > v4l2_dev ;
radio - > vdev . fops = & usb_ma901radio_fops ;
radio - > vdev . ioctl_ops = & usb_ma901radio_ioctl_ops ;
radio - > vdev . release = video_device_release_empty ;
radio - > vdev . lock = & radio - > lock ;
radio - > usbdev = interface_to_usbdev ( intf ) ;
radio - > intf = intf ;
usb_set_intfdata ( intf , & radio - > v4l2_dev ) ;
radio - > curfreq = 95.21 * FREQ_MUL ;
video_set_drvdata ( & radio - > vdev , radio ) ;
/* TODO: we can get some statistics (freq, volume) from device
* but it ' s not implemented yet . After insertion in usb - port radio
* setups frequency and starts playing without any initialization .
* So we don ' t call usb_ma901radio_init / get_stat ( ) here .
* retval = usb_ma901radio_init ( radio ) ;
*/
retval = video_register_device ( & radio - > vdev , VFL_TYPE_RADIO ,
radio_nr ) ;
if ( retval < 0 ) {
dev_err ( & intf - > dev , " could not register video device \n " ) ;
goto err_vdev ;
}
return 0 ;
err_vdev :
v4l2_ctrl_handler_free ( & radio - > hdl ) ;
err_ctrl :
v4l2_device_unregister ( & radio - > v4l2_dev ) ;
err_v4l2 :
kfree ( radio - > buffer ) ;
err_nobuf :
kfree ( radio ) ;
err :
return retval ;
}
/* USB Device ID List */
2017-08-13 04:54:45 -04:00
static const struct usb_device_id usb_ma901radio_device_table [ ] = {
2012-11-12 02:56:37 -03:00
{ USB_DEVICE_AND_INTERFACE_INFO ( USB_MA901_VENDOR , USB_MA901_PRODUCT ,
USB_CLASS_HID , 0 , 0 ) } ,
{ } /* Terminating entry */
} ;
MODULE_DEVICE_TABLE ( usb , usb_ma901radio_device_table ) ;
/* USB subsystem interface */
static struct usb_driver usb_ma901radio_driver = {
. name = MA901_DRIVER_NAME ,
. probe = usb_ma901radio_probe ,
. disconnect = usb_ma901radio_disconnect ,
. suspend = usb_ma901radio_suspend ,
. resume = usb_ma901radio_resume ,
. reset_resume = usb_ma901radio_resume ,
. id_table = usb_ma901radio_device_table ,
} ;
module_usb_driver ( usb_ma901radio_driver ) ;