blob: c2eeae7a10d02eb37e65f1b430ca3deb959edb38 [file] [log] [blame]
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -03001/*
2 * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net>
4 *
5 * Based in the radio Maestro PCI driver. Actually it uses the same chip
6 * for radio but different pci controller.
7 *
8 * I didn't have any specs I reversed engineered the protocol from
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -03009 * the windows driver (radio.dll).
Linus Torvalds1da177e2005-04-16 15:20:36 -070010 *
11 * The card uses the TEA5757 chip that includes a search function but it
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030012 * is useless as I haven't found any way to read back the frequency. If
Linus Torvalds1da177e2005-04-16 15:20:36 -070013 * anybody does please mail me.
14 *
15 * For the pdf file see:
16 * http://www.semiconductors.philips.com/pip/TEA5757H/V1
17 *
18 *
19 * CHANGES:
20 * 0.75b
21 * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr>
22 *
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -030023 * 0.75 Sun Feb 4 22:51:27 EET 2001
Linus Torvalds1da177e2005-04-16 15:20:36 -070024 * - tiding up
25 * - removed support for multiple devices as it didn't work anyway
26 *
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030027 * BUGS:
Linus Torvalds1da177e2005-04-16 15:20:36 -070028 * - card unmutes if you change frequency
29 *
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -030030 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
Linus Torvalds1da177e2005-04-16 15:20:36 -070031 */
32
33
34#include <linux/module.h>
35#include <linux/init.h>
36#include <linux/ioport.h>
37#include <linux/delay.h>
38#include <linux/sched.h>
39#include <asm/io.h>
40#include <asm/uaccess.h>
Ingo Molnar3593cab2006-02-07 06:49:14 -020041#include <linux/mutex.h>
42
Linus Torvalds1da177e2005-04-16 15:20:36 -070043#include <linux/pci.h>
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -030044#include <linux/videodev2.h>
Mauro Carvalho Chehab5e87efa2006-06-05 10:26:32 -030045#include <media/v4l2-common.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070046
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -030047#define DRIVER_VERSION "0.76"
48
49#include <linux/version.h> /* for KERNEL_VERSION MACRO */
50#define RADIO_VERSION KERNEL_VERSION(0,7,6)
51
52static struct v4l2_queryctrl radio_qctrl[] = {
53 {
54 .id = V4L2_CID_AUDIO_MUTE,
55 .name = "Mute",
56 .minimum = 0,
57 .maximum = 1,
58 .default_value = 1,
59 .type = V4L2_CTRL_TYPE_BOOLEAN,
60 }
61};
Linus Torvalds1da177e2005-04-16 15:20:36 -070062
63#ifndef PCI_VENDOR_ID_GUILLEMOT
64#define PCI_VENDOR_ID_GUILLEMOT 0x5046
65#endif
66
67#ifndef PCI_DEVICE_ID_GUILLEMOT
68#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
69#endif
70
71
72/* TEA5757 pin mappings */
73static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ;
74
75static int radio_nr = -1;
76module_param(radio_nr, int, 0);
77
78
79#define FREQ_LO 50*16000
80#define FREQ_HI 150*16000
81
82#define FREQ_IF 171200 /* 10.7*16000 */
83#define FREQ_STEP 200 /* 12.5*16 */
84
85#define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\
86 /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */
87
88#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)
89
90
91static int radio_ioctl(struct inode *inode, struct file *file,
92 unsigned int cmd, unsigned long arg);
93
94static struct file_operations maxiradio_fops = {
95 .owner = THIS_MODULE,
96 .open = video_exclusive_open,
97 .release = video_exclusive_release,
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -030098 .ioctl = radio_ioctl,
Arnd Bergmann0d0fbf82006-01-09 15:24:57 -020099 .compat_ioctl = v4l_compat_ioctl32,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 .llseek = no_llseek,
101};
102static struct video_device maxiradio_radio =
103{
104 .owner = THIS_MODULE,
105 .name = "Maxi Radio FM2000 radio",
106 .type = VID_TYPE_TUNER,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107 .fops = &maxiradio_fops,
108};
109
110static struct radio_device
111{
112 __u16 io, /* base of radio io */
113 muted, /* VIDEO_AUDIO_MUTE */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300114 stereo, /* VIDEO_TUNER_STEREO_ON */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 tuned; /* signal strength (0 or 0xffff) */
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300116
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 unsigned long freq;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300118
Ingo Molnar3593cab2006-02-07 06:49:14 -0200119 struct mutex lock;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120} radio_unit = {0, 0, 0, 0, };
121
122
123static void outbit(unsigned long bit, __u16 io)
124{
125 if(bit != 0)
126 {
127 outb( power|wren|data ,io); udelay(4);
128 outb( power|wren|data|clk ,io); udelay(4);
129 outb( power|wren|data ,io); udelay(4);
130 }
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300131 else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 {
133 outb( power|wren ,io); udelay(4);
134 outb( power|wren|clk ,io); udelay(4);
135 outb( power|wren ,io); udelay(4);
136 }
137}
138
139static void turn_power(__u16 io, int p)
140{
141 if(p != 0) outb(power, io); else outb(0,io);
142}
143
144
145static void set_freq(__u16 io, __u32 data)
146{
147 unsigned long int si;
148 int bl;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300149
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150 /* TEA5757 shift register bits (see pdf) */
151
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300152 outbit(0,io); // 24 search
Linus Torvalds1da177e2005-04-16 15:20:36 -0700153 outbit(1,io); // 23 search up/down
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300154
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155 outbit(0,io); // 22 stereo/mono
156
157 outbit(0,io); // 21 band
158 outbit(0,io); // 20 band (only 00=FM works I think)
159
160 outbit(0,io); // 19 port ?
161 outbit(0,io); // 18 port ?
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300162
Linus Torvalds1da177e2005-04-16 15:20:36 -0700163 outbit(0,io); // 17 search level
164 outbit(0,io); // 16 search level
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300165
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 si = 0x8000;
167 for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; }
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300168
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169 outb(power,io);
170}
171
172static int get_stereo(__u16 io)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300173{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174 outb(power,io); udelay(4);
175 return !(inb(io) & mo_st);
176}
177
178static int get_tune(__u16 io)
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300179{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 outb(power+clk,io); udelay(4);
181 return !(inb(io) & mo_st);
182}
183
184
Jesper Juhl77933d72005-07-27 11:46:09 -0700185static inline int radio_function(struct inode *inode, struct file *file,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186 unsigned int cmd, void *arg)
187{
188 struct video_device *dev = video_devdata(file);
189 struct radio_device *card=dev->priv;
190
191 switch(cmd) {
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300192 case VIDIOC_QUERYCAP:
193 {
194 struct v4l2_capability *v = arg;
195 memset(v,0,sizeof(*v));
196 strlcpy(v->driver, "radio-maxiradio", sizeof (v->driver));
197 strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof (v->card));
198 sprintf(v->bus_info,"ISA");
199 v->version = RADIO_VERSION;
200 v->capabilities = V4L2_CAP_TUNER;
201
202 return 0;
203 }
204 case VIDIOC_G_TUNER:
205 {
206 struct v4l2_tuner *v = arg;
207
208 if (v->index > 0)
209 return -EINVAL;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300210
Linus Torvalds1da177e2005-04-16 15:20:36 -0700211 memset(v,0,sizeof(*v));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212 strcpy(v->name, "FM");
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300213 v->type = V4L2_TUNER_RADIO;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300214
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300215 v->rangelow=FREQ_LO;
216 v->rangehigh=FREQ_HI;
217 v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
218 v->capability=V4L2_TUNER_CAP_LOW;
219 if(get_stereo(card->io))
220 v->audmode = V4L2_TUNER_MODE_STEREO;
221 else
222 v->audmode = V4L2_TUNER_MODE_MONO;
223 v->signal=0xffff*get_tune(card->io);
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300224
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225 return 0;
226 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300227 case VIDIOC_S_TUNER:
228 {
229 struct v4l2_tuner *v = arg;
230
231 if (v->index > 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700232 return -EINVAL;
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300233
Linus Torvalds1da177e2005-04-16 15:20:36 -0700234 return 0;
235 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300236 case VIDIOC_S_FREQUENCY:
237 {
238 struct v4l2_frequency *f = arg;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300239
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300240 if (f->frequency < FREQ_LO || f->frequency > FREQ_HI)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700241 return -EINVAL;
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300242
243 card->freq = f->frequency;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244 set_freq(card->io, FREQ2BITS(card->freq));
245 msleep(125);
246 return 0;
247 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300248 case VIDIOC_G_FREQUENCY:
249 {
250 struct v4l2_frequency *f = arg;
251
252 f->type = V4L2_TUNER_RADIO;
253 f->frequency = card->freq;
254
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300255 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700256 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300257 case VIDIOC_QUERYCTRL:
258 {
259 struct v4l2_queryctrl *qc = arg;
260 int i;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300261
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300262 for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
263 if (qc->id && qc->id == radio_qctrl[i].id) {
264 memcpy(qc, &(radio_qctrl[i]),
265 sizeof(*qc));
266 return (0);
267 }
268 }
269 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300271 case VIDIOC_G_CTRL:
272 {
273 struct v4l2_control *ctrl= arg;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300274
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300275 switch (ctrl->id) {
276 case V4L2_CID_AUDIO_MUTE:
277 ctrl->value=card->muted;
278 return (0);
279 }
280 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700281 }
Mauro Carvalho Chehabe84fef62006-08-08 09:10:05 -0300282 case VIDIOC_S_CTRL:
283 {
284 struct v4l2_control *ctrl= arg;
285
286 switch (ctrl->id) {
287 case V4L2_CID_AUDIO_MUTE:
288 card->muted = ctrl->value;
289 if(card->muted)
290 turn_power(card->io, 0);
291 else
292 set_freq(card->io, FREQ2BITS(card->freq));
293 return 0;
294 }
295 return -EINVAL;
296 }
297
298 default:
299 return v4l_compat_translate_ioctl(inode,file,cmd,arg,
300 radio_function);
301
Linus Torvalds1da177e2005-04-16 15:20:36 -0700302 }
303}
304
305static int radio_ioctl(struct inode *inode, struct file *file,
306 unsigned int cmd, unsigned long arg)
307{
308 struct video_device *dev = video_devdata(file);
309 struct radio_device *card=dev->priv;
310 int ret;
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300311
Ingo Molnar3593cab2006-02-07 06:49:14 -0200312 mutex_lock(&card->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700313 ret = video_usercopy(inode, file, cmd, arg, radio_function);
Ingo Molnar3593cab2006-02-07 06:49:14 -0200314 mutex_unlock(&card->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700315 return ret;
316}
317
318MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
319MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");
320MODULE_LICENSE("GPL");
321
322
323static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
324{
325 if(!request_region(pci_resource_start(pdev, 0),
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300326 pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) {
327 printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n");
328 goto err_out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329 }
330
331 if (pci_enable_device(pdev))
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300332 goto err_out_free_region;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700333
334 radio_unit.io = pci_resource_start(pdev, 0);
Ingo Molnar3593cab2006-02-07 06:49:14 -0200335 mutex_init(&radio_unit.lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700336 maxiradio_radio.priv = &radio_unit;
337
338 if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) {
Mauro Carvalho Chehab4286c6f2006-04-08 16:06:16 -0300339 printk("radio-maxiradio: can't register device!");
340 goto err_out_free_region;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 }
342
343 printk(KERN_INFO "radio-maxiradio: version "
344 DRIVER_VERSION
345 " time "
346 __TIME__ " "
347 __DATE__
348 "\n");
349
350 printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n",
351 radio_unit.io);
352 return 0;
353
354err_out_free_region:
355 release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
356err_out:
357 return -ENODEV;
358}
359
360static void __devexit maxiradio_remove_one(struct pci_dev *pdev)
361{
362 video_unregister_device(&maxiradio_radio);
363 release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
364}
365
366static struct pci_device_id maxiradio_pci_tbl[] = {
367 { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO,
368 PCI_ANY_ID, PCI_ANY_ID, },
369 { 0,}
370};
371
372MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl);
373
374static struct pci_driver maxiradio_driver = {
375 .name = "radio-maxiradio",
376 .id_table = maxiradio_pci_tbl,
377 .probe = maxiradio_init_one,
378 .remove = __devexit_p(maxiradio_remove_one),
379};
380
381static int __init maxiradio_radio_init(void)
382{
Richard Knutsson9bfab8c2005-11-30 00:59:34 +0100383 return pci_register_driver(&maxiradio_driver);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700384}
385
386static void __exit maxiradio_radio_exit(void)
387{
388 pci_unregister_driver(&maxiradio_driver);
389}
390
391module_init(maxiradio_radio_init);
392module_exit(maxiradio_radio_exit);