xref: /linux/drivers/media/radio/radio-terratec.c (revision c537b994505099b7197e7d3125b942ecbcc51eb6)
1 /* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
2  * (c) 1999 R. Offermanns (rolf@offermanns.de)
3  * based on the aimslab radio driver from M. Kirkwood
4  * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
5  *
6  *
7  * History:
8  * 1999-05-21	First preview release
9  *
10  *  Notes on the hardware:
11  *  There are two "main" chips on the card:
12  *  - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
13  *  - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
14  *  (you can get the datasheet at the above links)
15  *
16  *  Frequency control is done digitally -- ie out(port,encodefreq(95.8));
17  *  Volume Control is done digitally
18  *
19  *  there is a I2C controlled RDS decoder (SAA6588)  onboard, which i would like to support someday
20  *  (as soon i have understand how to get started :)
21  *  If you can help me out with that, please contact me!!
22  *
23  *
24  * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
25  */
26 
27 #include <linux/module.h>	/* Modules 			*/
28 #include <linux/init.h>		/* Initdata			*/
29 #include <linux/ioport.h>	/* request_region		*/
30 #include <linux/delay.h>	/* udelay			*/
31 #include <asm/io.h>		/* outb, outb_p			*/
32 #include <asm/uaccess.h>	/* copy to/from user		*/
33 #include <linux/videodev2.h>	/* kernel radio structs		*/
34 #include <media/v4l2-common.h>
35 #include <linux/spinlock.h>
36 
37 #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
38 #define RADIO_VERSION KERNEL_VERSION(0,0,2)
39 
40 static struct v4l2_queryctrl radio_qctrl[] = {
41 	{
42 		.id            = V4L2_CID_AUDIO_MUTE,
43 		.name          = "Mute",
44 		.minimum       = 0,
45 		.maximum       = 1,
46 		.default_value = 1,
47 		.type          = V4L2_CTRL_TYPE_BOOLEAN,
48 	},{
49 		.id            = V4L2_CID_AUDIO_VOLUME,
50 		.name          = "Volume",
51 		.minimum       = 0,
52 		.maximum       = 0xff,
53 		.step          = 1,
54 		.default_value = 0xff,
55 		.type          = V4L2_CTRL_TYPE_INTEGER,
56 	}
57 };
58 
59 #ifndef CONFIG_RADIO_TERRATEC_PORT
60 #define CONFIG_RADIO_TERRATEC_PORT 0x590
61 #endif
62 
63 /**************** this ones are for the terratec *******************/
64 #define BASEPORT 	0x590
65 #define VOLPORT 	0x591
66 #define WRT_DIS 	0x00
67 #define CLK_OFF		0x00
68 #define IIC_DATA	0x01
69 #define IIC_CLK		0x02
70 #define DATA		0x04
71 #define CLK_ON 		0x08
72 #define WRT_EN		0x10
73 /*******************************************************************/
74 
75 static int io = CONFIG_RADIO_TERRATEC_PORT;
76 static int radio_nr = -1;
77 static spinlock_t lock;
78 
79 struct tt_device
80 {
81 	int port;
82 	int curvol;
83 	unsigned long curfreq;
84 	int muted;
85 };
86 
87 
88 /* local things */
89 
90 static void cardWriteVol(int volume)
91 {
92 	int i;
93 	volume = volume+(volume * 32); // change both channels
94 	spin_lock(&lock);
95 	for (i=0;i<8;i++)
96 	{
97 		if (volume & (0x80>>i))
98 			outb(0x80, VOLPORT);
99 		else outb(0x00, VOLPORT);
100 	}
101 	spin_unlock(&lock);
102 }
103 
104 
105 
106 static void tt_mute(struct tt_device *dev)
107 {
108 	dev->muted = 1;
109 	cardWriteVol(0);
110 }
111 
112 static int tt_setvol(struct tt_device *dev, int vol)
113 {
114 
115 //	printk(KERN_ERR "setvol called, vol = %d\n", vol);
116 
117 	if(vol == dev->curvol) {	/* requested volume = current */
118 		if (dev->muted) {	/* user is unmuting the card  */
119 			dev->muted = 0;
120 			cardWriteVol(vol);	/* enable card */
121 		}
122 
123 		return 0;
124 	}
125 
126 	if(vol == 0) {			/* volume = 0 means mute the card */
127 		cardWriteVol(0);	/* "turn off card" by setting vol to 0 */
128 		dev->curvol = vol;	/* track the volume state!	*/
129 		return 0;
130 	}
131 
132 	dev->muted = 0;
133 
134 	cardWriteVol(vol);
135 
136 	dev->curvol = vol;
137 
138 	return 0;
139 
140 }
141 
142 
143 /* this is the worst part in this driver */
144 /* many more or less strange things are going on here, but hey, it works :) */
145 
146 static int tt_setfreq(struct tt_device *dev, unsigned long freq1)
147 {
148 	int freq;
149 	int i;
150 	int p;
151 	int  temp;
152 	long rest;
153 
154 	unsigned char buffer[25];		/* we have to bit shift 25 registers */
155 	freq = freq1/160;			/* convert the freq. to a nice to handle value */
156 	for(i=24;i>-1;i--)
157 		buffer[i]=0;
158 
159 	rest = freq*10+10700;		/* i once had understood what is going on here */
160 					/* maybe some wise guy (friedhelm?) can comment this stuff */
161 	i=13;
162 	p=10;
163 	temp=102400;
164 	while (rest!=0)
165 	{
166 		if (rest%temp  == rest)
167 			buffer[i] = 0;
168 		else
169 		{
170 			buffer[i] = 1;
171 			rest = rest-temp;
172 		}
173 		i--;
174 		p--;
175 		temp = temp/2;
176        }
177 
178 	spin_lock(&lock);
179 
180 	for (i=24;i>-1;i--)			/* bit shift the values to the radiocard */
181 	{
182 		if (buffer[i]==1)
183 		{
184 			outb(WRT_EN|DATA, BASEPORT);
185 			outb(WRT_EN|DATA|CLK_ON  , BASEPORT);
186 			outb(WRT_EN|DATA, BASEPORT);
187 		}
188 		else
189 		{
190 			outb(WRT_EN|0x00, BASEPORT);
191 			outb(WRT_EN|0x00|CLK_ON  , BASEPORT);
192 		}
193 	}
194 	outb(0x00, BASEPORT);
195 
196 	spin_unlock(&lock);
197 
198 	return 0;
199 }
200 
201 static int tt_getsigstr(struct tt_device *dev)		/* TODO */
202 {
203 	if (inb(io) & 2)	/* bit set = no signal present	*/
204 		return 0;
205 	return 1;		/* signal present		*/
206 }
207 
208 
209 /* implement the video4linux api */
210 
211 static int tt_do_ioctl(struct inode *inode, struct file *file,
212 		       unsigned int cmd, void *arg)
213 {
214 	struct video_device *dev = video_devdata(file);
215 	struct tt_device *tt=dev->priv;
216 
217 	switch(cmd)
218 	{
219 		case VIDIOC_QUERYCAP:
220 		{
221 			struct v4l2_capability *v = arg;
222 			memset(v,0,sizeof(*v));
223 			strlcpy(v->driver, "radio-terratec", sizeof (v->driver));
224 			strlcpy(v->card, "ActiveRadio", sizeof (v->card));
225 			sprintf(v->bus_info,"ISA");
226 			v->version = RADIO_VERSION;
227 			v->capabilities = V4L2_CAP_TUNER;
228 
229 			return 0;
230 		}
231 		case VIDIOC_G_TUNER:
232 		{
233 			struct v4l2_tuner *v = arg;
234 
235 			if (v->index > 0)
236 				return -EINVAL;
237 
238 			memset(v,0,sizeof(*v));
239 			strcpy(v->name, "FM");
240 			v->type = V4L2_TUNER_RADIO;
241 
242 			v->rangelow=(87*16000);
243 			v->rangehigh=(108*16000);
244 			v->rxsubchans =V4L2_TUNER_SUB_MONO;
245 			v->capability=V4L2_TUNER_CAP_LOW;
246 			v->audmode = V4L2_TUNER_MODE_MONO;
247 			v->signal=0xFFFF*tt_getsigstr(tt);
248 
249 			return 0;
250 		}
251 		case VIDIOC_S_TUNER:
252 		{
253 			struct v4l2_tuner *v = arg;
254 
255 			if (v->index > 0)
256 				return -EINVAL;
257 
258 			return 0;
259 		}
260 		case VIDIOC_S_FREQUENCY:
261 		{
262 			struct v4l2_frequency *f = arg;
263 
264 			tt->curfreq = f->frequency;
265 			tt_setfreq(tt, tt->curfreq);
266 			return 0;
267 		}
268 		case VIDIOC_G_FREQUENCY:
269 		{
270 			struct v4l2_frequency *f = arg;
271 
272 			f->type = V4L2_TUNER_RADIO;
273 			f->frequency = tt->curfreq;
274 
275 			return 0;
276 		}
277 		case VIDIOC_QUERYCTRL:
278 		{
279 			struct v4l2_queryctrl *qc = arg;
280 			int i;
281 
282 			for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
283 				if (qc->id && qc->id == radio_qctrl[i].id) {
284 					memcpy(qc, &(radio_qctrl[i]),
285 								sizeof(*qc));
286 					return (0);
287 				}
288 			}
289 			return -EINVAL;
290 		}
291 		case VIDIOC_G_CTRL:
292 		{
293 			struct v4l2_control *ctrl= arg;
294 
295 			switch (ctrl->id) {
296 				case V4L2_CID_AUDIO_MUTE:
297 					if (tt->muted)
298 						ctrl->value=1;
299 					else
300 						ctrl->value=0;
301 					return (0);
302 				case V4L2_CID_AUDIO_VOLUME:
303 					ctrl->value=tt->curvol * 6554;
304 					return (0);
305 			}
306 			return -EINVAL;
307 		}
308 		case VIDIOC_S_CTRL:
309 		{
310 			struct v4l2_control *ctrl= arg;
311 
312 			switch (ctrl->id) {
313 				case V4L2_CID_AUDIO_MUTE:
314 					if (ctrl->value) {
315 						tt_mute(tt);
316 					} else {
317 						tt_setvol(tt,tt->curvol);
318 					}
319 					return (0);
320 				case V4L2_CID_AUDIO_VOLUME:
321 					tt_setvol(tt,ctrl->value);
322 					return (0);
323 			}
324 			return -EINVAL;
325 		}
326 
327 		default:
328 			return v4l_compat_translate_ioctl(inode,file,cmd,arg,
329 							  tt_do_ioctl);
330 	}
331 }
332 
333 static int tt_ioctl(struct inode *inode, struct file *file,
334 		    unsigned int cmd, unsigned long arg)
335 {
336 	return video_usercopy(inode, file, cmd, arg, tt_do_ioctl);
337 }
338 
339 static struct tt_device terratec_unit;
340 
341 static const struct file_operations terratec_fops = {
342 	.owner		= THIS_MODULE,
343 	.open           = video_exclusive_open,
344 	.release        = video_exclusive_release,
345 	.ioctl		= tt_ioctl,
346 	.compat_ioctl	= v4l_compat_ioctl32,
347 	.llseek         = no_llseek,
348 };
349 
350 static struct video_device terratec_radio=
351 {
352 	.owner		= THIS_MODULE,
353 	.name		= "TerraTec ActiveRadio",
354 	.type		= VID_TYPE_TUNER,
355 	.hardware	= 0,
356 	.fops           = &terratec_fops,
357 };
358 
359 static int __init terratec_init(void)
360 {
361 	if(io==-1)
362 	{
363 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
364 		return -EINVAL;
365 	}
366 	if (!request_region(io, 2, "terratec"))
367 	{
368 		printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io);
369 		return -EBUSY;
370 	}
371 
372 	terratec_radio.priv=&terratec_unit;
373 
374 	spin_lock_init(&lock);
375 
376 	if(video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr)==-1)
377 	{
378 		release_region(io,2);
379 		return -EINVAL;
380 	}
381 
382 	printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n");
383 
384 	/* mute card - prevents noisy bootups */
385 
386 	/* this ensures that the volume is all the way down  */
387 	cardWriteVol(0);
388 	terratec_unit.curvol = 0;
389 
390 	return 0;
391 }
392 
393 MODULE_AUTHOR("R.OFFERMANNS & others");
394 MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
395 MODULE_LICENSE("GPL");
396 module_param(io, int, 0);
397 MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
398 module_param(radio_nr, int, 0);
399 
400 static void __exit terratec_cleanup_module(void)
401 {
402 	video_unregister_device(&terratec_radio);
403 	release_region(io,2);
404 	printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n");
405 }
406 
407 module_init(terratec_init);
408 module_exit(terratec_cleanup_module);
409 
410