2005-04-17 06:20:36 +08:00
|
|
|
/* Miro PCM20 radio driver for Linux radio support
|
|
|
|
* (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
|
|
|
|
* Thanks to Norberto Pellici for the ACI device interface specification
|
|
|
|
* The API part is based on the radiotrack driver by M. Kirkwood
|
|
|
|
* This driver relies on the aci mixer (drivers/sound/aci.c)
|
|
|
|
* Look there for further info...
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Revision history:
|
|
|
|
*
|
|
|
|
* 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
|
|
|
|
* 2000-09-05 Robert Siemer <Robert.Siemer@gmx.de>
|
|
|
|
* removed unfinished volume control (maybe adding it later again)
|
|
|
|
* use OSS-mixer; added stereo control
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* What ever you think about the ACI, version 0x07 is not very well!
|
|
|
|
* I can't get frequency, 'tuner status', 'tuner flags' or mute/mono
|
2006-04-09 03:06:16 +08:00
|
|
|
* conditions... Robert
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/videodev.h>
|
2006-06-05 21:26:32 +08:00
|
|
|
#include <media/v4l2-common.h>
|
2006-05-24 05:39:29 +08:00
|
|
|
#include "oss/aci.h"
|
2005-04-17 06:20:36 +08:00
|
|
|
#include "miropcm20-rds-core.h"
|
|
|
|
|
|
|
|
static int radio_nr = -1;
|
|
|
|
module_param(radio_nr, int, 0);
|
|
|
|
|
|
|
|
struct pcm20_device {
|
|
|
|
unsigned long freq;
|
|
|
|
int muted;
|
|
|
|
int stereo;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int pcm20_mute(struct pcm20_device *dev, unsigned char mute)
|
|
|
|
{
|
|
|
|
dev->muted = mute;
|
|
|
|
return aci_write_cmd(ACI_SET_TUNERMUTE, mute);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo)
|
|
|
|
{
|
|
|
|
dev->stereo = stereo;
|
|
|
|
return aci_write_cmd(ACI_SET_TUNERMONO, !stereo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq)
|
|
|
|
{
|
|
|
|
unsigned char freql;
|
|
|
|
unsigned char freqh;
|
|
|
|
|
|
|
|
dev->freq=freq;
|
|
|
|
|
|
|
|
freq /= 160;
|
|
|
|
if (!(aci_version==0x07 || aci_version>=0xb0))
|
|
|
|
freq /= 10; /* I don't know exactly which version
|
|
|
|
* needs this hack */
|
|
|
|
freql = freq & 0xff;
|
|
|
|
freqh = freq >> 8;
|
|
|
|
|
|
|
|
aci_rds_cmd(RDS_RESET, NULL, 0);
|
|
|
|
pcm20_stereo(dev, 1);
|
|
|
|
|
|
|
|
return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal)
|
|
|
|
{
|
|
|
|
/* okay, check for signal, stereo and rds here... */
|
|
|
|
int i;
|
|
|
|
unsigned char buf;
|
|
|
|
|
|
|
|
if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0)
|
|
|
|
return i;
|
|
|
|
pr_debug("check_sig: 0x%x\n", i);
|
|
|
|
if (i & 0x80) {
|
|
|
|
/* no signal from tuner */
|
|
|
|
*flags=0;
|
|
|
|
*signal=0;
|
|
|
|
return 0;
|
|
|
|
} else
|
|
|
|
*signal=0xffff;
|
|
|
|
|
|
|
|
if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0)
|
|
|
|
return i;
|
|
|
|
if (i & 0x40) {
|
|
|
|
*flags=0;
|
|
|
|
} else {
|
|
|
|
/* stereo */
|
|
|
|
*flags=VIDEO_TUNER_STEREO_ON;
|
|
|
|
/* I can't see stereo, when forced to mono */
|
|
|
|
dev->stereo=1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0)
|
|
|
|
return i;
|
|
|
|
if (buf & 1)
|
|
|
|
/* RDS available */
|
|
|
|
*flags|=VIDEO_TUNER_RDS_ON;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0)
|
|
|
|
return i;
|
|
|
|
pr_debug("rds-signal: %d\n", buf);
|
|
|
|
if (buf > 15) {
|
|
|
|
printk("miropcm20-radio: RX strengths unexpected high...\n");
|
|
|
|
buf=15;
|
|
|
|
}
|
|
|
|
/* refine signal */
|
|
|
|
if ((*signal=SCALE(15, 0xffff, buf))==0)
|
|
|
|
*signal = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm20_do_ioctl(struct inode *inode, struct file *file,
|
|
|
|
unsigned int cmd, void *arg)
|
|
|
|
{
|
|
|
|
struct video_device *dev = video_devdata(file);
|
|
|
|
struct pcm20_device *pcm20 = dev->priv;
|
|
|
|
int i;
|
2006-04-09 03:06:16 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch(cmd)
|
|
|
|
{
|
|
|
|
case VIDIOCGCAP:
|
|
|
|
{
|
|
|
|
struct video_capability *v = arg;
|
|
|
|
memset(v,0,sizeof(*v));
|
|
|
|
v->type=VID_TYPE_TUNER;
|
|
|
|
strcpy(v->name, "Miro PCM20");
|
|
|
|
v->channels=1;
|
|
|
|
v->audios=1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
case VIDIOCGTUNER:
|
|
|
|
{
|
|
|
|
struct video_tuner *v = arg;
|
2006-04-09 03:06:16 +08:00
|
|
|
if(v->tuner) /* Only 1 tuner */
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
v->rangelow=87*16000;
|
|
|
|
v->rangehigh=108*16000;
|
|
|
|
pcm20_getflags(pcm20, &v->flags, &v->signal);
|
|
|
|
v->flags|=VIDEO_TUNER_LOW;
|
|
|
|
v->mode=VIDEO_MODE_AUTO;
|
|
|
|
strcpy(v->name, "FM");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
case VIDIOCSTUNER:
|
|
|
|
{
|
|
|
|
struct video_tuner *v = arg;
|
|
|
|
if(v->tuner!=0)
|
|
|
|
return -EINVAL;
|
|
|
|
/* Only 1 tuner so no setting needed ! */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
case VIDIOCGFREQ:
|
|
|
|
{
|
|
|
|
unsigned long *freq = arg;
|
|
|
|
*freq = pcm20->freq;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
case VIDIOCSFREQ:
|
|
|
|
{
|
|
|
|
unsigned long *freq = arg;
|
|
|
|
pcm20->freq = *freq;
|
|
|
|
i=pcm20_setfreq(pcm20, pcm20->freq);
|
|
|
|
pr_debug("First view (setfreq): 0x%x\n", i);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
case VIDIOCGAUDIO:
|
2006-04-09 03:06:16 +08:00
|
|
|
{
|
2005-04-17 06:20:36 +08:00
|
|
|
struct video_audio *v = arg;
|
|
|
|
memset(v,0, sizeof(*v));
|
|
|
|
v->flags=VIDEO_AUDIO_MUTABLE;
|
|
|
|
if (pcm20->muted)
|
|
|
|
v->flags|=VIDEO_AUDIO_MUTE;
|
|
|
|
v->mode=VIDEO_SOUND_STEREO;
|
|
|
|
if (pcm20->stereo)
|
|
|
|
v->mode|=VIDEO_SOUND_MONO;
|
|
|
|
/* v->step=2048; */
|
|
|
|
strcpy(v->name, "Radio");
|
2006-04-09 03:06:16 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
case VIDIOCSAUDIO:
|
|
|
|
{
|
|
|
|
struct video_audio *v = arg;
|
2006-04-09 03:06:16 +08:00
|
|
|
if(v->audio)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE));
|
|
|
|
if(v->flags&VIDEO_SOUND_MONO)
|
|
|
|
pcm20_stereo(pcm20, 0);
|
|
|
|
if(v->flags&VIDEO_SOUND_STEREO)
|
|
|
|
pcm20_stereo(pcm20, 1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return -ENOIOCTLCMD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm20_ioctl(struct inode *inode, struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pcm20_device pcm20_unit = {
|
|
|
|
.freq = 87*16000,
|
|
|
|
.muted = 1,
|
|
|
|
};
|
|
|
|
|
2007-02-12 16:55:33 +08:00
|
|
|
static const struct file_operations pcm20_fops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = video_exclusive_open,
|
|
|
|
.release = video_exclusive_release,
|
|
|
|
.ioctl = pcm20_ioctl,
|
2006-01-10 01:24:57 +08:00
|
|
|
.compat_ioctl = v4l_compat_ioctl32,
|
2005-04-17 06:20:36 +08:00
|
|
|
.llseek = no_llseek,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct video_device pcm20_radio = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.name = "Miro PCM 20 radio",
|
|
|
|
.type = VID_TYPE_TUNER,
|
|
|
|
.fops = &pcm20_fops,
|
|
|
|
.priv = &pcm20_unit
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init pcm20_init(void)
|
|
|
|
{
|
|
|
|
if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1)
|
|
|
|
goto video_register_device;
|
2006-04-09 03:06:16 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if(attach_aci_rds()<0)
|
|
|
|
goto attach_aci_rds;
|
|
|
|
|
|
|
|
printk(KERN_INFO "Miro PCM20 radio card driver.\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
attach_aci_rds:
|
|
|
|
video_unregister_device(&pcm20_radio);
|
|
|
|
video_register_device:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Ruurd Reitsma");
|
|
|
|
MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
static void __exit pcm20_cleanup(void)
|
|
|
|
{
|
|
|
|
unload_aci_rds();
|
|
|
|
video_unregister_device(&pcm20_radio);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(pcm20_init);
|
|
|
|
module_exit(pcm20_cleanup);
|