| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* Miro PCM20 radio driver for Linux radio support | 
 | 2 |  * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | 
 | 3 |  * Thanks to Norberto Pellici for the ACI device interface specification | 
 | 4 |  * The API part is based on the radiotrack driver by M. Kirkwood | 
 | 5 |  * This driver relies on the aci mixer (drivers/sound/aci.c) | 
 | 6 |  * Look there for further info... | 
 | 7 |  */ | 
 | 8 |  | 
 | 9 | /* Revision history: | 
 | 10 |  * | 
 | 11 |  *   1998        Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | 
 | 12 |  *   2000-09-05  Robert Siemer <Robert.Siemer@gmx.de> | 
 | 13 |  *        removed unfinished volume control (maybe adding it later again) | 
 | 14 |  *        use OSS-mixer; added stereo control | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | /* What ever you think about the ACI, version 0x07 is not very well! | 
 | 18 |  * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 19 |  * conditions...                Robert | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 20 |  */ | 
 | 21 |  | 
 | 22 | #include <linux/module.h> | 
 | 23 | #include <linux/init.h> | 
 | 24 | #include <linux/videodev.h> | 
| Mauro Carvalho Chehab | 5e87efa | 2006-06-05 10:26:32 -0300 | [diff] [blame] | 25 | #include <media/v4l2-common.h> | 
| Michael Krufky | 2938d78 | 2006-05-23 18:39:29 -0300 | [diff] [blame] | 26 | #include "oss/aci.h" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 27 | #include "miropcm20-rds-core.h" | 
 | 28 |  | 
 | 29 | static int radio_nr = -1; | 
 | 30 | module_param(radio_nr, int, 0); | 
 | 31 |  | 
 | 32 | struct pcm20_device { | 
 | 33 | 	unsigned long freq; | 
 | 34 | 	int muted; | 
 | 35 | 	int stereo; | 
 | 36 | }; | 
 | 37 |  | 
 | 38 |  | 
 | 39 | static int pcm20_mute(struct pcm20_device *dev, unsigned char mute) | 
 | 40 | { | 
 | 41 | 	dev->muted = mute; | 
 | 42 | 	return aci_write_cmd(ACI_SET_TUNERMUTE, mute); | 
 | 43 | } | 
 | 44 |  | 
 | 45 | static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo) | 
 | 46 | { | 
 | 47 | 	dev->stereo = stereo; | 
 | 48 | 	return aci_write_cmd(ACI_SET_TUNERMONO, !stereo); | 
 | 49 | } | 
 | 50 |  | 
 | 51 | static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq) | 
 | 52 | { | 
 | 53 | 	unsigned char freql; | 
 | 54 | 	unsigned char freqh; | 
 | 55 |  | 
 | 56 | 	dev->freq=freq; | 
 | 57 |  | 
 | 58 | 	freq /= 160; | 
 | 59 | 	if (!(aci_version==0x07 || aci_version>=0xb0)) | 
 | 60 | 		freq /= 10;  /* I don't know exactly which version | 
 | 61 | 			      * needs this hack */ | 
 | 62 | 	freql = freq & 0xff; | 
 | 63 | 	freqh = freq >> 8; | 
 | 64 |  | 
 | 65 | 	aci_rds_cmd(RDS_RESET, NULL, 0); | 
 | 66 | 	pcm20_stereo(dev, 1); | 
 | 67 |  | 
 | 68 | 	return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh); | 
 | 69 | } | 
 | 70 |  | 
 | 71 | static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal) | 
 | 72 | { | 
 | 73 | 	/* okay, check for signal, stereo and rds here... */ | 
 | 74 | 	int i; | 
 | 75 | 	unsigned char buf; | 
 | 76 |  | 
 | 77 | 	if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0) | 
 | 78 | 		return i; | 
 | 79 | 	pr_debug("check_sig: 0x%x\n", i); | 
 | 80 | 	if (i & 0x80) { | 
 | 81 | 		/* no signal from tuner */ | 
 | 82 | 		*flags=0; | 
 | 83 | 		*signal=0; | 
 | 84 | 		return 0; | 
 | 85 | 	} else | 
 | 86 | 		*signal=0xffff; | 
 | 87 |  | 
 | 88 | 	if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0) | 
 | 89 | 		return i; | 
 | 90 | 	if (i & 0x40) { | 
 | 91 | 		*flags=0; | 
 | 92 | 	} else { | 
 | 93 | 		/* stereo */ | 
 | 94 | 		*flags=VIDEO_TUNER_STEREO_ON; | 
 | 95 | 		/* I can't see stereo, when forced to mono */ | 
 | 96 | 		dev->stereo=1; | 
 | 97 | 	} | 
 | 98 |  | 
 | 99 | 	if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0) | 
 | 100 | 		return i; | 
 | 101 | 	if (buf & 1) | 
 | 102 | 		/* RDS available */ | 
 | 103 | 		*flags|=VIDEO_TUNER_RDS_ON; | 
 | 104 | 	else | 
 | 105 | 		return 0; | 
 | 106 |  | 
 | 107 | 	if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0) | 
 | 108 | 		return i; | 
 | 109 | 	pr_debug("rds-signal: %d\n", buf); | 
 | 110 | 	if (buf > 15) { | 
 | 111 | 		printk("miropcm20-radio: RX strengths unexpected high...\n"); | 
 | 112 | 		buf=15; | 
 | 113 | 	} | 
 | 114 | 	/* refine signal */ | 
 | 115 | 	if ((*signal=SCALE(15, 0xffff, buf))==0) | 
 | 116 | 		*signal = 1; | 
 | 117 |  | 
 | 118 | 	return 0; | 
 | 119 | } | 
 | 120 |  | 
 | 121 | static int pcm20_do_ioctl(struct inode *inode, struct file *file, | 
 | 122 | 			  unsigned int cmd, void *arg) | 
 | 123 | { | 
 | 124 | 	struct video_device *dev = video_devdata(file); | 
 | 125 | 	struct pcm20_device *pcm20 = dev->priv; | 
 | 126 | 	int i; | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 127 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 128 | 	switch(cmd) | 
 | 129 | 	{ | 
 | 130 | 		case VIDIOCGCAP: | 
 | 131 | 		{ | 
 | 132 | 			struct video_capability *v = arg; | 
 | 133 | 			memset(v,0,sizeof(*v)); | 
 | 134 | 			v->type=VID_TYPE_TUNER; | 
 | 135 | 			strcpy(v->name, "Miro PCM20"); | 
 | 136 | 			v->channels=1; | 
 | 137 | 			v->audios=1; | 
 | 138 | 			return 0; | 
 | 139 | 		} | 
 | 140 | 		case VIDIOCGTUNER: | 
 | 141 | 		{ | 
 | 142 | 			struct video_tuner *v = arg; | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 143 | 			if(v->tuner)	/* Only 1 tuner */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 144 | 				return -EINVAL; | 
 | 145 | 			v->rangelow=87*16000; | 
 | 146 | 			v->rangehigh=108*16000; | 
 | 147 | 			pcm20_getflags(pcm20, &v->flags, &v->signal); | 
 | 148 | 			v->flags|=VIDEO_TUNER_LOW; | 
 | 149 | 			v->mode=VIDEO_MODE_AUTO; | 
 | 150 | 			strcpy(v->name, "FM"); | 
 | 151 | 			return 0; | 
 | 152 | 		} | 
 | 153 | 		case VIDIOCSTUNER: | 
 | 154 | 		{ | 
 | 155 | 			struct video_tuner *v = arg; | 
 | 156 | 			if(v->tuner!=0) | 
 | 157 | 				return -EINVAL; | 
 | 158 | 			/* Only 1 tuner so no setting needed ! */ | 
 | 159 | 			return 0; | 
 | 160 | 		} | 
 | 161 | 		case VIDIOCGFREQ: | 
 | 162 | 		{ | 
 | 163 | 			unsigned long *freq = arg; | 
 | 164 | 			*freq = pcm20->freq; | 
 | 165 | 			return 0; | 
 | 166 | 		} | 
 | 167 | 		case VIDIOCSFREQ: | 
 | 168 | 		{ | 
 | 169 | 			unsigned long *freq = arg; | 
 | 170 | 			pcm20->freq = *freq; | 
 | 171 | 			i=pcm20_setfreq(pcm20, pcm20->freq); | 
 | 172 | 			pr_debug("First view (setfreq): 0x%x\n", i); | 
 | 173 | 			return i; | 
 | 174 | 		} | 
 | 175 | 		case VIDIOCGAUDIO: | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 176 | 		{ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 177 | 			struct video_audio *v = arg; | 
 | 178 | 			memset(v,0, sizeof(*v)); | 
 | 179 | 			v->flags=VIDEO_AUDIO_MUTABLE; | 
 | 180 | 			if (pcm20->muted) | 
 | 181 | 				v->flags|=VIDEO_AUDIO_MUTE; | 
 | 182 | 			v->mode=VIDEO_SOUND_STEREO; | 
 | 183 | 			if (pcm20->stereo) | 
 | 184 | 				v->mode|=VIDEO_SOUND_MONO; | 
 | 185 | 			/* v->step=2048; */ | 
 | 186 | 			strcpy(v->name, "Radio"); | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 187 | 			return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 188 | 		} | 
 | 189 | 		case VIDIOCSAUDIO: | 
 | 190 | 		{ | 
 | 191 | 			struct video_audio *v = arg; | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 192 | 			if(v->audio) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 193 | 				return -EINVAL; | 
 | 194 |  | 
 | 195 | 			pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE)); | 
 | 196 | 			if(v->flags&VIDEO_SOUND_MONO) | 
 | 197 | 				pcm20_stereo(pcm20, 0); | 
 | 198 | 			if(v->flags&VIDEO_SOUND_STEREO) | 
 | 199 | 				pcm20_stereo(pcm20, 1); | 
 | 200 |  | 
 | 201 | 			return 0; | 
 | 202 | 		} | 
 | 203 | 		default: | 
 | 204 | 			return -ENOIOCTLCMD; | 
 | 205 | 	} | 
 | 206 | } | 
 | 207 |  | 
 | 208 | static int pcm20_ioctl(struct inode *inode, struct file *file, | 
 | 209 | 		       unsigned int cmd, unsigned long arg) | 
 | 210 | { | 
 | 211 | 	return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl); | 
 | 212 | } | 
 | 213 |  | 
 | 214 | static struct pcm20_device pcm20_unit = { | 
 | 215 | 	.freq   = 87*16000, | 
 | 216 | 	.muted  = 1, | 
 | 217 | }; | 
 | 218 |  | 
| Arjan van de Ven | fa027c2 | 2007-02-12 00:55:33 -0800 | [diff] [blame^] | 219 | static const struct file_operations pcm20_fops = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 220 | 	.owner		= THIS_MODULE, | 
 | 221 | 	.open           = video_exclusive_open, | 
 | 222 | 	.release        = video_exclusive_release, | 
 | 223 | 	.ioctl		= pcm20_ioctl, | 
| Arnd Bergmann | 0d0fbf8 | 2006-01-09 15:24:57 -0200 | [diff] [blame] | 224 | 	.compat_ioctl	= v4l_compat_ioctl32, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 225 | 	.llseek         = no_llseek, | 
 | 226 | }; | 
 | 227 |  | 
 | 228 | static struct video_device pcm20_radio = { | 
 | 229 | 	.owner		= THIS_MODULE, | 
 | 230 | 	.name		= "Miro PCM 20 radio", | 
 | 231 | 	.type		= VID_TYPE_TUNER, | 
 | 232 | 	.hardware	= VID_HARDWARE_RTRACK, | 
 | 233 | 	.fops           = &pcm20_fops, | 
 | 234 | 	.priv		= &pcm20_unit | 
 | 235 | }; | 
 | 236 |  | 
 | 237 | static int __init pcm20_init(void) | 
 | 238 | { | 
 | 239 | 	if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1) | 
 | 240 | 		goto video_register_device; | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 241 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 242 | 	if(attach_aci_rds()<0) | 
 | 243 | 		goto attach_aci_rds; | 
 | 244 |  | 
 | 245 | 	printk(KERN_INFO "Miro PCM20 radio card driver.\n"); | 
 | 246 |  | 
 | 247 | 	return 0; | 
 | 248 |  | 
 | 249 |  attach_aci_rds: | 
 | 250 | 	video_unregister_device(&pcm20_radio); | 
 | 251 |  video_register_device: | 
 | 252 | 	return -EINVAL; | 
 | 253 | } | 
 | 254 |  | 
 | 255 | MODULE_AUTHOR("Ruurd Reitsma"); | 
 | 256 | MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); | 
 | 257 | MODULE_LICENSE("GPL"); | 
 | 258 |  | 
 | 259 | static void __exit pcm20_cleanup(void) | 
 | 260 | { | 
 | 261 | 	unload_aci_rds(); | 
 | 262 | 	video_unregister_device(&pcm20_radio); | 
 | 263 | } | 
 | 264 |  | 
 | 265 | module_init(pcm20_init); | 
 | 266 | module_exit(pcm20_cleanup); |