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 | * | |
06470ed6 MCC |
30 | * (c) 2006, 2007 by Mauro Carvalho Chehab <mchehab@infradead.org>: |
31 | * - Conversion to V4L2 API | |
32 | * - Uses video_ioctl2 for parsing and to add debug support | |
1da177e4 LT |
33 | */ |
34 | ||
35 | ||
36 | #include <linux/module.h> | |
37 | #include <linux/init.h> | |
38 | #include <linux/ioport.h> | |
39 | #include <linux/delay.h> | |
3593cab5 | 40 | #include <linux/mutex.h> |
1da177e4 | 41 | #include <linux/pci.h> |
e84fef6b | 42 | #include <linux/videodev2.h> |
2710e6aa HV |
43 | #include <linux/version.h> /* for KERNEL_VERSION MACRO */ |
44 | #include <linux/io.h> | |
2710e6aa | 45 | #include <media/v4l2-device.h> |
35ea11ff | 46 | #include <media/v4l2-ioctl.h> |
1da177e4 | 47 | |
2710e6aa HV |
48 | MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); |
49 | MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); | |
50 | MODULE_LICENSE("GPL"); | |
51 | ||
52 | static int radio_nr = -1; | |
53 | module_param(radio_nr, int, 0); | |
54 | ||
55 | static int debug; | |
56 | ||
57 | module_param(debug, int, 0644); | |
58 | MODULE_PARM_DESC(debug, "activates debug info"); | |
59 | ||
f1557ceb | 60 | #define DRIVER_VERSION "0.77" |
e84fef6b | 61 | |
2710e6aa HV |
62 | #define RADIO_VERSION KERNEL_VERSION(0, 7, 7) |
63 | ||
64 | #define dprintk(dev, num, fmt, arg...) \ | |
65 | v4l2_dbg(num, debug, &dev->v4l2_dev, fmt, ## arg) | |
1da177e4 LT |
66 | |
67 | #ifndef PCI_VENDOR_ID_GUILLEMOT | |
68 | #define PCI_VENDOR_ID_GUILLEMOT 0x5046 | |
69 | #endif | |
70 | ||
71 | #ifndef PCI_DEVICE_ID_GUILLEMOT | |
72 | #define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 | |
73 | #endif | |
74 | ||
75 | ||
76 | /* TEA5757 pin mappings */ | |
2710e6aa | 77 | static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16; |
1da177e4 | 78 | |
2710e6aa HV |
79 | #define FREQ_LO (50 * 16000) |
80 | #define FREQ_HI (150 * 16000) | |
1da177e4 LT |
81 | |
82 | #define FREQ_IF 171200 /* 10.7*16000 */ | |
83 | #define FREQ_STEP 200 /* 12.5*16 */ | |
84 | ||
f1557ceb | 85 | /* (x==fmhz*16*1000) -> bits */ |
2710e6aa HV |
86 | #define FREQ2BITS(x) \ |
87 | ((((unsigned int)(x) + FREQ_IF + (FREQ_STEP << 1)) / (FREQ_STEP << 2)) << 2) | |
1da177e4 LT |
88 | |
89 | #define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) | |
90 | ||
91 | ||
2710e6aa | 92 | struct maxiradio |
3ca685aa | 93 | { |
2710e6aa HV |
94 | struct v4l2_device v4l2_dev; |
95 | struct video_device vdev; | |
96 | struct pci_dev *pdev; | |
1da177e4 | 97 | |
2710e6aa HV |
98 | u16 io; /* base of radio io */ |
99 | u16 muted; /* VIDEO_AUDIO_MUTE */ | |
100 | u16 stereo; /* VIDEO_TUNER_STEREO_ON */ | |
101 | u16 tuned; /* signal strength (0 or 0xffff) */ | |
4286c6f6 | 102 | |
1da177e4 | 103 | unsigned long freq; |
4286c6f6 | 104 | |
3593cab5 | 105 | struct mutex lock; |
712642b8 | 106 | }; |
1da177e4 | 107 | |
2710e6aa | 108 | static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) |
1da177e4 | 109 | { |
2710e6aa | 110 | return container_of(v4l2_dev, struct maxiradio, v4l2_dev); |
1da177e4 LT |
111 | } |
112 | ||
2710e6aa HV |
113 | static void outbit(unsigned long bit, u16 io) |
114 | { | |
115 | int val = power | wren | (bit ? data : 0); | |
116 | ||
117 | outb(val, io); | |
118 | udelay(4); | |
119 | outb(val | clk, io); | |
120 | udelay(4); | |
121 | outb(val, io); | |
122 | udelay(4); | |
123 | } | |
124 | ||
125 | static void turn_power(struct maxiradio *dev, int p) | |
1da177e4 | 126 | { |
f1557ceb | 127 | if (p != 0) { |
2710e6aa HV |
128 | dprintk(dev, 1, "Radio powered on\n"); |
129 | outb(power, dev->io); | |
f1557ceb | 130 | } else { |
2710e6aa HV |
131 | dprintk(dev, 1, "Radio powered off\n"); |
132 | outb(0, dev->io); | |
f1557ceb | 133 | } |
1da177e4 LT |
134 | } |
135 | ||
2710e6aa | 136 | static void set_freq(struct maxiradio *dev, u32 freq) |
1da177e4 LT |
137 | { |
138 | unsigned long int si; | |
139 | int bl; | |
2710e6aa | 140 | int io = dev->io; |
c6eb8eaf | 141 | int val = FREQ2BITS(freq); |
4286c6f6 | 142 | |
1da177e4 LT |
143 | /* TEA5757 shift register bits (see pdf) */ |
144 | ||
c6eb8eaf HV |
145 | outbit(0, io); /* 24 search */ |
146 | outbit(1, io); /* 23 search up/down */ | |
4286c6f6 | 147 | |
c6eb8eaf | 148 | outbit(0, io); /* 22 stereo/mono */ |
1da177e4 | 149 | |
c6eb8eaf HV |
150 | outbit(0, io); /* 21 band */ |
151 | outbit(0, io); /* 20 band (only 00=FM works I think) */ | |
1da177e4 | 152 | |
c6eb8eaf HV |
153 | outbit(0, io); /* 19 port ? */ |
154 | outbit(0, io); /* 18 port ? */ | |
4286c6f6 | 155 | |
c6eb8eaf HV |
156 | outbit(0, io); /* 17 search level */ |
157 | outbit(0, io); /* 16 search level */ | |
4286c6f6 | 158 | |
1da177e4 | 159 | si = 0x8000; |
c6eb8eaf HV |
160 | for (bl = 1; bl <= 16; bl++) { |
161 | outbit(val & si, io); | |
162 | si >>= 1; | |
f1557ceb | 163 | } |
4286c6f6 | 164 | |
2710e6aa | 165 | dprintk(dev, 1, "Radio freq set to %d.%02d MHz\n", |
f1557ceb MCC |
166 | freq / 16000, |
167 | freq % 16000 * 100 / 16000); | |
168 | ||
2710e6aa | 169 | turn_power(dev, 1); |
1da177e4 LT |
170 | } |
171 | ||
2710e6aa | 172 | static int get_stereo(u16 io) |
4286c6f6 | 173 | { |
f1557ceb MCC |
174 | outb(power,io); |
175 | udelay(4); | |
176 | ||
1da177e4 LT |
177 | return !(inb(io) & mo_st); |
178 | } | |
179 | ||
2710e6aa | 180 | static int get_tune(u16 io) |
4286c6f6 | 181 | { |
f1557ceb MCC |
182 | outb(power+clk,io); |
183 | udelay(4); | |
184 | ||
1da177e4 LT |
185 | return !(inb(io) & mo_st); |
186 | } | |
187 | ||
188 | ||
2710e6aa | 189 | static int vidioc_querycap(struct file *file, void *priv, |
06470ed6 MCC |
190 | struct v4l2_capability *v) |
191 | { | |
2710e6aa | 192 | struct maxiradio *dev = video_drvdata(file); |
06470ed6 | 193 | |
2710e6aa HV |
194 | strlcpy(v->driver, "radio-maxiradio", sizeof(v->driver)); |
195 | strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof(v->card)); | |
196 | snprintf(v->bus_info, sizeof(v->bus_info), "PCI:%s", pci_name(dev->pdev)); | |
197 | v->version = RADIO_VERSION; | |
198 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; | |
06470ed6 MCC |
199 | return 0; |
200 | } | |
201 | ||
2710e6aa | 202 | static int vidioc_g_tuner(struct file *file, void *priv, |
06470ed6 | 203 | struct v4l2_tuner *v) |
1da177e4 | 204 | { |
2710e6aa | 205 | struct maxiradio *dev = video_drvdata(file); |
1da177e4 | 206 | |
06470ed6 MCC |
207 | if (v->index > 0) |
208 | return -EINVAL; | |
e84fef6b | 209 | |
2710e6aa HV |
210 | mutex_lock(&dev->lock); |
211 | strlcpy(v->name, "FM", sizeof(v->name)); | |
06470ed6 | 212 | v->type = V4L2_TUNER_RADIO; |
2710e6aa HV |
213 | v->rangelow = FREQ_LO; |
214 | v->rangehigh = FREQ_HI; | |
215 | v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; | |
216 | v->capability = V4L2_TUNER_CAP_LOW; | |
217 | if (get_stereo(dev->io)) | |
06470ed6 MCC |
218 | v->audmode = V4L2_TUNER_MODE_STEREO; |
219 | else | |
220 | v->audmode = V4L2_TUNER_MODE_MONO; | |
2710e6aa HV |
221 | v->signal = 0xffff * get_tune(dev->io); |
222 | mutex_unlock(&dev->lock); | |
4286c6f6 | 223 | |
06470ed6 MCC |
224 | return 0; |
225 | } | |
e84fef6b | 226 | |
2710e6aa | 227 | static int vidioc_s_tuner(struct file *file, void *priv, |
06470ed6 MCC |
228 | struct v4l2_tuner *v) |
229 | { | |
2710e6aa | 230 | return v->index ? -EINVAL : 0; |
140dcc46 MCC |
231 | } |
232 | ||
a0c05ab9 MCC |
233 | static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) |
234 | { | |
235 | *i = 0; | |
236 | return 0; | |
237 | } | |
238 | ||
239 | static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) | |
240 | { | |
2710e6aa HV |
241 | return i ? -EINVAL : 0; |
242 | } | |
f1557ceb | 243 | |
2710e6aa HV |
244 | static int vidioc_g_audio(struct file *file, void *priv, |
245 | struct v4l2_audio *a) | |
246 | { | |
247 | a->index = 0; | |
248 | strlcpy(a->name, "Radio", sizeof(a->name)); | |
249 | a->capability = V4L2_AUDCAP_STEREO; | |
a0c05ab9 MCC |
250 | return 0; |
251 | } | |
252 | ||
253 | ||
2710e6aa | 254 | static int vidioc_s_audio(struct file *file, void *priv, |
140dcc46 MCC |
255 | struct v4l2_audio *a) |
256 | { | |
2710e6aa | 257 | return a->index ? -EINVAL : 0; |
140dcc46 MCC |
258 | } |
259 | ||
2710e6aa | 260 | static int vidioc_s_frequency(struct file *file, void *priv, |
06470ed6 MCC |
261 | struct v4l2_frequency *f) |
262 | { | |
2710e6aa | 263 | struct maxiradio *dev = video_drvdata(file); |
4286c6f6 | 264 | |
a3a9e287 HV |
265 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) |
266 | return -EINVAL; | |
f1557ceb | 267 | if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) { |
2710e6aa | 268 | dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n", |
f1557ceb MCC |
269 | f->frequency / 16000, |
270 | f->frequency % 16000 * 100 / 16000, | |
271 | FREQ_LO / 16000, FREQ_HI / 16000); | |
272 | ||
06470ed6 | 273 | return -EINVAL; |
f1557ceb | 274 | } |
4286c6f6 | 275 | |
2710e6aa HV |
276 | mutex_lock(&dev->lock); |
277 | dev->freq = f->frequency; | |
278 | set_freq(dev, dev->freq); | |
06470ed6 | 279 | msleep(125); |
2710e6aa | 280 | mutex_unlock(&dev->lock); |
e84fef6b | 281 | |
06470ed6 MCC |
282 | return 0; |
283 | } | |
4286c6f6 | 284 | |
2710e6aa | 285 | static int vidioc_g_frequency(struct file *file, void *priv, |
06470ed6 MCC |
286 | struct v4l2_frequency *f) |
287 | { | |
2710e6aa | 288 | struct maxiradio *dev = video_drvdata(file); |
4286c6f6 | 289 | |
a3a9e287 HV |
290 | if (f->tuner != 0) |
291 | return -EINVAL; | |
06470ed6 | 292 | f->type = V4L2_TUNER_RADIO; |
2710e6aa | 293 | f->frequency = dev->freq; |
06470ed6 | 294 | |
2710e6aa | 295 | dprintk(dev, 4, "radio freq is %d.%02d MHz", |
f1557ceb MCC |
296 | f->frequency / 16000, |
297 | f->frequency % 16000 * 100 / 16000); | |
298 | ||
06470ed6 MCC |
299 | return 0; |
300 | } | |
301 | ||
2710e6aa | 302 | static int vidioc_queryctrl(struct file *file, void *priv, |
06470ed6 MCC |
303 | struct v4l2_queryctrl *qc) |
304 | { | |
2710e6aa HV |
305 | switch (qc->id) { |
306 | case V4L2_CID_AUDIO_MUTE: | |
307 | return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); | |
06470ed6 MCC |
308 | } |
309 | return -EINVAL; | |
310 | } | |
e84fef6b | 311 | |
2710e6aa HV |
312 | static int vidioc_g_ctrl(struct file *file, void *priv, |
313 | struct v4l2_control *ctrl) | |
06470ed6 | 314 | { |
2710e6aa | 315 | struct maxiradio *dev = video_drvdata(file); |
e84fef6b | 316 | |
06470ed6 | 317 | switch (ctrl->id) { |
2710e6aa HV |
318 | case V4L2_CID_AUDIO_MUTE: |
319 | ctrl->value = dev->muted; | |
320 | return 0; | |
1da177e4 | 321 | } |
f1557ceb | 322 | |
06470ed6 | 323 | return -EINVAL; |
1da177e4 LT |
324 | } |
325 | ||
2710e6aa HV |
326 | static int vidioc_s_ctrl(struct file *file, void *priv, |
327 | struct v4l2_control *ctrl) | |
1da177e4 | 328 | { |
2710e6aa | 329 | struct maxiradio *dev = video_drvdata(file); |
4286c6f6 | 330 | |
06470ed6 | 331 | switch (ctrl->id) { |
2710e6aa HV |
332 | case V4L2_CID_AUDIO_MUTE: |
333 | mutex_lock(&dev->lock); | |
334 | dev->muted = ctrl->value; | |
335 | if (dev->muted) | |
336 | turn_power(dev, 0); | |
337 | else | |
338 | set_freq(dev, dev->freq); | |
339 | mutex_unlock(&dev->lock); | |
340 | return 0; | |
06470ed6 | 341 | } |
f1557ceb | 342 | |
06470ed6 | 343 | return -EINVAL; |
1da177e4 LT |
344 | } |
345 | ||
2710e6aa HV |
346 | static const struct v4l2_file_operations maxiradio_fops = { |
347 | .owner = THIS_MODULE, | |
2710e6aa HV |
348 | .ioctl = video_ioctl2, |
349 | }; | |
350 | ||
a399810c | 351 | static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = { |
06470ed6 MCC |
352 | .vidioc_querycap = vidioc_querycap, |
353 | .vidioc_g_tuner = vidioc_g_tuner, | |
354 | .vidioc_s_tuner = vidioc_s_tuner, | |
140dcc46 MCC |
355 | .vidioc_g_audio = vidioc_g_audio, |
356 | .vidioc_s_audio = vidioc_s_audio, | |
a0c05ab9 MCC |
357 | .vidioc_g_input = vidioc_g_input, |
358 | .vidioc_s_input = vidioc_s_input, | |
06470ed6 MCC |
359 | .vidioc_g_frequency = vidioc_g_frequency, |
360 | .vidioc_s_frequency = vidioc_s_frequency, | |
361 | .vidioc_queryctrl = vidioc_queryctrl, | |
362 | .vidioc_g_ctrl = vidioc_g_ctrl, | |
363 | .vidioc_s_ctrl = vidioc_s_ctrl, | |
06470ed6 | 364 | }; |
1da177e4 LT |
365 | |
366 | static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | |
367 | { | |
2710e6aa HV |
368 | struct maxiradio *dev; |
369 | struct v4l2_device *v4l2_dev; | |
370 | int retval = -ENOMEM; | |
371 | ||
372 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
373 | if (dev == NULL) { | |
374 | dev_err(&pdev->dev, "not enough memory\n"); | |
375 | return -ENOMEM; | |
376 | } | |
377 | ||
378 | v4l2_dev = &dev->v4l2_dev; | |
379 | mutex_init(&dev->lock); | |
380 | dev->pdev = pdev; | |
381 | dev->muted = 1; | |
382 | dev->freq = FREQ_LO; | |
383 | ||
384 | strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name)); | |
385 | ||
386 | retval = v4l2_device_register(&pdev->dev, v4l2_dev); | |
387 | if (retval < 0) { | |
388 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | |
389 | goto errfr; | |
390 | } | |
391 | ||
392 | if (!request_region(pci_resource_start(pdev, 0), | |
4286c6f6 | 393 | pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { |
2710e6aa | 394 | v4l2_err(v4l2_dev, "can't reserve I/O ports\n"); |
4286c6f6 | 395 | goto err_out; |
1da177e4 LT |
396 | } |
397 | ||
398 | if (pci_enable_device(pdev)) | |
4286c6f6 | 399 | goto err_out_free_region; |
1da177e4 | 400 | |
2710e6aa HV |
401 | dev->io = pci_resource_start(pdev, 0); |
402 | strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); | |
403 | dev->vdev.v4l2_dev = v4l2_dev; | |
404 | dev->vdev.fops = &maxiradio_fops; | |
405 | dev->vdev.ioctl_ops = &maxiradio_ioctl_ops; | |
406 | dev->vdev.release = video_device_release_empty; | |
407 | video_set_drvdata(&dev->vdev, dev); | |
1da177e4 | 408 | |
2710e6aa HV |
409 | if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { |
410 | v4l2_err(v4l2_dev, "can't register device!"); | |
4286c6f6 | 411 | goto err_out_free_region; |
1da177e4 LT |
412 | } |
413 | ||
2710e6aa HV |
414 | v4l2_info(v4l2_dev, "version " DRIVER_VERSION |
415 | " time " __TIME__ " " __DATE__ "\n"); | |
1da177e4 | 416 | |
2710e6aa HV |
417 | v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n", |
418 | dev->io); | |
1da177e4 LT |
419 | return 0; |
420 | ||
421 | err_out_free_region: | |
422 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | |
423 | err_out: | |
2710e6aa HV |
424 | v4l2_device_unregister(v4l2_dev); |
425 | errfr: | |
426 | kfree(dev); | |
1da177e4 LT |
427 | return -ENODEV; |
428 | } | |
429 | ||
430 | static void __devexit maxiradio_remove_one(struct pci_dev *pdev) | |
431 | { | |
2710e6aa HV |
432 | struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); |
433 | struct maxiradio *dev = to_maxiradio(v4l2_dev); | |
434 | ||
435 | video_unregister_device(&dev->vdev); | |
436 | v4l2_device_unregister(&dev->v4l2_dev); | |
1da177e4 LT |
437 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); |
438 | } | |
439 | ||
440 | static struct pci_device_id maxiradio_pci_tbl[] = { | |
441 | { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, | |
442 | PCI_ANY_ID, PCI_ANY_ID, }, | |
2710e6aa | 443 | { 0 } |
1da177e4 LT |
444 | }; |
445 | ||
446 | MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); | |
447 | ||
448 | static struct pci_driver maxiradio_driver = { | |
449 | .name = "radio-maxiradio", | |
450 | .id_table = maxiradio_pci_tbl, | |
451 | .probe = maxiradio_init_one, | |
452 | .remove = __devexit_p(maxiradio_remove_one), | |
453 | }; | |
454 | ||
455 | static int __init maxiradio_radio_init(void) | |
456 | { | |
9bfab8ce | 457 | return pci_register_driver(&maxiradio_driver); |
1da177e4 LT |
458 | } |
459 | ||
460 | static void __exit maxiradio_radio_exit(void) | |
461 | { | |
462 | pci_unregister_driver(&maxiradio_driver); | |
463 | } | |
464 | ||
465 | module_init(maxiradio_radio_init); | |
466 | module_exit(maxiradio_radio_exit); |