xref: /openbmc/linux/drivers/media/radio/radio-terratec.c (revision 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2)
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  */
25 
26 #include <linux/module.h>	/* Modules 			*/
27 #include <linux/init.h>		/* Initdata			*/
28 #include <linux/ioport.h>	/* check_region, request_region	*/
29 #include <linux/delay.h>	/* udelay			*/
30 #include <asm/io.h>		/* outb, outb_p			*/
31 #include <asm/uaccess.h>	/* copy to/from user		*/
32 #include <linux/videodev.h>	/* kernel radio structs		*/
33 #include <linux/config.h>	/* CONFIG_RADIO_TERRATEC_PORT 	*/
34 #include <linux/spinlock.h>
35 
36 #ifndef CONFIG_RADIO_TERRATEC_PORT
37 #define CONFIG_RADIO_TERRATEC_PORT 0x590
38 #endif
39 
40 /**************** this ones are for the terratec *******************/
41 #define BASEPORT 	0x590
42 #define VOLPORT 	0x591
43 #define WRT_DIS 	0x00
44 #define CLK_OFF		0x00
45 #define IIC_DATA	0x01
46 #define IIC_CLK		0x02
47 #define DATA		0x04
48 #define CLK_ON 		0x08
49 #define WRT_EN		0x10
50 /*******************************************************************/
51 
52 static int io = CONFIG_RADIO_TERRATEC_PORT;
53 static int radio_nr = -1;
54 static spinlock_t lock;
55 
56 struct tt_device
57 {
58 	int port;
59 	int curvol;
60 	unsigned long curfreq;
61 	int muted;
62 };
63 
64 
65 /* local things */
66 
67 static void cardWriteVol(int volume)
68 {
69 	int i;
70 	volume = volume+(volume * 32); // change both channels
71 	spin_lock(&lock);
72 	for (i=0;i<8;i++)
73 	{
74 		if (volume & (0x80>>i))
75 			outb(0x80, VOLPORT);
76 		else outb(0x00, VOLPORT);
77 	}
78 	spin_unlock(&lock);
79 }
80 
81 
82 
83 static void tt_mute(struct tt_device *dev)
84 {
85 	dev->muted = 1;
86 	cardWriteVol(0);
87 }
88 
89 static int tt_setvol(struct tt_device *dev, int vol)
90 {
91 
92 //	printk(KERN_ERR "setvol called, vol = %d\n", vol);
93 
94 	if(vol == dev->curvol) {	/* requested volume = current */
95 		if (dev->muted) {	/* user is unmuting the card  */
96 			dev->muted = 0;
97 			cardWriteVol(vol);	/* enable card */
98 		}
99 
100 		return 0;
101 	}
102 
103 	if(vol == 0) {			/* volume = 0 means mute the card */
104 		cardWriteVol(0);	/* "turn off card" by setting vol to 0 */
105 		dev->curvol = vol;	/* track the volume state!	*/
106 		return 0;
107 	}
108 
109 	dev->muted = 0;
110 
111 	cardWriteVol(vol);
112 
113 	dev->curvol = vol;
114 
115 	return 0;
116 
117 }
118 
119 
120 /* this is the worst part in this driver */
121 /* many more or less strange things are going on here, but hey, it works :) */
122 
123 static int tt_setfreq(struct tt_device *dev, unsigned long freq1)
124 {
125 	int freq;
126 	int i;
127 	int p;
128 	int  temp;
129 	long rest;
130 
131 	unsigned char buffer[25];		/* we have to bit shift 25 registers */
132 	freq = freq1/160;			/* convert the freq. to a nice to handle value */
133 	for(i=24;i>-1;i--)
134 		buffer[i]=0;
135 
136 	rest = freq*10+10700;		/* i once had understood what is going on here */
137 					/* maybe some wise guy (friedhelm?) can comment this stuff */
138 	i=13;
139 	p=10;
140 	temp=102400;
141 	while (rest!=0)
142 	{
143 		if (rest%temp  == rest)
144 			buffer[i] = 0;
145 		else
146 		{
147 			buffer[i] = 1;
148 			rest = rest-temp;
149 		}
150 		i--;
151 		p--;
152 		temp = temp/2;
153        }
154 
155 	spin_lock(&lock);
156 
157 	for (i=24;i>-1;i--)			/* bit shift the values to the radiocard */
158 	{
159 		if (buffer[i]==1)
160 		{
161 			outb(WRT_EN|DATA, BASEPORT);
162 			outb(WRT_EN|DATA|CLK_ON  , BASEPORT);
163 			outb(WRT_EN|DATA, BASEPORT);
164 		}
165 		else
166 		{
167 			outb(WRT_EN|0x00, BASEPORT);
168 			outb(WRT_EN|0x00|CLK_ON  , BASEPORT);
169 		}
170 	}
171 	outb(0x00, BASEPORT);
172 
173 	spin_unlock(&lock);
174 
175   	return 0;
176 }
177 
178 static int tt_getsigstr(struct tt_device *dev)		/* TODO */
179 {
180 	if (inb(io) & 2)	/* bit set = no signal present	*/
181 		return 0;
182 	return 1;		/* signal present		*/
183 }
184 
185 
186 /* implement the video4linux api */
187 
188 static int tt_do_ioctl(struct inode *inode, struct file *file,
189 		       unsigned int cmd, void *arg)
190 {
191 	struct video_device *dev = video_devdata(file);
192 	struct tt_device *tt=dev->priv;
193 
194 	switch(cmd)
195 	{
196 		case VIDIOCGCAP:
197 		{
198 			struct video_capability *v = arg;
199 			memset(v,0,sizeof(*v));
200 			v->type=VID_TYPE_TUNER;
201 			v->channels=1;
202 			v->audios=1;
203 			strcpy(v->name, "ActiveRadio");
204 			return 0;
205 		}
206 		case VIDIOCGTUNER:
207 		{
208 			struct video_tuner *v = arg;
209 			if(v->tuner)	/* Only 1 tuner */
210 				return -EINVAL;
211 			v->rangelow=(87*16000);
212 			v->rangehigh=(108*16000);
213 			v->flags=VIDEO_TUNER_LOW;
214 			v->mode=VIDEO_MODE_AUTO;
215 			strcpy(v->name, "FM");
216 			v->signal=0xFFFF*tt_getsigstr(tt);
217 			return 0;
218 		}
219 		case VIDIOCSTUNER:
220 		{
221 			struct video_tuner *v = arg;
222 			if(v->tuner!=0)
223 				return -EINVAL;
224 			/* Only 1 tuner so no setting needed ! */
225 			return 0;
226 		}
227 		case VIDIOCGFREQ:
228 		{
229 			unsigned long *freq = arg;
230 			*freq = tt->curfreq;
231 			return 0;
232 		}
233 		case VIDIOCSFREQ:
234 		{
235 			unsigned long *freq = arg;
236 			tt->curfreq = *freq;
237 			tt_setfreq(tt, tt->curfreq);
238 			return 0;
239 		}
240 		case VIDIOCGAUDIO:
241 		{
242 			struct video_audio *v = arg;
243 			memset(v,0, sizeof(*v));
244 			v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
245 			v->volume=tt->curvol * 6554;
246 			v->step=6554;
247 			strcpy(v->name, "Radio");
248 			return 0;
249 		}
250 		case VIDIOCSAUDIO:
251 		{
252 			struct video_audio *v = arg;
253 			if(v->audio)
254 				return -EINVAL;
255 			if(v->flags&VIDEO_AUDIO_MUTE)
256 				tt_mute(tt);
257 			else
258 				tt_setvol(tt,v->volume/6554);
259 			return 0;
260 		}
261 		default:
262 			return -ENOIOCTLCMD;
263 	}
264 }
265 
266 static int tt_ioctl(struct inode *inode, struct file *file,
267 		    unsigned int cmd, unsigned long arg)
268 {
269 	return video_usercopy(inode, file, cmd, arg, tt_do_ioctl);
270 }
271 
272 static struct tt_device terratec_unit;
273 
274 static struct file_operations terratec_fops = {
275 	.owner		= THIS_MODULE,
276 	.open           = video_exclusive_open,
277 	.release        = video_exclusive_release,
278 	.ioctl		= tt_ioctl,
279 	.llseek         = no_llseek,
280 };
281 
282 static struct video_device terratec_radio=
283 {
284 	.owner		= THIS_MODULE,
285 	.name		= "TerraTec ActiveRadio",
286 	.type		= VID_TYPE_TUNER,
287 	.hardware	= VID_HARDWARE_TERRATEC,
288 	.fops           = &terratec_fops,
289 };
290 
291 static int __init terratec_init(void)
292 {
293 	if(io==-1)
294 	{
295 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
296 		return -EINVAL;
297 	}
298 	if (!request_region(io, 2, "terratec"))
299 	{
300 		printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io);
301 		return -EBUSY;
302 	}
303 
304 	terratec_radio.priv=&terratec_unit;
305 
306 	spin_lock_init(&lock);
307 
308 	if(video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr)==-1)
309 	{
310 		release_region(io,2);
311 		return -EINVAL;
312 	}
313 
314 	printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n");
315 
316  	/* mute card - prevents noisy bootups */
317 
318 	/* this ensures that the volume is all the way down  */
319 	cardWriteVol(0);
320 	terratec_unit.curvol = 0;
321 
322 	return 0;
323 }
324 
325 MODULE_AUTHOR("R.OFFERMANNS & others");
326 MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
327 MODULE_LICENSE("GPL");
328 module_param(io, int, 0);
329 MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
330 module_param(radio_nr, int, 0);
331 
332 static void __exit terratec_cleanup_module(void)
333 {
334 	video_unregister_device(&terratec_radio);
335 	release_region(io,2);
336 	printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n");
337 }
338 
339 module_init(terratec_init);
340 module_exit(terratec_cleanup_module);
341 
342