2005-04-16 15:20:36 -07:00
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2006-04-08 16:06:16 -03:00
*
2005-04-16 15:20:36 -07:00
* radio - gemtek - pci . c - Gemtek PCI Radio driver
* ( C ) 2001 Vladimir Shebordaev < vshebordaev @ mail . ru >
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 .
*
* You should have received a copy of the GNU General Public
* License along with this program ; if not , write to the Free
* Software Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 ,
* USA .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Gemtek Corp still silently refuses to release any specifications
* of their multimedia devices , so the protocol still has to be
* reverse engineered .
*
* The v4l code was inspired by Jonas Munsin ' s Gemtek serial line
* radio device driver .
*
* Please , let me know if this piece of code was useful : )
2006-04-08 16:06:16 -03:00
*
2005-04-16 15:20:36 -07:00
* TODO : multiple device support and portability were not tested
*
2006-08-08 09:10:01 -03:00
* Converted to V4L2 API by Mauro Carvalho Chehab < mchehab @ infradead . org >
*
2005-04-16 15:20:36 -07:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
# include <linux/types.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
2006-08-08 09:10:01 -03:00
# include <linux/videodev2.h>
2005-04-16 15:20:36 -07:00
# include <linux/errno.h>
2006-08-08 09:10:01 -03:00
# include <linux/version.h> /* for KERNEL_VERSION MACRO */
2009-03-06 13:50:07 -03:00
# include <linux/io.h>
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
2006-08-08 09:10:01 -03:00
2009-03-06 13:50:07 -03:00
MODULE_AUTHOR ( " Vladimir Shebordaev <vshebordaev@mail.ru> " ) ;
MODULE_DESCRIPTION ( " The video4linux driver for the Gemtek PCI Radio Card " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int nr_radio = - 1 ;
static int mx = 1 ;
module_param ( mx , bool , 0 ) ;
MODULE_PARM_DESC ( mx , " single digit: 1 - turn off the turner upon module exit (default), 0 - do not " ) ;
module_param ( nr_radio , int , 0 ) ;
MODULE_PARM_DESC ( nr_radio , " video4linux device number to use " ) ;
# define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
2005-04-16 15:20:36 -07:00
# ifndef PCI_VENDOR_ID_GEMTEK
# define PCI_VENDOR_ID_GEMTEK 0x5046
# endif
# ifndef PCI_DEVICE_ID_GEMTEK_PR103
# define PCI_DEVICE_ID_GEMTEK_PR103 0x1001
# endif
# ifndef GEMTEK_PCI_RANGE_LOW
# define GEMTEK_PCI_RANGE_LOW (87*16000)
# endif
# ifndef GEMTEK_PCI_RANGE_HIGH
# define GEMTEK_PCI_RANGE_HIGH (108*16000)
# endif
2009-03-06 13:50:07 -03:00
struct gemtek_pci {
struct v4l2_device v4l2_dev ;
struct video_device vdev ;
struct mutex lock ;
struct pci_dev * pdev ;
2006-04-08 16:06:16 -03:00
2005-04-16 15:20:36 -07:00
u32 iobase ;
u32 length ;
2006-04-08 16:06:16 -03:00
2005-04-16 15:20:36 -07:00
u32 current_frequency ;
u8 mute ;
} ;
2009-03-06 13:50:07 -03:00
static inline struct gemtek_pci * to_gemtek_pci ( struct v4l2_device * v4l2_dev )
{
return container_of ( v4l2_dev , struct gemtek_pci , v4l2_dev ) ;
}
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
static inline u8 gemtek_pci_out ( u16 value , u32 port )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
outw ( value , port ) ;
2005-04-16 15:20:36 -07:00
return ( u8 ) value ;
}
2009-03-06 13:50:07 -03:00
# define _b0(v) (*((u8 *)&v))
static void __gemtek_pci_cmd ( u16 value , u32 port , u8 * last_byte , int keep )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
u8 byte = * last_byte ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
if ( ! value ) {
if ( ! keep )
2005-04-16 15:20:36 -07:00
value = ( u16 ) port ;
2006-04-08 16:06:16 -03:00
byte & = 0xfd ;
2005-04-16 15:20:36 -07:00
} else
byte | = 2 ;
2009-03-06 13:50:07 -03:00
_b0 ( value ) = byte ;
outw ( value , port ) ;
2005-04-16 15:20:36 -07:00
byte | = 1 ;
2009-03-06 13:50:07 -03:00
_b0 ( value ) = byte ;
outw ( value , port ) ;
2005-04-16 15:20:36 -07:00
byte & = 0xfe ;
2009-03-06 13:50:07 -03:00
_b0 ( value ) = byte ;
outw ( value , port ) ;
2006-04-08 16:06:16 -03:00
2005-04-16 15:20:36 -07:00
* last_byte = byte ;
}
2009-03-06 13:50:07 -03:00
static inline void gemtek_pci_nil ( u32 port , u8 * last_byte )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
__gemtek_pci_cmd ( 0x00 , port , last_byte , false ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static inline void gemtek_pci_cmd ( u16 cmd , u32 port , u8 * last_byte )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
__gemtek_pci_cmd ( cmd , port , last_byte , true ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static void gemtek_pci_setfrequency ( struct gemtek_pci * card , unsigned long frequency )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
int i ;
u32 value = frequency / 200 + 856 ;
u16 mask = 0x8000 ;
2005-04-16 15:20:36 -07:00
u8 last_byte ;
u32 port = card - > iobase ;
2009-03-06 13:50:07 -03:00
mutex_lock ( & card - > lock ) ;
card - > current_frequency = frequency ;
last_byte = gemtek_pci_out ( 0x06 , port ) ;
2005-04-16 15:20:36 -07:00
i = 0 ;
do {
2009-03-06 13:50:07 -03:00
gemtek_pci_nil ( port , & last_byte ) ;
2005-04-16 15:20:36 -07:00
i + + ;
2009-03-06 13:50:07 -03:00
} while ( i < 9 ) ;
2005-04-16 15:20:36 -07:00
i = 0 ;
do {
2009-03-06 13:50:07 -03:00
gemtek_pci_cmd ( value & mask , port , & last_byte ) ;
2005-04-16 15:20:36 -07:00
mask > > = 1 ;
i + + ;
2009-03-06 13:50:07 -03:00
} while ( i < 16 ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
outw ( 0x10 , port ) ;
mutex_unlock ( & card - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static void gemtek_pci_mute ( struct gemtek_pci * card )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
mutex_lock ( & card - > lock ) ;
outb ( 0x1f , card - > iobase ) ;
2007-02-06 21:55:07 -03:00
card - > mute = true ;
2009-03-06 13:50:07 -03:00
mutex_unlock ( & card - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static void gemtek_pci_unmute ( struct gemtek_pci * card )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
mutex_lock ( & card - > lock ) ;
if ( card - > mute ) {
gemtek_pci_setfrequency ( card , card - > current_frequency ) ;
2007-02-06 21:55:07 -03:00
card - > mute = false ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
mutex_unlock ( & card - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static int gemtek_pci_getsignal ( struct gemtek_pci * card )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
int sig ;
mutex_lock ( & card - > lock ) ;
sig = ( inb ( card - > iobase ) & 0x08 ) ? 0 : 1 ;
mutex_unlock ( & card - > lock ) ;
return sig ;
2005-04-16 15:20:36 -07:00
}
2007-04-26 10:42:12 -03:00
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2007-04-26 10:42:12 -03:00
strlcpy ( v - > driver , " radio-gemtek-pci " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " GemTek PCI Radio " , sizeof ( v - > card ) ) ;
2009-03-06 13:50:07 -03:00
snprintf ( v - > bus_info , sizeof ( v - > bus_info ) , " PCI:%s " , pci_name ( card - > pdev ) ) ;
2007-04-26 10:42:12 -03:00
v - > version = RADIO_VERSION ;
2009-03-06 13:50:07 -03:00
v - > capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO ;
2007-04-26 10:42:12 -03:00
return 0 ;
}
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
if ( v - > index > 0 )
return - EINVAL ;
2009-03-06 13:50:07 -03:00
strlcpy ( v - > name , " FM " , sizeof ( v - > name ) ) ;
2007-04-26 10:42:12 -03:00
v - > type = V4L2_TUNER_RADIO ;
v - > rangelow = GEMTEK_PCI_RANGE_LOW ;
v - > rangehigh = GEMTEK_PCI_RANGE_HIGH ;
v - > rxsubchans = V4L2_TUNER_SUB_MONO ;
v - > capability = V4L2_TUNER_CAP_LOW ;
v - > audmode = V4L2_TUNER_MODE_MONO ;
v - > signal = 0xffff * gemtek_pci_getsignal ( card ) ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
static int vidioc_s_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2009-03-06 13:50:07 -03:00
return v - > index ? - EINVAL : 0 ;
2007-04-26 10:42:12 -03:00
}
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
static int vidioc_s_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2006-08-08 09:10:01 -03:00
2009-03-06 13:50:07 -03:00
if ( f - > frequency < GEMTEK_PCI_RANGE_LOW | |
f - > frequency > GEMTEK_PCI_RANGE_HIGH )
2007-04-26 10:42:12 -03:00
return - EINVAL ;
gemtek_pci_setfrequency ( card , f - > frequency ) ;
card - > mute = false ;
return 0 ;
}
2006-08-08 09:10:01 -03:00
2007-04-26 10:42:12 -03:00
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2006-08-08 09:10:01 -03:00
2007-04-26 10:42:12 -03:00
f - > type = V4L2_TUNER_RADIO ;
f - > frequency = card - > current_frequency ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
static int vidioc_queryctrl ( struct file * file , void * priv ,
struct v4l2_queryctrl * qc )
{
2009-03-06 13:50:07 -03:00
switch ( qc - > id ) {
case V4L2_CID_AUDIO_MUTE :
return v4l2_ctrl_query_fill ( qc , 0 , 1 , 1 , 1 ) ;
case V4L2_CID_AUDIO_VOLUME :
return v4l2_ctrl_query_fill ( qc , 0 , 65535 , 65535 , 65535 ) ;
2007-04-26 10:42:12 -03:00
}
return - EINVAL ;
}
static int vidioc_g_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2006-04-08 16:06:16 -03:00
2007-04-26 10:42:12 -03:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
ctrl - > value = card - > mute ;
return 0 ;
case V4L2_CID_AUDIO_VOLUME :
if ( card - > mute )
ctrl - > value = 0 ;
else
ctrl - > value = 65535 ;
return 0 ;
}
return - EINVAL ;
}
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
static int vidioc_s_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card = video_drvdata ( file ) ;
2005-04-16 15:20:36 -07:00
2007-04-26 10:42:12 -03:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
if ( ctrl - > value )
gemtek_pci_mute ( card ) ;
else
gemtek_pci_unmute ( card ) ;
return 0 ;
case V4L2_CID_AUDIO_VOLUME :
if ( ctrl - > value )
gemtek_pci_unmute ( card ) ;
else
gemtek_pci_mute ( card ) ;
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-04-26 10:42:12 -03:00
return - EINVAL ;
}
static int vidioc_g_input ( struct file * filp , void * priv , unsigned int * i )
{
* i = 0 ;
return 0 ;
}
static int vidioc_s_input ( struct file * filp , void * priv , unsigned int i )
{
2009-03-06 13:50:07 -03:00
return i ? - EINVAL : 0 ;
}
static int vidioc_g_audio ( struct file * file , void * priv ,
struct v4l2_audio * a )
{
a - > index = 0 ;
strlcpy ( a - > name , " Radio " , sizeof ( a - > name ) ) ;
a - > capability = V4L2_AUDCAP_STEREO ;
2007-04-26 10:42:12 -03:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-04-26 10:42:12 -03:00
static int vidioc_s_audio ( struct file * file , void * priv ,
struct v4l2_audio * a )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
return a - > index ? - EINVAL : 0 ;
2005-04-16 15:20:36 -07:00
}
enum {
GEMTEK_PR103
} ;
static char * card_names [ ] __devinitdata = {
" GEMTEK_PR103 "
} ;
static struct pci_device_id gemtek_pci_id [ ] =
{
{ PCI_VENDOR_ID_GEMTEK , PCI_DEVICE_ID_GEMTEK_PR103 ,
PCI_ANY_ID , PCI_ANY_ID , 0 , 0 , GEMTEK_PR103 } ,
{ 0 }
} ;
2009-03-06 13:50:07 -03:00
MODULE_DEVICE_TABLE ( pci , gemtek_pci_id ) ;
2005-04-16 15:20:36 -07:00
2008-12-30 06:58:20 -03:00
static const struct v4l2_file_operations gemtek_pci_fops = {
2005-04-16 15:20:36 -07:00
. owner = THIS_MODULE ,
2007-04-26 10:42:12 -03:00
. ioctl = video_ioctl2 ,
2005-04-16 15:20:36 -07:00
} ;
2008-07-21 02:57:38 -03:00
static const struct v4l2_ioctl_ops gemtek_pci_ioctl_ops = {
2007-04-26 10:42:12 -03:00
. vidioc_querycap = vidioc_querycap ,
. vidioc_g_tuner = vidioc_g_tuner ,
. vidioc_s_tuner = vidioc_s_tuner ,
. vidioc_g_audio = vidioc_g_audio ,
. vidioc_s_audio = vidioc_s_audio ,
. vidioc_g_input = vidioc_g_input ,
. vidioc_s_input = vidioc_s_input ,
. vidioc_g_frequency = vidioc_g_frequency ,
. vidioc_s_frequency = vidioc_s_frequency ,
. vidioc_queryctrl = vidioc_queryctrl ,
. vidioc_g_ctrl = vidioc_g_ctrl ,
. vidioc_s_ctrl = vidioc_s_ctrl ,
2005-04-16 15:20:36 -07:00
} ;
2009-03-06 13:50:07 -03:00
static int __devinit gemtek_pci_probe ( struct pci_dev * pdev , const struct pci_device_id * pci_id )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
struct gemtek_pci * card ;
struct v4l2_device * v4l2_dev ;
int res ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
card = kzalloc ( sizeof ( struct gemtek_pci ) , GFP_KERNEL ) ;
if ( card = = NULL ) {
dev_err ( & pdev - > dev , " out of memory \n " ) ;
2005-04-16 15:20:36 -07:00
return - ENOMEM ;
}
2009-03-06 13:50:07 -03:00
v4l2_dev = & card - > v4l2_dev ;
mutex_init ( & card - > lock ) ;
card - > pdev = pdev ;
2006-04-08 16:06:16 -03:00
2009-03-06 13:50:07 -03:00
strlcpy ( v4l2_dev - > name , " gemtek_pci " , sizeof ( v4l2_dev - > name ) ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
res = v4l2_device_register ( & pdev - > dev , v4l2_dev ) ;
if ( res < 0 ) {
v4l2_err ( v4l2_dev , " Could not register v4l2_device \n " ) ;
kfree ( card ) ;
return res ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
if ( pci_enable_device ( pdev ) )
goto err_pci ;
2006-04-08 16:06:16 -03:00
2009-03-06 13:50:07 -03:00
card - > iobase = pci_resource_start ( pdev , 0 ) ;
card - > length = pci_resource_len ( pdev , 0 ) ;
if ( request_region ( card - > iobase , card - > length , card_names [ pci_id - > driver_data ] ) = = NULL ) {
v4l2_err ( v4l2_dev , " i/o port already in use \n " ) ;
goto err_pci ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
strlcpy ( card - > vdev . name , v4l2_dev - > name , sizeof ( card - > vdev . name ) ) ;
card - > vdev . v4l2_dev = v4l2_dev ;
card - > vdev . fops = & gemtek_pci_fops ;
card - > vdev . ioctl_ops = & gemtek_pci_ioctl_ops ;
card - > vdev . release = video_device_release_empty ;
video_set_drvdata ( & card - > vdev , card ) ;
if ( video_register_device ( & card - > vdev , VFL_TYPE_RADIO , nr_radio ) < 0 )
2005-04-16 15:20:36 -07:00
goto err_video ;
2009-03-06 13:50:07 -03:00
gemtek_pci_mute ( card ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
v4l2_info ( v4l2_dev , " Gemtek PCI Radio (rev. %d) found at 0x%04x-0x%04x. \n " ,
pdev - > revision , card - > iobase , card - > iobase + card - > length - 1 ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
err_video :
2009-03-06 13:50:07 -03:00
release_region ( card - > iobase , card - > length ) ;
2005-04-16 15:20:36 -07:00
err_pci :
2009-03-06 13:50:07 -03:00
v4l2_device_unregister ( v4l2_dev ) ;
kfree ( card ) ;
2006-04-08 16:06:16 -03:00
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static void __devexit gemtek_pci_remove ( struct pci_dev * pdev )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
struct v4l2_device * v4l2_dev = dev_get_drvdata ( & pdev - > dev ) ;
struct gemtek_pci * card = to_gemtek_pci ( v4l2_dev ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
video_unregister_device ( & card - > vdev ) ;
v4l2_device_unregister ( v4l2_dev ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
release_region ( card - > iobase , card - > length ) ;
2006-04-08 16:06:16 -03:00
2009-03-06 13:50:07 -03:00
if ( mx )
gemtek_pci_mute ( card ) ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:50:07 -03:00
kfree ( card ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static struct pci_driver gemtek_pci_driver = {
2005-04-16 15:20:36 -07:00
. name = " gemtek_pci " ,
. id_table = gemtek_pci_id ,
. probe = gemtek_pci_probe ,
. remove = __devexit_p ( gemtek_pci_remove ) ,
} ;
2009-03-06 13:50:07 -03:00
static int __init gemtek_pci_init ( void )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:50:07 -03:00
return pci_register_driver ( & gemtek_pci_driver ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
static void __exit gemtek_pci_exit ( void )
2005-04-16 15:20:36 -07:00
{
2006-10-04 08:09:10 -03:00
pci_unregister_driver ( & gemtek_pci_driver ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:50:07 -03:00
module_init ( gemtek_pci_init ) ;
module_exit ( gemtek_pci_exit ) ;