2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2012-01-16 12:18:40 +04:00
/*
* Zoltrix Radio Plus driver
* Copyright 1998 C . van Schaik < carl @ leg . uct . ac . za >
2005-04-17 02:20:36 +04:00
*
2006-04-08 23:06:16 +04:00
* BUGS
2005-04-17 02:20:36 +04:00
* Due to the inconsistency in reading from the signal flags
* it is difficult to get an accurate tuned signal .
*
* It seems that the card is not linear to 0 volume . It cuts off
* at a low volume , and it is not possible ( at least I have not found )
* to get fine volume control over the low volume range .
*
* Some code derived from code by Romolo Manfredini
* romolo @ bicnet . it
*
* 1999 - 05 - 06 - ( C . van Schaik )
* - Make signal strength and stereo scans
2006-04-08 23:06:16 +04:00
* kinder to cpu while in delay
2005-04-17 02:20:36 +04:00
* 1999 - 01 - 05 - ( C . van Schaik )
* - Changed tuning to 1 / 160 Mhz accuracy
* - Added stereo support
* ( card defaults to stereo )
* ( can explicitly force mono on the card )
* ( can detect if station is in stereo )
* - Added unmute function
* - Reworked ioctl functions
* 2002 - 07 - 15 - Fix Stereo typo
2006-08-08 16:10:04 +04:00
*
* 2006 - 07 - 24 - Converted to V4L2 API
MAINTAINERS & files: Canonize the e-mails I use at files
From now on, I'll start using my @kernel.org as my development e-mail.
As such, let's remove the entries that point to the old
mchehab@s-opensource.com at MAINTAINERS file.
For the files written with a copyright with mchehab@s-opensource,
let's keep Samsung on their names, using mchehab+samsung@kernel.org,
in order to keep pointing to my employer, with sponsors the work.
For the files written before I join Samsung (on July, 4 2013),
let's just use mchehab@kernel.org.
For bug reports, we can simply point to just kernel.org, as
this will reach my mchehab+samsung inbox anyway.
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Signed-off-by: Brian Warner <brian.warner@samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
2018-04-25 12:34:48 +03:00
* by Mauro Carvalho Chehab < mchehab @ kernel . org >
2012-01-16 12:18:40 +04:00
*
* Converted to the radio - isa framework by Hans Verkuil < hans . verkuil @ cisco . com >
*
* Note that this is the driver for the Zoltrix Radio Plus .
* This driver does not work for the Zoltrix Radio Plus 108 or the
* Zoltrix Radio Plus for Windows .
*
* 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, msleep */
2006-08-08 16:10:04 +04:00
# include <linux/videodev2.h> /* kernel radio structs */
2009-03-06 19:55:34 +03:00
# include <linux/mutex.h>
# include <linux/io.h> /* outb, outb_p */
2012-02-29 12:50:27 +04:00
# include <linux/slab.h>
2009-03-06 19:55:34 +03:00
# include <media/v4l2-device.h>
2008-07-20 15:12:02 +04:00
# include <media/v4l2-ioctl.h>
2012-01-16 12:18:40 +04:00
# include "radio-isa.h"
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
MODULE_AUTHOR ( " C. van Schaik " ) ;
2009-03-06 19:55:34 +03:00
MODULE_DESCRIPTION ( " A driver for the Zoltrix Radio Plus. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-01-16 12:18:40 +04:00
MODULE_VERSION ( " 0.1.99 " ) ;
2006-08-08 16:10:04 +04:00
2005-04-17 02:20:36 +04:00
# ifndef CONFIG_RADIO_ZOLTRIX_PORT
# define CONFIG_RADIO_ZOLTRIX_PORT -1
# endif
2012-01-16 12:18:40 +04:00
# define ZOLTRIX_MAX 2
static int io [ ZOLTRIX_MAX ] = { [ 0 ] = CONFIG_RADIO_ZOLTRIX_PORT ,
[ 1 . . . ( ZOLTRIX_MAX - 1 ) ] = - 1 } ;
static int radio_nr [ ZOLTRIX_MAX ] = { [ 0 . . . ( ZOLTRIX_MAX - 1 ) ] = - 1 } ;
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
module_param_array ( io , int , NULL , 0444 ) ;
MODULE_PARM_DESC ( io , " I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c) " ) ;
module_param_array ( radio_nr , int , NULL , 0444 ) ;
MODULE_PARM_DESC ( radio_nr , " Radio device numbers " ) ;
2009-03-06 19:55:34 +03:00
struct zoltrix {
2012-01-16 12:18:40 +04:00
struct radio_isa_card isa ;
2005-04-17 02:20:36 +04:00
int curvol ;
2012-01-16 12:18:40 +04:00
bool muted ;
2005-04-17 02:20:36 +04:00
} ;
2012-01-16 12:18:40 +04:00
static struct radio_isa_card * zoltrix_alloc ( void )
{
struct zoltrix * zol = kzalloc ( sizeof ( * zol ) , GFP_KERNEL ) ;
return zol ? & zol - > isa : NULL ;
}
2009-03-06 19:55:34 +03:00
2012-01-16 12:18:40 +04:00
static int zoltrix_s_mute_volume ( struct radio_isa_card * isa , bool mute , int vol )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:18:40 +04:00
struct zoltrix * zol = container_of ( isa , struct zoltrix , isa ) ;
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
zol - > curvol = vol ;
zol - > muted = mute ;
if ( mute | | vol = = 0 ) {
outb ( 0 , isa - > io ) ;
outb ( 0 , isa - > io ) ;
inb ( isa - > io + 3 ) ; /* Zoltrix needs to be read to confirm */
2005-04-17 02:20:36 +04:00
return 0 ;
}
2012-01-16 12:18:40 +04:00
outb ( vol - 1 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 10 ) ;
2012-01-16 12:18:40 +04:00
inb ( isa - > io + 2 ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2012-01-16 12:18:40 +04:00
/* tunes the radio to the desired frequency */
static int zoltrix_s_frequency ( struct radio_isa_card * isa , u32 freq )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:18:40 +04:00
struct zoltrix * zol = container_of ( isa , struct zoltrix , isa ) ;
struct v4l2_device * v4l2_dev = & isa - > v4l2_dev ;
2005-04-17 02:20:36 +04:00
unsigned long long bitmask , f , m ;
2012-01-16 12:18:40 +04:00
bool stereo = isa - > stereo ;
2005-04-17 02:20:36 +04:00
int i ;
2008-10-09 20:46:59 +04:00
if ( freq = = 0 ) {
2009-03-06 19:55:34 +03:00
v4l2_warn ( v4l2_dev , " cannot set a frequency of 0. \n " ) ;
2008-10-09 20:46:59 +04:00
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
m = ( freq / 160 - 8800 ) * 2 ;
2009-03-06 19:55:34 +03:00
f = ( unsigned long long ) m + 0x4d1c ;
2005-04-17 02:20:36 +04:00
bitmask = 0xc480402c10080000ull ;
i = 45 ;
2012-01-16 12:18:40 +04:00
outb ( 0 , isa - > io ) ;
outb ( 0 , isa - > io ) ;
inb ( isa - > io + 3 ) ; /* Zoltrix needs to be read to confirm */
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
outb ( 0x40 , isa - > io ) ;
outb ( 0xc0 , isa - > io ) ;
2009-03-06 19:55:34 +03:00
bitmask = ( bitmask ^ ( ( f & 0xff ) < < 47 ) ^ ( ( f & 0xff00 ) < < 30 ) ^ ( stereo < < 31 ) ) ;
2005-04-17 02:20:36 +04:00
while ( i - - ) {
if ( ( bitmask & 0x8000000000000000ull ) ! = 0 ) {
2012-01-16 12:18:40 +04:00
outb ( 0x80 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
2012-01-16 12:18:40 +04:00
outb ( 0x00 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
2012-01-16 12:18:40 +04:00
outb ( 0x80 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
} else {
2012-01-16 12:18:40 +04:00
outb ( 0xc0 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
2012-01-16 12:18:40 +04:00
outb ( 0x40 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
2012-01-16 12:18:40 +04:00
outb ( 0xc0 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 50 ) ;
}
bitmask * = 2 ;
}
/* termination sequence */
2012-01-16 12:18:40 +04:00
outb ( 0x80 , isa - > io ) ;
outb ( 0xc0 , isa - > io ) ;
outb ( 0x40 , isa - > io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 1000 ) ;
2012-01-16 12:18:40 +04:00
inb ( isa - > io + 2 ) ;
2006-04-08 23:06:16 +04:00
udelay ( 1000 ) ;
2012-01-16 12:18:40 +04:00
return zoltrix_s_mute_volume ( isa , zol - > muted , zol - > curvol ) ;
2005-04-17 02:20:36 +04:00
}
/* Get signal strength */
2012-01-16 12:18:40 +04:00
static u32 zoltrix_g_rxsubchans ( struct radio_isa_card * isa )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:18:40 +04:00
struct zoltrix * zol = container_of ( isa , struct zoltrix , isa ) ;
2005-04-17 02:20:36 +04:00
int a , b ;
2012-01-16 12:18:40 +04:00
outb ( 0x00 , isa - > io ) ; /* This stuff I found to do nothing */
outb ( zol - > curvol , isa - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 20 ) ;
2012-01-16 12:18:40 +04:00
a = inb ( isa - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 10 ) ;
2012-01-16 12:18:40 +04:00
b = inb ( isa - > io ) ;
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
return ( a = = b & & a = = 0xcf ) ?
V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO ;
2005-04-17 02:20:36 +04:00
}
2012-01-16 12:18:40 +04:00
static u32 zoltrix_g_signal ( struct radio_isa_card * isa )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:18:40 +04:00
struct zoltrix * zol = container_of ( isa , struct zoltrix , isa ) ;
int a , b ;
2006-04-08 23:06:16 +04:00
2012-01-16 12:18:40 +04:00
outb ( 0x00 , isa - > io ) ; /* This stuff I found to do nothing */
outb ( zol - > curvol , isa - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 20 ) ;
2012-01-16 12:18:40 +04:00
a = inb ( isa - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 10 ) ;
2012-01-16 12:18:40 +04:00
b = inb ( isa - > io ) ;
2007-04-21 01:23:38 +04:00
2012-01-16 12:18:40 +04:00
if ( a ! = b )
2007-04-21 01:23:38 +04:00
return 0 ;
2012-01-16 12:18:40 +04:00
/* I found this out by playing with a binary scanner on the card io */
return ( a = = 0xcf | | a = = 0xdf | | a = = 0xef ) ? 0xffff : 0 ;
2005-04-17 02:20:36 +04:00
}
2012-01-16 12:18:40 +04:00
static int zoltrix_s_stereo ( struct radio_isa_card * isa , bool stereo )
2009-03-06 19:55:34 +03:00
{
2012-01-16 12:18:40 +04:00
return zoltrix_s_frequency ( isa , isa - > freq ) ;
2009-03-06 19:55:34 +03:00
}
2005-04-17 02:20:36 +04:00
2012-01-16 12:18:40 +04:00
static const struct radio_isa_ops zoltrix_ops = {
. alloc = zoltrix_alloc ,
. s_mute_volume = zoltrix_s_mute_volume ,
. s_frequency = zoltrix_s_frequency ,
. s_stereo = zoltrix_s_stereo ,
. g_rxsubchans = zoltrix_g_rxsubchans ,
. g_signal = zoltrix_g_signal ,
2005-04-17 02:20:36 +04:00
} ;
2012-01-16 12:18:40 +04:00
static const int zoltrix_ioports [ ] = { 0x20c , 0x30c } ;
static struct radio_isa_driver zoltrix_driver = {
. driver = {
. match = radio_isa_match ,
. probe = radio_isa_probe ,
. remove = radio_isa_remove ,
. driver = {
. name = " radio-zoltrix " ,
} ,
} ,
. io_params = io ,
. radio_nr_params = radio_nr ,
. io_ports = zoltrix_ioports ,
. num_of_io_ports = ARRAY_SIZE ( zoltrix_ioports ) ,
. region_size = 2 ,
. card = " Zoltrix Radio Plus " ,
. ops = & zoltrix_ops ,
. has_stereo = true ,
. max_volume = 15 ,
2005-04-17 02:20:36 +04:00
} ;
static int __init zoltrix_init ( void )
{
2012-01-16 12:18:40 +04:00
return isa_register_driver ( & zoltrix_driver . driver , ZOLTRIX_MAX ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:55:34 +03:00
static void __exit zoltrix_exit ( void )
2005-04-17 02:20:36 +04:00
{
2012-01-16 12:18:40 +04:00
isa_unregister_driver ( & zoltrix_driver . driver ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( zoltrix_init ) ;
2009-03-06 19:55:34 +03:00
module_exit ( zoltrix_exit ) ;
2005-04-17 02:20:36 +04:00