]>
Commit | Line | Data |
---|---|---|
4286c6f6 MCC |
1 | /* |
2 | * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux | |
1da177e4 LT |
3 | * (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 | |
4286c6f6 | 9 | * the windows driver (radio.dll). |
1da177e4 LT |
10 | * |
11 | * The card uses the TEA5757 chip that includes a search function but it | |
4286c6f6 | 12 | * is useless as I haven't found any way to read back the frequency. If |
1da177e4 LT |
13 | * 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 | * | |
e84fef6b | 23 | * 0.75 Sun Feb 4 22:51:27 EET 2001 |
1da177e4 LT |
24 | * - tiding up |
25 | * - removed support for multiple devices as it didn't work anyway | |
26 | * | |
4286c6f6 | 27 | * BUGS: |
1da177e4 LT |
28 | * - card unmutes if you change frequency |
29 | * | |
e84fef6b | 30 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> |
1da177e4 LT |
31 | */ |
32 | ||
33 | ||
34 | #include <linux/module.h> | |
35 | #include <linux/init.h> | |
36 | #include <linux/ioport.h> | |
37 | #include <linux/delay.h> | |
1da177e4 LT |
38 | #include <asm/io.h> |
39 | #include <asm/uaccess.h> | |
3593cab5 IM |
40 | #include <linux/mutex.h> |
41 | ||
1da177e4 | 42 | #include <linux/pci.h> |
e84fef6b | 43 | #include <linux/videodev2.h> |
5e87efa3 | 44 | #include <media/v4l2-common.h> |
1da177e4 | 45 | |
e84fef6b MCC |
46 | #define DRIVER_VERSION "0.76" |
47 | ||
48 | #include <linux/version.h> /* for KERNEL_VERSION MACRO */ | |
49 | #define RADIO_VERSION KERNEL_VERSION(0,7,6) | |
50 | ||
51 | static struct v4l2_queryctrl radio_qctrl[] = { | |
52 | { | |
53 | .id = V4L2_CID_AUDIO_MUTE, | |
54 | .name = "Mute", | |
55 | .minimum = 0, | |
56 | .maximum = 1, | |
57 | .default_value = 1, | |
58 | .type = V4L2_CTRL_TYPE_BOOLEAN, | |
59 | } | |
60 | }; | |
1da177e4 LT |
61 | |
62 | #ifndef PCI_VENDOR_ID_GUILLEMOT | |
63 | #define PCI_VENDOR_ID_GUILLEMOT 0x5046 | |
64 | #endif | |
65 | ||
66 | #ifndef PCI_DEVICE_ID_GUILLEMOT | |
67 | #define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 | |
68 | #endif | |
69 | ||
70 | ||
71 | /* TEA5757 pin mappings */ | |
72 | static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ; | |
73 | ||
74 | static int radio_nr = -1; | |
75 | module_param(radio_nr, int, 0); | |
76 | ||
77 | ||
78 | #define FREQ_LO 50*16000 | |
79 | #define FREQ_HI 150*16000 | |
80 | ||
81 | #define FREQ_IF 171200 /* 10.7*16000 */ | |
82 | #define FREQ_STEP 200 /* 12.5*16 */ | |
83 | ||
84 | #define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\ | |
85 | /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */ | |
86 | ||
87 | #define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) | |
88 | ||
89 | ||
90 | static int radio_ioctl(struct inode *inode, struct file *file, | |
91 | unsigned int cmd, unsigned long arg); | |
92 | ||
fa027c2a | 93 | static const struct file_operations maxiradio_fops = { |
1da177e4 LT |
94 | .owner = THIS_MODULE, |
95 | .open = video_exclusive_open, | |
96 | .release = video_exclusive_release, | |
4286c6f6 | 97 | .ioctl = radio_ioctl, |
0d0fbf81 | 98 | .compat_ioctl = v4l_compat_ioctl32, |
1da177e4 LT |
99 | .llseek = no_llseek, |
100 | }; | |
101 | static struct video_device maxiradio_radio = | |
102 | { | |
103 | .owner = THIS_MODULE, | |
104 | .name = "Maxi Radio FM2000 radio", | |
105 | .type = VID_TYPE_TUNER, | |
1da177e4 LT |
106 | .fops = &maxiradio_fops, |
107 | }; | |
108 | ||
109 | static struct radio_device | |
110 | { | |
111 | __u16 io, /* base of radio io */ | |
112 | muted, /* VIDEO_AUDIO_MUTE */ | |
4286c6f6 | 113 | stereo, /* VIDEO_TUNER_STEREO_ON */ |
1da177e4 | 114 | tuned; /* signal strength (0 or 0xffff) */ |
4286c6f6 | 115 | |
1da177e4 | 116 | unsigned long freq; |
4286c6f6 | 117 | |
3593cab5 | 118 | struct mutex lock; |
1da177e4 LT |
119 | } radio_unit = {0, 0, 0, 0, }; |
120 | ||
121 | ||
122 | static void outbit(unsigned long bit, __u16 io) | |
123 | { | |
124 | if(bit != 0) | |
125 | { | |
126 | outb( power|wren|data ,io); udelay(4); | |
127 | outb( power|wren|data|clk ,io); udelay(4); | |
128 | outb( power|wren|data ,io); udelay(4); | |
129 | } | |
4286c6f6 | 130 | else |
1da177e4 LT |
131 | { |
132 | outb( power|wren ,io); udelay(4); | |
133 | outb( power|wren|clk ,io); udelay(4); | |
134 | outb( power|wren ,io); udelay(4); | |
135 | } | |
136 | } | |
137 | ||
138 | static void turn_power(__u16 io, int p) | |
139 | { | |
140 | if(p != 0) outb(power, io); else outb(0,io); | |
141 | } | |
142 | ||
143 | ||
144 | static void set_freq(__u16 io, __u32 data) | |
145 | { | |
146 | unsigned long int si; | |
147 | int bl; | |
4286c6f6 | 148 | |
1da177e4 LT |
149 | /* TEA5757 shift register bits (see pdf) */ |
150 | ||
4286c6f6 | 151 | outbit(0,io); // 24 search |
1da177e4 | 152 | outbit(1,io); // 23 search up/down |
4286c6f6 | 153 | |
1da177e4 LT |
154 | outbit(0,io); // 22 stereo/mono |
155 | ||
156 | outbit(0,io); // 21 band | |
157 | outbit(0,io); // 20 band (only 00=FM works I think) | |
158 | ||
159 | outbit(0,io); // 19 port ? | |
160 | outbit(0,io); // 18 port ? | |
4286c6f6 | 161 | |
1da177e4 LT |
162 | outbit(0,io); // 17 search level |
163 | outbit(0,io); // 16 search level | |
4286c6f6 | 164 | |
1da177e4 LT |
165 | si = 0x8000; |
166 | for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; } | |
4286c6f6 | 167 | |
1da177e4 LT |
168 | outb(power,io); |
169 | } | |
170 | ||
171 | static int get_stereo(__u16 io) | |
4286c6f6 | 172 | { |
1da177e4 LT |
173 | outb(power,io); udelay(4); |
174 | return !(inb(io) & mo_st); | |
175 | } | |
176 | ||
177 | static int get_tune(__u16 io) | |
4286c6f6 | 178 | { |
1da177e4 LT |
179 | outb(power+clk,io); udelay(4); |
180 | return !(inb(io) & mo_st); | |
181 | } | |
182 | ||
183 | ||
77933d72 | 184 | static inline int radio_function(struct inode *inode, struct file *file, |
1da177e4 LT |
185 | unsigned int cmd, void *arg) |
186 | { | |
187 | struct video_device *dev = video_devdata(file); | |
188 | struct radio_device *card=dev->priv; | |
189 | ||
190 | switch(cmd) { | |
e84fef6b MCC |
191 | case VIDIOC_QUERYCAP: |
192 | { | |
193 | struct v4l2_capability *v = arg; | |
1da177e4 | 194 | memset(v,0,sizeof(*v)); |
e84fef6b MCC |
195 | strlcpy(v->driver, "radio-maxiradio", sizeof (v->driver)); |
196 | strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof (v->card)); | |
197 | sprintf(v->bus_info,"ISA"); | |
198 | v->version = RADIO_VERSION; | |
199 | v->capabilities = V4L2_CAP_TUNER; | |
200 | ||
1da177e4 LT |
201 | return 0; |
202 | } | |
e84fef6b MCC |
203 | case VIDIOC_G_TUNER: |
204 | { | |
205 | struct v4l2_tuner *v = arg; | |
4286c6f6 | 206 | |
e84fef6b | 207 | if (v->index > 0) |
1da177e4 | 208 | return -EINVAL; |
4286c6f6 | 209 | |
e84fef6b | 210 | memset(v,0,sizeof(*v)); |
1da177e4 | 211 | strcpy(v->name, "FM"); |
e84fef6b MCC |
212 | v->type = V4L2_TUNER_RADIO; |
213 | ||
214 | v->rangelow=FREQ_LO; | |
215 | v->rangehigh=FREQ_HI; | |
216 | v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO; | |
217 | v->capability=V4L2_TUNER_CAP_LOW; | |
218 | if(get_stereo(card->io)) | |
219 | v->audmode = V4L2_TUNER_MODE_STEREO; | |
220 | else | |
221 | v->audmode = V4L2_TUNER_MODE_MONO; | |
222 | v->signal=0xffff*get_tune(card->io); | |
4286c6f6 | 223 | |
1da177e4 LT |
224 | return 0; |
225 | } | |
e84fef6b MCC |
226 | case VIDIOC_S_TUNER: |
227 | { | |
228 | struct v4l2_tuner *v = arg; | |
229 | ||
230 | if (v->index > 0) | |
1da177e4 | 231 | return -EINVAL; |
4286c6f6 | 232 | |
1da177e4 LT |
233 | return 0; |
234 | } | |
e84fef6b MCC |
235 | case VIDIOC_S_FREQUENCY: |
236 | { | |
237 | struct v4l2_frequency *f = arg; | |
4286c6f6 | 238 | |
e84fef6b | 239 | if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) |
1da177e4 | 240 | return -EINVAL; |
e84fef6b MCC |
241 | |
242 | card->freq = f->frequency; | |
1da177e4 LT |
243 | set_freq(card->io, FREQ2BITS(card->freq)); |
244 | msleep(125); | |
245 | return 0; | |
246 | } | |
e84fef6b MCC |
247 | case VIDIOC_G_FREQUENCY: |
248 | { | |
249 | struct v4l2_frequency *f = arg; | |
4286c6f6 | 250 | |
e84fef6b MCC |
251 | f->type = V4L2_TUNER_RADIO; |
252 | f->frequency = card->freq; | |
4286c6f6 | 253 | |
1da177e4 LT |
254 | return 0; |
255 | } | |
e84fef6b MCC |
256 | case VIDIOC_QUERYCTRL: |
257 | { | |
258 | struct v4l2_queryctrl *qc = arg; | |
259 | int i; | |
260 | ||
261 | for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { | |
262 | if (qc->id && qc->id == radio_qctrl[i].id) { | |
263 | memcpy(qc, &(radio_qctrl[i]), | |
264 | sizeof(*qc)); | |
265 | return (0); | |
266 | } | |
267 | } | |
268 | return -EINVAL; | |
269 | } | |
270 | case VIDIOC_G_CTRL: | |
271 | { | |
272 | struct v4l2_control *ctrl= arg; | |
273 | ||
274 | switch (ctrl->id) { | |
275 | case V4L2_CID_AUDIO_MUTE: | |
276 | ctrl->value=card->muted; | |
277 | return (0); | |
278 | } | |
279 | return -EINVAL; | |
1da177e4 | 280 | } |
e84fef6b MCC |
281 | case VIDIOC_S_CTRL: |
282 | { | |
283 | struct v4l2_control *ctrl= arg; | |
284 | ||
285 | switch (ctrl->id) { | |
286 | case V4L2_CID_AUDIO_MUTE: | |
287 | card->muted = ctrl->value; | |
288 | if(card->muted) | |
289 | turn_power(card->io, 0); | |
290 | else | |
291 | set_freq(card->io, FREQ2BITS(card->freq)); | |
292 | return 0; | |
293 | } | |
294 | return -EINVAL; | |
295 | } | |
296 | ||
297 | default: | |
298 | return v4l_compat_translate_ioctl(inode,file,cmd,arg, | |
299 | radio_function); | |
300 | ||
1da177e4 LT |
301 | } |
302 | } | |
303 | ||
304 | static int radio_ioctl(struct inode *inode, struct file *file, | |
305 | unsigned int cmd, unsigned long arg) | |
306 | { | |
307 | struct video_device *dev = video_devdata(file); | |
308 | struct radio_device *card=dev->priv; | |
309 | int ret; | |
4286c6f6 | 310 | |
3593cab5 | 311 | mutex_lock(&card->lock); |
1da177e4 | 312 | ret = video_usercopy(inode, file, cmd, arg, radio_function); |
3593cab5 | 313 | mutex_unlock(&card->lock); |
1da177e4 LT |
314 | return ret; |
315 | } | |
316 | ||
317 | MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); | |
318 | MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); | |
319 | MODULE_LICENSE("GPL"); | |
320 | ||
321 | ||
322 | static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | |
323 | { | |
324 | if(!request_region(pci_resource_start(pdev, 0), | |
4286c6f6 MCC |
325 | pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { |
326 | printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n"); | |
327 | goto err_out; | |
1da177e4 LT |
328 | } |
329 | ||
330 | if (pci_enable_device(pdev)) | |
4286c6f6 | 331 | goto err_out_free_region; |
1da177e4 LT |
332 | |
333 | radio_unit.io = pci_resource_start(pdev, 0); | |
3593cab5 | 334 | mutex_init(&radio_unit.lock); |
1da177e4 LT |
335 | maxiradio_radio.priv = &radio_unit; |
336 | ||
337 | if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) { | |
4286c6f6 MCC |
338 | printk("radio-maxiradio: can't register device!"); |
339 | goto err_out_free_region; | |
1da177e4 LT |
340 | } |
341 | ||
342 | printk(KERN_INFO "radio-maxiradio: version " | |
343 | DRIVER_VERSION | |
344 | " time " | |
345 | __TIME__ " " | |
346 | __DATE__ | |
347 | "\n"); | |
348 | ||
349 | printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n", | |
350 | radio_unit.io); | |
351 | return 0; | |
352 | ||
353 | err_out_free_region: | |
354 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | |
355 | err_out: | |
356 | return -ENODEV; | |
357 | } | |
358 | ||
359 | static void __devexit maxiradio_remove_one(struct pci_dev *pdev) | |
360 | { | |
361 | video_unregister_device(&maxiradio_radio); | |
362 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | |
363 | } | |
364 | ||
365 | static struct pci_device_id maxiradio_pci_tbl[] = { | |
366 | { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, | |
367 | PCI_ANY_ID, PCI_ANY_ID, }, | |
368 | { 0,} | |
369 | }; | |
370 | ||
371 | MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); | |
372 | ||
373 | static struct pci_driver maxiradio_driver = { | |
374 | .name = "radio-maxiradio", | |
375 | .id_table = maxiradio_pci_tbl, | |
376 | .probe = maxiradio_init_one, | |
377 | .remove = __devexit_p(maxiradio_remove_one), | |
378 | }; | |
379 | ||
380 | static int __init maxiradio_radio_init(void) | |
381 | { | |
9bfab8ce | 382 | return pci_register_driver(&maxiradio_driver); |
1da177e4 LT |
383 | } |
384 | ||
385 | static void __exit maxiradio_radio_exit(void) | |
386 | { | |
387 | pci_unregister_driver(&maxiradio_driver); | |
388 | } | |
389 | ||
390 | module_init(maxiradio_radio_init); | |
391 | module_exit(maxiradio_radio_exit); |