| /* 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 |
| * conditions... Robert |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/videodev.h> |
| #include <media/v4l2-common.h> |
| #include <media/v4l2-ioctl.h> |
| #include "oss/aci.h" |
| #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; |
| |
| 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; |
| if(v->tuner) /* Only 1 tuner */ |
| 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: |
| { |
| 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"); |
| return 0; |
| } |
| case VIDIOCSAUDIO: |
| { |
| struct video_audio *v = arg; |
| if(v->audio) |
| 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, |
| }; |
| |
| static const struct file_operations pcm20_fops = { |
| .owner = THIS_MODULE, |
| .open = video_exclusive_open, |
| .release = video_exclusive_release, |
| .ioctl = pcm20_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = v4l_compat_ioctl32, |
| #endif |
| .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; |
| |
| 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); |