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 | |
f1557ceb | 265 | if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) { |
2710e6aa | 266 | dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n", |
f1557ceb MCC |
267 | f->frequency / 16000, |
268 | f->frequency % 16000 * 100 / 16000, | |
269 | FREQ_LO / 16000, FREQ_HI / 16000); | |
270 | ||
06470ed6 | 271 | return -EINVAL; |
f1557ceb | 272 | } |
4286c6f6 | 273 | |
2710e6aa HV |
274 | mutex_lock(&dev->lock); |
275 | dev->freq = f->frequency; | |
276 | set_freq(dev, dev->freq); | |
06470ed6 | 277 | msleep(125); |
2710e6aa | 278 | mutex_unlock(&dev->lock); |
e84fef6b | 279 | |
06470ed6 MCC |
280 | return 0; |
281 | } | |
4286c6f6 | 282 | |
2710e6aa | 283 | static int vidioc_g_frequency(struct file *file, void *priv, |
06470ed6 MCC |
284 | struct v4l2_frequency *f) |
285 | { | |
2710e6aa | 286 | struct maxiradio *dev = video_drvdata(file); |
4286c6f6 | 287 | |
06470ed6 | 288 | f->type = V4L2_TUNER_RADIO; |
2710e6aa | 289 | f->frequency = dev->freq; |
06470ed6 | 290 | |
2710e6aa | 291 | dprintk(dev, 4, "radio freq is %d.%02d MHz", |
f1557ceb MCC |
292 | f->frequency / 16000, |
293 | f->frequency % 16000 * 100 / 16000); | |
294 | ||
06470ed6 MCC |
295 | return 0; |
296 | } | |
297 | ||
2710e6aa | 298 | static int vidioc_queryctrl(struct file *file, void *priv, |
06470ed6 MCC |
299 | struct v4l2_queryctrl *qc) |
300 | { | |
2710e6aa HV |
301 | switch (qc->id) { |
302 | case V4L2_CID_AUDIO_MUTE: | |
303 | return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); | |
06470ed6 MCC |
304 | } |
305 | return -EINVAL; | |
306 | } | |
e84fef6b | 307 | |
2710e6aa HV |
308 | static int vidioc_g_ctrl(struct file *file, void *priv, |
309 | struct v4l2_control *ctrl) | |
06470ed6 | 310 | { |
2710e6aa | 311 | struct maxiradio *dev = video_drvdata(file); |
e84fef6b | 312 | |
06470ed6 | 313 | switch (ctrl->id) { |
2710e6aa HV |
314 | case V4L2_CID_AUDIO_MUTE: |
315 | ctrl->value = dev->muted; | |
316 | return 0; | |
1da177e4 | 317 | } |
f1557ceb | 318 | |
06470ed6 | 319 | return -EINVAL; |
1da177e4 LT |
320 | } |
321 | ||
2710e6aa HV |
322 | static int vidioc_s_ctrl(struct file *file, void *priv, |
323 | struct v4l2_control *ctrl) | |
1da177e4 | 324 | { |
2710e6aa | 325 | struct maxiradio *dev = video_drvdata(file); |
4286c6f6 | 326 | |
06470ed6 | 327 | switch (ctrl->id) { |
2710e6aa HV |
328 | case V4L2_CID_AUDIO_MUTE: |
329 | mutex_lock(&dev->lock); | |
330 | dev->muted = ctrl->value; | |
331 | if (dev->muted) | |
332 | turn_power(dev, 0); | |
333 | else | |
334 | set_freq(dev, dev->freq); | |
335 | mutex_unlock(&dev->lock); | |
336 | return 0; | |
06470ed6 | 337 | } |
f1557ceb | 338 | |
06470ed6 | 339 | return -EINVAL; |
1da177e4 LT |
340 | } |
341 | ||
2710e6aa HV |
342 | static const struct v4l2_file_operations maxiradio_fops = { |
343 | .owner = THIS_MODULE, | |
2710e6aa HV |
344 | .ioctl = video_ioctl2, |
345 | }; | |
346 | ||
a399810c | 347 | static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = { |
06470ed6 MCC |
348 | .vidioc_querycap = vidioc_querycap, |
349 | .vidioc_g_tuner = vidioc_g_tuner, | |
350 | .vidioc_s_tuner = vidioc_s_tuner, | |
140dcc46 MCC |
351 | .vidioc_g_audio = vidioc_g_audio, |
352 | .vidioc_s_audio = vidioc_s_audio, | |
a0c05ab9 MCC |
353 | .vidioc_g_input = vidioc_g_input, |
354 | .vidioc_s_input = vidioc_s_input, | |
06470ed6 MCC |
355 | .vidioc_g_frequency = vidioc_g_frequency, |
356 | .vidioc_s_frequency = vidioc_s_frequency, | |
357 | .vidioc_queryctrl = vidioc_queryctrl, | |
358 | .vidioc_g_ctrl = vidioc_g_ctrl, | |
359 | .vidioc_s_ctrl = vidioc_s_ctrl, | |
06470ed6 | 360 | }; |
1da177e4 LT |
361 | |
362 | static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | |
363 | { | |
2710e6aa HV |
364 | struct maxiradio *dev; |
365 | struct v4l2_device *v4l2_dev; | |
366 | int retval = -ENOMEM; | |
367 | ||
368 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
369 | if (dev == NULL) { | |
370 | dev_err(&pdev->dev, "not enough memory\n"); | |
371 | return -ENOMEM; | |
372 | } | |
373 | ||
374 | v4l2_dev = &dev->v4l2_dev; | |
375 | mutex_init(&dev->lock); | |
376 | dev->pdev = pdev; | |
377 | dev->muted = 1; | |
378 | dev->freq = FREQ_LO; | |
379 | ||
380 | strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name)); | |
381 | ||
382 | retval = v4l2_device_register(&pdev->dev, v4l2_dev); | |
383 | if (retval < 0) { | |
384 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | |
385 | goto errfr; | |
386 | } | |
387 | ||
388 | if (!request_region(pci_resource_start(pdev, 0), | |
4286c6f6 | 389 | pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { |
2710e6aa | 390 | v4l2_err(v4l2_dev, "can't reserve I/O ports\n"); |
4286c6f6 | 391 | goto err_out; |
1da177e4 LT |
392 | } |
393 | ||
394 | if (pci_enable_device(pdev)) | |
4286c6f6 | 395 | goto err_out_free_region; |
1da177e4 | 396 | |
2710e6aa HV |
397 | dev->io = pci_resource_start(pdev, 0); |
398 | strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); | |
399 | dev->vdev.v4l2_dev = v4l2_dev; | |
400 | dev->vdev.fops = &maxiradio_fops; | |
401 | dev->vdev.ioctl_ops = &maxiradio_ioctl_ops; | |
402 | dev->vdev.release = video_device_release_empty; | |
403 | video_set_drvdata(&dev->vdev, dev); | |
1da177e4 | 404 | |
2710e6aa HV |
405 | if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { |
406 | v4l2_err(v4l2_dev, "can't register device!"); | |
4286c6f6 | 407 | goto err_out_free_region; |
1da177e4 LT |
408 | } |
409 | ||
2710e6aa HV |
410 | v4l2_info(v4l2_dev, "version " DRIVER_VERSION |
411 | " time " __TIME__ " " __DATE__ "\n"); | |
1da177e4 | 412 | |
2710e6aa HV |
413 | v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n", |
414 | dev->io); | |
1da177e4 LT |
415 | return 0; |
416 | ||
417 | err_out_free_region: | |
418 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | |
419 | err_out: | |
2710e6aa HV |
420 | v4l2_device_unregister(v4l2_dev); |
421 | errfr: | |
422 | kfree(dev); | |
1da177e4 LT |
423 | return -ENODEV; |
424 | } | |
425 | ||
426 | static void __devexit maxiradio_remove_one(struct pci_dev *pdev) | |
427 | { | |
2710e6aa HV |
428 | struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); |
429 | struct maxiradio *dev = to_maxiradio(v4l2_dev); | |
430 | ||
431 | video_unregister_device(&dev->vdev); | |
432 | v4l2_device_unregister(&dev->v4l2_dev); | |
1da177e4 LT |
433 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); |
434 | } | |
435 | ||
436 | static struct pci_device_id maxiradio_pci_tbl[] = { | |
437 | { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, | |
438 | PCI_ANY_ID, PCI_ANY_ID, }, | |
2710e6aa | 439 | { 0 } |
1da177e4 LT |
440 | }; |
441 | ||
442 | MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); | |
443 | ||
444 | static struct pci_driver maxiradio_driver = { | |
445 | .name = "radio-maxiradio", | |
446 | .id_table = maxiradio_pci_tbl, | |
447 | .probe = maxiradio_init_one, | |
448 | .remove = __devexit_p(maxiradio_remove_one), | |
449 | }; | |
450 | ||
451 | static int __init maxiradio_radio_init(void) | |
452 | { | |
9bfab8ce | 453 | return pci_register_driver(&maxiradio_driver); |
1da177e4 LT |
454 | } |
455 | ||
456 | static void __exit maxiradio_radio_exit(void) | |
457 | { | |
458 | pci_unregister_driver(&maxiradio_driver); | |
459 | } | |
460 | ||
461 | module_init(maxiradio_radio_init); | |
462 | module_exit(maxiradio_radio_exit); |