2005-04-17 02:20:36 +04:00
/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
* ( c ) 1999 R . Offermanns ( rolf @ offermanns . de )
* based on the aimslab radio driver from M . Kirkwood
* many thanks to Michael Becker and Friedhelm Birth ( from TerraTec )
2006-04-08 23:06:16 +04:00
*
2005-04-17 02:20:36 +04:00
*
* History :
* 1999 - 05 - 21 First preview release
2006-04-08 23:06:16 +04:00
*
2005-04-17 02:20:36 +04:00
* Notes on the hardware :
* There are two " main " chips on the card :
* - Philips OM5610 ( http : //www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
* - Philips SAA6588 ( http : //www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
* ( you can get the datasheet at the above links )
*
* Frequency control is done digitally - - ie out ( port , encodefreq ( 95.8 ) ) ;
* Volume Control is done digitally
*
* there is a I2C controlled RDS decoder ( SAA6588 ) onboard , which i would like to support someday
* ( as soon i have understand how to get started : )
* If you can help me out with that , please contact me ! !
*
2006-04-08 23:06:16 +04:00
*
2006-08-08 16:10:03 +04:00
* Converted to V4L2 API by Mauro Carvalho Chehab < mchehab @ infradead . org >
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 */
2006-08-08 16:10:03 +04:00
# include <linux/videodev2.h> /* kernel radio structs */
2009-03-06 19:53:58 +03:00
# include <linux/mutex.h>
# include <linux/version.h> /* for KERNEL_VERSION MACRO */
# include <linux/io.h> /* outb, outb_p */
# include <media/v4l2-device.h>
2008-07-20 15:12:02 +04:00
# include <media/v4l2-ioctl.h>
2005-04-17 02:20:36 +04:00
2009-03-06 19:53:58 +03:00
MODULE_AUTHOR ( " R.OFFERMANNS & others " ) ;
MODULE_DESCRIPTION ( " A driver for the TerraTec ActiveRadio Standalone radio card. " ) ;
MODULE_LICENSE ( " GPL " ) ;
# ifndef CONFIG_RADIO_TERRATEC_PORT
# define CONFIG_RADIO_TERRATEC_PORT 0x590
# endif
static int io = CONFIG_RADIO_TERRATEC_PORT ;
static int radio_nr = - 1 ;
module_param ( io , int , 0 ) ;
MODULE_PARM_DESC ( io , " I/O address of the TerraTec ActiveRadio card (0x590 or 0x591) " ) ;
module_param ( radio_nr , int , 0 ) ;
# define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
2006-08-08 16:10:03 +04:00
static struct v4l2_queryctrl radio_qctrl [ ] = {
{
. id = V4L2_CID_AUDIO_MUTE ,
. name = " Mute " ,
. minimum = 0 ,
. maximum = 1 ,
. default_value = 1 ,
. type = V4L2_CTRL_TYPE_BOOLEAN ,
} , {
. id = V4L2_CID_AUDIO_VOLUME ,
. name = " Volume " ,
. minimum = 0 ,
. maximum = 0xff ,
. step = 1 ,
. default_value = 0xff ,
. type = V4L2_CTRL_TYPE_INTEGER ,
}
} ;
2005-04-17 02:20:36 +04:00
# define WRT_DIS 0x00
# define CLK_OFF 0x00
# define IIC_DATA 0x01
# define IIC_CLK 0x02
# define DATA 0x04
# define CLK_ON 0x08
# define WRT_EN 0x10
2009-03-06 19:53:58 +03:00
struct terratec
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
struct v4l2_device v4l2_dev ;
struct video_device vdev ;
int io ;
2005-04-17 02:20:36 +04:00
int curvol ;
unsigned long curfreq ;
int muted ;
2009-03-06 19:53:58 +03:00
struct mutex lock ;
2005-04-17 02:20:36 +04:00
} ;
2009-03-06 19:53:58 +03:00
static struct terratec terratec_card ;
2005-04-17 02:20:36 +04:00
/* local things */
2009-03-06 19:53:58 +03:00
static void tt_write_vol ( struct terratec * tt , int volume )
2005-04-17 02:20:36 +04:00
{
int i ;
2009-03-06 19:53:58 +03:00
volume = volume + ( volume * 32 ) ; /* change both channels */
mutex_lock ( & tt - > lock ) ;
for ( i = 0 ; i < 8 ; i + + ) {
if ( volume & ( 0x80 > > i ) )
outb ( 0x80 , tt - > io + 1 ) ;
else
outb ( 0x00 , tt - > io + 1 ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:53:58 +03:00
mutex_unlock ( & tt - > lock ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:53:58 +03:00
static void tt_mute ( struct terratec * tt )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
tt - > muted = 1 ;
tt_write_vol ( tt , 0 ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:53:58 +03:00
static int tt_setvol ( struct terratec * tt , int vol )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
if ( vol = = tt - > curvol ) { /* requested volume = current */
if ( tt - > muted ) { /* user is unmuting the card */
tt - > muted = 0 ;
tt_write_vol ( tt , vol ) ; /* enable card */
2006-04-08 23:06:16 +04:00
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-03-06 19:53:58 +03:00
if ( vol = = 0 ) { /* volume = 0 means mute the card */
tt_write_vol ( tt , 0 ) ; /* "turn off card" by setting vol to 0 */
tt - > curvol = vol ; /* track the volume state! */
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-03-06 19:53:58 +03:00
tt - > muted = 0 ;
tt_write_vol ( tt , vol ) ;
tt - > curvol = vol ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/* this is the worst part in this driver */
/* many more or less strange things are going on here, but hey, it works :) */
2009-03-06 19:53:58 +03:00
static int tt_setfreq ( struct terratec * tt , unsigned long freq1 )
2006-04-08 23:06:16 +04:00
{
2005-04-17 02:20:36 +04:00
int freq ;
int i ;
int p ;
int temp ;
long rest ;
unsigned char buffer [ 25 ] ; /* we have to bit shift 25 registers */
2009-03-06 19:53:58 +03:00
mutex_lock ( & tt - > lock ) ;
tt - > curfreq = freq1 ;
freq = freq1 / 160 ; /* convert the freq. to a nice to handle value */
memset ( buffer , 0 , sizeof ( buffer ) ) ;
rest = freq * 10 + 10700 ; /* I once had understood what is going on here */
2005-04-17 02:20:36 +04:00
/* maybe some wise guy (friedhelm?) can comment this stuff */
2009-03-06 19:53:58 +03:00
i = 13 ;
p = 10 ;
temp = 102400 ;
while ( rest ! = 0 ) {
if ( rest % temp = = rest )
2005-04-17 02:20:36 +04:00
buffer [ i ] = 0 ;
2009-03-06 19:53:58 +03:00
else {
2006-04-08 23:06:16 +04:00
buffer [ i ] = 1 ;
2009-03-06 19:53:58 +03:00
rest = rest - temp ;
2005-04-17 02:20:36 +04:00
}
i - - ;
p - - ;
2009-03-06 19:53:58 +03:00
temp = temp / 2 ;
2007-08-28 01:16:54 +04:00
}
2005-04-17 02:20:36 +04:00
2009-03-06 19:53:58 +03:00
for ( i = 24 ; i > - 1 ; i - - ) { /* bit shift the values to the radiocard */
if ( buffer [ i ] = = 1 ) {
outb ( WRT_EN | DATA , tt - > io ) ;
outb ( WRT_EN | DATA | CLK_ON , tt - > io ) ;
outb ( WRT_EN | DATA , tt - > io ) ;
} else {
outb ( WRT_EN | 0x00 , tt - > io ) ;
outb ( WRT_EN | 0x00 | CLK_ON , tt - > io ) ;
2005-04-17 02:20:36 +04:00
}
}
2009-03-06 19:53:58 +03:00
outb ( 0x00 , tt - > io ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:53:58 +03:00
mutex_unlock ( & tt - > lock ) ;
2006-04-08 23:06:16 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:53:58 +03:00
static int tt_getsigstr ( struct terratec * tt )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
if ( inb ( tt - > io ) & 2 ) /* bit set = no signal present */
2005-04-17 02:20:36 +04:00
return 0 ;
return 1 ; /* signal present */
}
2007-04-23 06:15:47 +04:00
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
strlcpy ( v - > driver , " radio-terratec " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " ActiveRadio " , sizeof ( v - > card ) ) ;
2009-03-06 19:53:58 +03:00
strlcpy ( v - > bus_info , " ISA " , sizeof ( v - > bus_info ) ) ;
2007-04-23 06:15:47 +04:00
v - > version = RADIO_VERSION ;
2009-03-06 19:53:58 +03:00
v - > capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO ;
2007-04-23 06:15:47 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = video_drvdata ( file ) ;
2006-04-08 23:06:16 +04:00
2007-04-23 06:15:47 +04:00
if ( v - > index > 0 )
return - EINVAL ;
2006-08-08 16:10:03 +04:00
2009-03-06 19:53:58 +03:00
strlcpy ( v - > name , " FM " , sizeof ( v - > name ) ) ;
2007-04-23 06:15:47 +04:00
v - > type = V4L2_TUNER_RADIO ;
2009-03-06 19:53:58 +03:00
v - > rangelow = 87 * 16000 ;
v - > rangehigh = 108 * 16000 ;
2007-04-23 06:15:47 +04:00
v - > rxsubchans = V4L2_TUNER_SUB_MONO ;
v - > capability = V4L2_TUNER_CAP_LOW ;
v - > audmode = V4L2_TUNER_MODE_MONO ;
2009-03-06 19:53:58 +03:00
v - > signal = 0xFFFF * tt_getsigstr ( tt ) ;
2007-04-23 06:15:47 +04:00
return 0 ;
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_s_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2009-03-06 19:53:58 +03:00
return v - > index ? - EINVAL : 0 ;
2007-04-23 06:15:47 +04:00
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_s_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = video_drvdata ( file ) ;
2006-08-08 16:10:03 +04:00
2009-03-06 19:53:58 +03:00
tt_setfreq ( tt , f - > frequency ) ;
2007-04-23 06:15:47 +04:00
return 0 ;
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = video_drvdata ( file ) ;
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
f - > type = V4L2_TUNER_RADIO ;
f - > frequency = tt - > curfreq ;
return 0 ;
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_queryctrl ( struct file * file , void * priv ,
struct v4l2_queryctrl * qc )
{
int i ;
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( radio_qctrl ) ; i + + ) {
if ( qc - > id & & qc - > id = = radio_qctrl [ i ] . id ) {
2009-03-06 19:53:58 +03:00
memcpy ( qc , & ( radio_qctrl [ i ] ) , sizeof ( * qc ) ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-04-23 06:15:47 +04:00
}
return - EINVAL ;
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_g_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = video_drvdata ( file ) ;
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
if ( tt - > muted )
ctrl - > value = 1 ;
else
ctrl - > value = 0 ;
return 0 ;
case V4L2_CID_AUDIO_VOLUME :
ctrl - > value = tt - > curvol * 6554 ;
return 0 ;
}
return - EINVAL ;
}
2006-08-08 16:10:03 +04:00
2007-04-23 06:15:47 +04:00
static int vidioc_s_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = video_drvdata ( file ) ;
2007-04-23 06:15:47 +04:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
if ( ctrl - > value )
tt_mute ( tt ) ;
else
tt_setvol ( tt , tt - > curvol ) ;
return 0 ;
case V4L2_CID_AUDIO_VOLUME :
tt_setvol ( tt , ctrl - > value ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-04-23 06:15:47 +04:00
return - EINVAL ;
}
static int vidioc_g_input ( struct file * filp , void * priv , unsigned int * i )
{
* i = 0 ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-04-23 06:15:47 +04:00
static int vidioc_s_input ( struct file * filp , void * priv , unsigned int i )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
return i ? - EINVAL : 0 ;
2007-04-23 06:15:47 +04:00
}
2009-03-06 19:53:58 +03:00
static int vidioc_g_audio ( struct file * file , void * priv ,
2007-04-23 06:15:47 +04:00
struct v4l2_audio * a )
{
2009-03-06 19:53:58 +03:00
a - > index = 0 ;
strlcpy ( a - > name , " Radio " , sizeof ( a - > name ) ) ;
a - > capability = V4L2_AUDCAP_STEREO ;
2007-04-23 06:15:47 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:53:58 +03:00
static int vidioc_s_audio ( struct file * file , void * priv ,
struct v4l2_audio * a )
{
return a - > index ? - EINVAL : 0 ;
}
2005-04-17 02:20:36 +04:00
2008-12-30 12:58:20 +03:00
static const struct v4l2_file_operations terratec_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
2007-04-23 06:15:47 +04:00
. ioctl = video_ioctl2 ,
2005-04-17 02:20:36 +04:00
} ;
2008-07-21 09:57:38 +04:00
static const struct v4l2_ioctl_ops terratec_ioctl_ops = {
2007-04-23 06:15:47 +04:00
. 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_queryctrl = vidioc_queryctrl ,
. vidioc_g_ctrl = vidioc_g_ctrl ,
. vidioc_s_ctrl = vidioc_s_ctrl ,
. vidioc_g_audio = vidioc_g_audio ,
. vidioc_s_audio = vidioc_s_audio ,
. vidioc_g_input = vidioc_g_input ,
. vidioc_s_input = vidioc_s_input ,
2005-04-17 02:20:36 +04:00
} ;
static int __init terratec_init ( void )
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = & terratec_card ;
struct v4l2_device * v4l2_dev = & tt - > v4l2_dev ;
int res ;
strlcpy ( v4l2_dev - > name , " terratec " , sizeof ( v4l2_dev - > name ) ) ;
tt - > io = io ;
if ( tt - > io = = - 1 ) {
2009-03-09 14:11:21 +03:00
v4l2_err ( v4l2_dev , " you must set an I/O address with io=0x590 or 0x591 \n " ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2009-03-06 19:53:58 +03:00
if ( ! request_region ( tt - > io , 2 , " terratec " ) ) {
v4l2_err ( v4l2_dev , " port 0x%x already in use \n " , io ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
2009-03-06 19:53:58 +03:00
res = v4l2_device_register ( NULL , v4l2_dev ) ;
if ( res < 0 ) {
release_region ( tt - > io , 2 ) ;
v4l2_err ( v4l2_dev , " Could not register v4l2_device \n " ) ;
return res ;
}
strlcpy ( tt - > vdev . name , v4l2_dev - > name , sizeof ( tt - > vdev . name ) ) ;
tt - > vdev . v4l2_dev = v4l2_dev ;
tt - > vdev . fops = & terratec_fops ;
tt - > vdev . ioctl_ops = & terratec_ioctl_ops ;
tt - > vdev . release = video_device_release_empty ;
video_set_drvdata ( & tt - > vdev , tt ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:53:58 +03:00
mutex_init ( & tt - > lock ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:53:58 +03:00
if ( video_register_device ( & tt - > vdev , VFL_TYPE_RADIO , radio_nr ) < 0 ) {
v4l2_device_unregister ( & tt - > v4l2_dev ) ;
release_region ( tt - > io , 2 ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2006-04-08 23:06:16 +04:00
2009-03-06 19:53:58 +03:00
v4l2_info ( v4l2_dev , " TERRATEC ActivRadio Standalone card driver. \n " ) ;
2005-04-17 02:20:36 +04:00
2006-04-08 23:06:16 +04:00
/* mute card - prevents noisy bootups */
2009-03-06 19:53:58 +03:00
tt_write_vol ( tt , 0 ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-03-06 19:53:58 +03:00
static void __exit terratec_exit ( void )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:53:58 +03:00
struct terratec * tt = & terratec_card ;
struct v4l2_device * v4l2_dev = & tt - > v4l2_dev ;
video_unregister_device ( & tt - > vdev ) ;
v4l2_device_unregister ( & tt - > v4l2_dev ) ;
release_region ( tt - > io , 2 ) ;
v4l2_info ( v4l2_dev , " TERRATEC ActivRadio Standalone card driver unloaded. \n " ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( terratec_init ) ;
2009-03-06 19:53:58 +03:00
module_exit ( terratec_exit ) ;
2005-04-17 02:20:36 +04:00