2012-01-16 12:00:26 +04:00
/*
* GemTek radio card driver
*
* Copyright 1998 Jonas Munsin < jmunsin @ iki . fi >
2005-04-17 02:20:36 +04:00
*
* GemTek hasn ' t released any specs on the card , so the protocol had to
* be reverse engineered with dosemu .
*
* Besides the protocol changes , this is mostly a copy of :
*
* RadioTrack II driver for Linux radio support ( C ) 1998 Ben Pfaff
2006-04-08 23:06:16 +04:00
*
2005-04-17 02:20:36 +04:00
* Based on RadioTrack I / RadioReveal ( C ) 1997 M . Kirkwood
2008-10-27 21:13:47 +03:00
* Converted to new API by Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
* Various bugfixes and enhancements by Russell Kroll < rkroll @ exploits . org >
*
2012-01-16 12:00:26 +04:00
* Converted to the radio - isa framework by Hans Verkuil < hans . verkuil @ cisco . com >
2006-08-08 16:10:01 +04:00
* Converted to V4L2 API by Mauro Carvalho Chehab < mchehab @ infradead . org >
2012-01-16 12:00:26 +04:00
*
* Note : this card seems to swap the left and right audio channels !
*
* Fully tested with the Keene USB FM Transmitter and the v4l2 - compliance tool .
2005-04-17 02:20:36 +04:00
*/
# include <linux/module.h> /* Modules */
# include <linux/init.h> /* Initdata */
2005-09-13 12:25:15 +04:00
# include <linux/ioport.h> /* request_region */
2005-04-17 02:20:36 +04:00
# include <linux/delay.h> /* udelay */
2006-08-08 16:10:01 +04:00
# include <linux/videodev2.h> /* kernel radio structs */
2009-03-06 19:50:42 +03:00
# include <linux/mutex.h>
# include <linux/io.h> /* outb, outb_p */
2012-03-22 21:53:29 +04:00
# include <linux/pnp.h>
2012-02-29 12:50:27 +04:00
# include <linux/slab.h>
2008-07-20 15:12:02 +04:00
# include <media/v4l2-ioctl.h>
2009-03-06 19:50:42 +03:00
# include <media/v4l2-device.h>
2012-01-16 12:00:26 +04:00
# include "radio-isa.h"
2005-04-17 02:20:36 +04:00
2007-10-01 07:27:55 +04:00
/*
* Module info .
*/
2011-06-25 17:15:42 +04:00
MODULE_AUTHOR ( " Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi> " ) ;
2007-10-01 07:27:55 +04:00
MODULE_DESCRIPTION ( " A driver for the GemTek Radio card. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-01-16 12:00:26 +04:00
MODULE_VERSION ( " 1.0.0 " ) ;
2007-10-01 07:27:55 +04:00
/*
* Module params .
*/
2006-08-08 16:10:01 +04:00
2005-04-17 02:20:36 +04:00
# ifndef CONFIG_RADIO_GEMTEK_PORT
# define CONFIG_RADIO_GEMTEK_PORT -1
# endif
2007-10-01 07:27:55 +04:00
# ifndef CONFIG_RADIO_GEMTEK_PROBE
# define CONFIG_RADIO_GEMTEK_PROBE 1
# endif
2005-04-17 02:20:36 +04:00
2012-01-16 12:00:26 +04:00
# define GEMTEK_MAX 4
2005-04-17 02:20:36 +04:00
2012-01-16 12:00:26 +04:00
static bool probe = CONFIG_RADIO_GEMTEK_PROBE ;
static bool hardmute ;
static int io [ GEMTEK_MAX ] = { [ 0 ] = CONFIG_RADIO_GEMTEK_PORT ,
[ 1 . . . ( GEMTEK_MAX - 1 ) ] = - 1 } ;
static int radio_nr [ GEMTEK_MAX ] = { [ 0 . . . ( GEMTEK_MAX - 1 ) ] = - 1 } ;
2007-10-01 07:27:55 +04:00
module_param ( probe , bool , 0444 ) ;
2012-01-16 12:00:26 +04:00
MODULE_PARM_DESC ( probe , " Enable automatic device probing. " ) ;
2007-10-01 07:27:55 +04:00
module_param ( hardmute , bool , 0644 ) ;
2012-01-16 12:00:26 +04:00
MODULE_PARM_DESC ( hardmute , " Enable 'hard muting' by shutting down PLL, may "
2007-10-01 07:27:55 +04:00
" reduce static noise. " ) ;
2012-01-16 12:00:26 +04:00
module_param_array ( io , int , NULL , 0444 ) ;
MODULE_PARM_DESC ( io , " Force I/O ports for the GemTek Radio card if automatic "
" probing is disabled or fails. The most common I/O ports are: 0x20c "
" 0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to "
" work for the combined sound/radiocard). " ) ;
2007-10-01 07:27:55 +04:00
2012-01-16 12:00:26 +04:00
module_param_array ( radio_nr , int , NULL , 0444 ) ;
MODULE_PARM_DESC ( radio_nr , " Radio device numbers " ) ;
2007-10-01 07:27:55 +04:00
2007-10-01 07:32:25 +04:00
/*
* Frequency calculation constants . Intermediate frequency 10.52 MHz ( nominal
* value 10.7 MHz ) , reference divisor 6.39 kHz ( nominal 6.25 kHz ) .
*/
# define FSCALE 8
# define IF_OFFSET ((unsigned int)(10.52 * 16000 * (1<<FSCALE)))
# define REF_FREQ ((unsigned int)(6.39 * 16 * (1<<FSCALE)))
2007-10-01 07:27:55 +04:00
# define GEMTEK_CK 0x01 /* Clock signal */
# define GEMTEK_DA 0x02 /* Serial data */
# define GEMTEK_CE 0x04 /* Chip enable */
# define GEMTEK_NS 0x08 /* No signal */
# define GEMTEK_MT 0x10 /* Line mute */
# define GEMTEK_STDF_3_125_KHZ 0x01 /* Standard frequency 3.125 kHz */
# define GEMTEK_PLL_OFF 0x07 /* PLL off */
# define BU2614_BUS_SIZE 32 /* BU2614 / BU2614FS bus size */
# define SHORT_DELAY 5 /* usec */
# define LONG_DELAY 75 /* usec */
2009-03-06 19:50:42 +03:00
struct gemtek {
2012-01-16 12:00:26 +04:00
struct radio_isa_card isa ;
bool muted ;
2007-10-01 07:38:30 +04:00
u32 bu2614data ;
2005-04-17 02:20:36 +04:00
} ;
2007-10-01 07:38:30 +04:00
# define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */
# define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */
# define BU2614_VOID_BITS 4 /* unused */
# define BU2614_FMES_BITS 1 /* CT, Frequency measurement beginning data */
# define BU2614_STDF_BITS 3 /* R0..R2, Standard frequency data */
# define BU2614_SWIN_BITS 1 /* S, Switch between FMIN / AMIN */
# define BU2614_SWAL_BITS 1 /* PS, Swallow counter division (AMIN only)*/
# define BU2614_VOID2_BITS 1 /* unused */
# define BU2614_FMUN_BITS 1 /* GT, Frequency measurement time & unlock */
# define BU2614_TEST_BITS 1 /* TS, Test data is input */
# define BU2614_FREQ_SHIFT 0
# define BU2614_PORT_SHIFT (BU2614_FREQ_BITS + BU2614_FREQ_SHIFT)
# define BU2614_VOID_SHIFT (BU2614_PORT_BITS + BU2614_PORT_SHIFT)
# define BU2614_FMES_SHIFT (BU2614_VOID_BITS + BU2614_VOID_SHIFT)
# define BU2614_STDF_SHIFT (BU2614_FMES_BITS + BU2614_FMES_SHIFT)
# define BU2614_SWIN_SHIFT (BU2614_STDF_BITS + BU2614_STDF_SHIFT)
# define BU2614_SWAL_SHIFT (BU2614_SWIN_BITS + BU2614_SWIN_SHIFT)
# define BU2614_VOID2_SHIFT (BU2614_SWAL_BITS + BU2614_SWAL_SHIFT)
# define BU2614_FMUN_SHIFT (BU2614_VOID2_BITS + BU2614_VOID2_SHIFT)
# define BU2614_TEST_SHIFT (BU2614_FMUN_BITS + BU2614_FMUN_SHIFT)
# define MKMASK(field) (((1<<BU2614_##field##_BITS) - 1) << \
BU2614_ # # field # # _SHIFT )
# define BU2614_PORT_MASK MKMASK(PORT)
# define BU2614_FREQ_MASK MKMASK(FREQ)
# define BU2614_VOID_MASK MKMASK(VOID)
# define BU2614_FMES_MASK MKMASK(FMES)
# define BU2614_STDF_MASK MKMASK(STDF)
# define BU2614_SWIN_MASK MKMASK(SWIN)
# define BU2614_SWAL_MASK MKMASK(SWAL)
# define BU2614_VOID2_MASK MKMASK(VOID2)
# define BU2614_FMUN_MASK MKMASK(FMUN)
# define BU2614_TEST_MASK MKMASK(TEST)
2007-10-01 07:27:55 +04:00
/*
* Set data which will be sent to BU2614FS .
2005-04-17 02:20:36 +04:00
*/
2007-10-01 07:38:30 +04:00
# define gemtek_bu2614_set(dev, field, data) ((dev)->bu2614data = \
( ( dev ) - > bu2614data & ~ field # # _MASK ) | ( ( data ) < < field # # _SHIFT ) )
2007-10-01 07:27:55 +04:00
/*
* Transmit settings to BU2614FS over GemTek IC .
*/
2009-03-06 19:50:42 +03:00
static void gemtek_bu2614_transmit ( struct gemtek * gt )
2007-10-01 07:27:55 +04:00
{
2012-01-16 12:00:26 +04:00
struct radio_isa_card * isa = & gt - > isa ;
2007-10-01 07:27:55 +04:00
int i , bit , q , mute ;
2009-03-06 19:50:42 +03:00
mute = gt - > muted ? GEMTEK_MT : 0x00 ;
2007-10-01 07:27:55 +04:00
2012-01-16 12:00:26 +04:00
outb_p ( mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK , isa - > io ) ;
2007-10-01 07:27:55 +04:00
udelay ( LONG_DELAY ) ;
2009-03-06 19:50:42 +03:00
for ( i = 0 , q = gt - > bu2614data ; i < 32 ; i + + , q > > = 1 ) {
bit = ( q & 1 ) ? GEMTEK_DA : 0 ;
2012-01-16 12:00:26 +04:00
outb_p ( mute | GEMTEK_CE | bit , isa - > io ) ;
2009-03-06 19:50:42 +03:00
udelay ( SHORT_DELAY ) ;
2012-01-16 12:00:26 +04:00
outb_p ( mute | GEMTEK_CE | bit | GEMTEK_CK , isa - > io ) ;
2009-03-06 19:50:42 +03:00
udelay ( SHORT_DELAY ) ;
2007-10-01 07:27:55 +04:00
}
2012-01-16 12:00:26 +04:00
outb_p ( mute | GEMTEK_DA | GEMTEK_CK , isa - > io ) ;
2007-10-01 07:27:55 +04:00
udelay ( SHORT_DELAY ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-01 07:27:55 +04:00
/*
2007-10-01 07:32:25 +04:00
* Calculate divisor from FM - frequency for BU2614FS ( 3.125 KHz STDF expected ) .
2007-10-01 07:27:55 +04:00
*/
2007-10-01 07:32:25 +04:00
static unsigned long gemtek_convfreq ( unsigned long freq )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:00:26 +04:00
return ( ( freq < < FSCALE ) + IF_OFFSET + REF_FREQ / 2 ) / REF_FREQ ;
}
static struct radio_isa_card * gemtek_alloc ( void )
{
struct gemtek * gt = kzalloc ( sizeof ( * gt ) , GFP_KERNEL ) ;
if ( gt )
gt - > muted = true ;
return gt ? & gt - > isa : NULL ;
2007-10-01 07:27:55 +04:00
}
/*
* Set FM - frequency .
*/
2012-01-16 12:00:26 +04:00
static int gemtek_s_frequency ( struct radio_isa_card * isa , u32 freq )
2007-10-01 07:27:55 +04:00
{
2012-01-16 12:00:26 +04:00
struct gemtek * gt = container_of ( isa , struct gemtek , isa ) ;
2007-10-01 07:27:55 +04:00
2012-01-16 12:00:26 +04:00
if ( hardmute & & gt - > muted )
return 0 ;
2007-10-01 07:27:55 +04:00
2009-03-06 19:50:42 +03:00
gemtek_bu2614_set ( gt , BU2614_PORT , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_FMES , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_SWIN , 0 ) ; /* FM-mode */
gemtek_bu2614_set ( gt , BU2614_SWAL , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_FMUN , 1 ) ; /* GT bit set */
gemtek_bu2614_set ( gt , BU2614_TEST , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_STDF , GEMTEK_STDF_3_125_KHZ ) ;
gemtek_bu2614_set ( gt , BU2614_FREQ , gemtek_convfreq ( freq ) ) ;
gemtek_bu2614_transmit ( gt ) ;
2012-01-16 12:00:26 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-10-01 07:27:55 +04:00
/*
* Set mute flag .
*/
2012-01-16 12:00:26 +04:00
static int gemtek_s_mute_volume ( struct radio_isa_card * isa , bool mute , int vol )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:00:26 +04:00
struct gemtek * gt = container_of ( isa , struct gemtek , isa ) ;
2007-10-01 07:27:55 +04:00
int i ;
2009-03-06 19:50:42 +03:00
2012-01-16 12:00:26 +04:00
gt - > muted = mute ;
2007-10-01 07:27:55 +04:00
if ( hardmute ) {
2012-01-16 12:00:26 +04:00
if ( ! mute )
return gemtek_s_frequency ( isa , isa - > freq ) ;
2007-10-01 07:27:55 +04:00
/* Turn off PLL, disable data output */
2009-03-06 19:50:42 +03:00
gemtek_bu2614_set ( gt , BU2614_PORT , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_FMES , 0 ) ; /* CT bit off */
gemtek_bu2614_set ( gt , BU2614_SWIN , 0 ) ; /* FM-mode */
gemtek_bu2614_set ( gt , BU2614_SWAL , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_FMUN , 0 ) ; /* GT bit off */
gemtek_bu2614_set ( gt , BU2614_TEST , 0 ) ;
gemtek_bu2614_set ( gt , BU2614_STDF , GEMTEK_PLL_OFF ) ;
gemtek_bu2614_set ( gt , BU2614_FREQ , 0 ) ;
gemtek_bu2614_transmit ( gt ) ;
2012-01-16 12:00:26 +04:00
return 0 ;
2009-03-06 19:50:42 +03:00
}
2007-10-01 07:27:55 +04:00
2009-03-06 19:50:42 +03:00
/* Read bus contents (CE, CK and DA). */
2012-01-16 12:00:26 +04:00
i = inb_p ( isa - > io ) ;
2009-03-06 19:50:42 +03:00
/* Write it back with mute flag set. */
2012-01-16 12:00:26 +04:00
outb_p ( ( i > > 5 ) | ( mute ? GEMTEK_MT : 0 ) , isa - > io ) ;
2009-03-06 19:50:42 +03:00
udelay ( SHORT_DELAY ) ;
2012-01-16 12:00:26 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2012-01-16 12:00:26 +04:00
static u32 gemtek_g_rxsubchans ( struct radio_isa_card * isa )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:00:26 +04:00
if ( inb_p ( isa - > io ) & GEMTEK_NS )
return V4L2_TUNER_SUB_MONO ;
return V4L2_TUNER_SUB_STEREO ;
2007-10-01 07:27:55 +04:00
}
2005-04-17 02:20:36 +04:00
2007-10-01 07:27:55 +04:00
/*
* Check if requested card acts like GemTek Radio card .
*/
2012-01-16 12:00:26 +04:00
static bool gemtek_probe ( struct radio_isa_card * isa , int io )
2007-10-01 07:27:55 +04:00
{
int i , q ;
2005-04-17 02:20:36 +04:00
2012-01-16 12:00:26 +04:00
q = inb_p ( io ) ; /* Read bus contents before probing. */
2007-10-01 07:27:55 +04:00
/* Try to turn on CE, CK and DA respectively and check if card responds
properly . */
for ( i = 0 ; i < 3 ; + + i ) {
2012-01-16 12:00:26 +04:00
outb_p ( 1 < < i , io ) ;
2007-10-01 07:27:55 +04:00
udelay ( SHORT_DELAY ) ;
2005-04-17 02:20:36 +04:00
2012-01-16 12:00:26 +04:00
if ( ( inb_p ( io ) & ~ GEMTEK_NS ) ! = ( 0x17 | ( 1 < < ( i + 5 ) ) ) )
return false ;
2007-10-01 07:27:55 +04:00
}
2012-01-16 12:00:26 +04:00
outb_p ( q > > 5 , io ) ; /* Write bus contents back. */
2007-10-01 07:27:55 +04:00
udelay ( SHORT_DELAY ) ;
2012-01-16 12:00:26 +04:00
return true ;
2005-04-17 02:20:36 +04:00
}
2012-01-16 12:00:26 +04:00
static const struct radio_isa_ops gemtek_ops = {
. alloc = gemtek_alloc ,
. probe = gemtek_probe ,
. s_mute_volume = gemtek_s_mute_volume ,
. s_frequency = gemtek_s_frequency ,
. g_rxsubchans = gemtek_g_rxsubchans ,
2007-10-01 07:27:55 +04:00
} ;
2012-01-16 12:00:26 +04:00
static const int gemtek_ioports [ ] = { 0x20c , 0x30c , 0x24c , 0x34c , 0x248 , 0x28c } ;
2012-03-22 21:53:29 +04:00
# ifdef CONFIG_PNP
static struct pnp_device_id gemtek_pnp_devices [ ] = {
/* AOpen FX-3D/Pro Radio */
{ . id = " ADS7183 " , . driver_data = 0 } ,
{ . id = " " }
} ;
MODULE_DEVICE_TABLE ( pnp , gemtek_pnp_devices ) ;
# endif
2012-01-16 12:00:26 +04:00
static struct radio_isa_driver gemtek_driver = {
. driver = {
. match = radio_isa_match ,
. probe = radio_isa_probe ,
. remove = radio_isa_remove ,
. driver = {
. name = " radio-gemtek " ,
} ,
} ,
2012-03-22 21:53:29 +04:00
# ifdef CONFIG_PNP
. pnp_driver = {
. name = " radio-gemtek " ,
. id_table = gemtek_pnp_devices ,
. probe = radio_isa_pnp_probe ,
. remove = radio_isa_pnp_remove ,
} ,
# endif
2012-01-16 12:00:26 +04:00
. io_params = io ,
. radio_nr_params = radio_nr ,
. io_ports = gemtek_ioports ,
. num_of_io_ports = ARRAY_SIZE ( gemtek_ioports ) ,
. region_size = 1 ,
. card = " GemTek Radio " ,
. ops = & gemtek_ops ,
. has_stereo = true ,
2005-04-17 02:20:36 +04:00
} ;
static int __init gemtek_init ( void )
{
2012-01-16 12:00:26 +04:00
gemtek_driver . probe = probe ;
2012-03-22 21:53:29 +04:00
# ifdef CONFIG_PNP
pnp_register_driver ( & gemtek_driver . pnp_driver ) ;
# endif
2012-01-16 12:00:26 +04:00
return isa_register_driver ( & gemtek_driver . driver , GEMTEK_MAX ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-01 07:27:55 +04:00
static void __exit gemtek_exit ( void )
2005-04-17 02:20:36 +04:00
{
2014-09-03 22:54:17 +04:00
hardmute = true ; /* Turn off PLL */
2012-03-22 21:53:29 +04:00
# ifdef CONFIG_PNP
pnp_unregister_driver ( & gemtek_driver . pnp_driver ) ;
# endif
2012-01-16 12:00:26 +04:00
isa_unregister_driver ( & gemtek_driver . driver ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( gemtek_init ) ;
2007-10-01 07:27:55 +04:00
module_exit ( gemtek_exit ) ;