1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * driver/media/radio/radio-tea5764.c |
4 | * |
5 | * Driver for TEA5764 radio chip for linux 2.6. |
6 | * This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola. |
7 | * The I2C protocol is used for communicate with chip. |
8 | * |
9 | * Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation |
10 | * |
11 | * Copyright (c) 2008 Fabio Belavenuto <belavenuto@gmail.com> |
12 | * |
13 | * History: |
14 | * 2008-12-06 Fabio Belavenuto <belavenuto@gmail.com> |
15 | * initial code |
16 | * |
17 | * TODO: |
18 | * add platform_data support for IRQs platform dependencies |
19 | * add RDS support |
20 | */ |
21 | #include <linux/kernel.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/module.h> |
24 | #include <linux/init.h> /* Initdata */ |
25 | #include <linux/videodev2.h> /* kernel radio structs */ |
26 | #include <linux/i2c.h> /* I2C */ |
27 | #include <media/v4l2-common.h> |
28 | #include <media/v4l2-ioctl.h> |
29 | #include <media/v4l2-device.h> |
30 | #include <media/v4l2-ctrls.h> |
31 | #include <media/v4l2-event.h> |
32 | |
33 | #define DRIVER_VERSION "0.0.2" |
34 | |
35 | #define DRIVER_AUTHOR "Fabio Belavenuto <belavenuto@gmail.com>" |
36 | #define DRIVER_DESC "A driver for the TEA5764 radio chip for EZX Phones." |
37 | |
38 | #define PINFO(format, ...)\ |
39 | printk(KERN_INFO KBUILD_MODNAME ": "\ |
40 | DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) |
41 | #define PWARN(format, ...)\ |
42 | printk(KERN_WARNING KBUILD_MODNAME ": "\ |
43 | DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) |
44 | # define PDEBUG(format, ...)\ |
45 | printk(KERN_DEBUG KBUILD_MODNAME ": "\ |
46 | DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) |
47 | |
48 | /* Frequency limits in MHz -- these are European values. For Japanese |
49 | devices, that would be 76000 and 91000. */ |
50 | #define FREQ_MIN 87500U |
51 | #define FREQ_MAX 108000U |
52 | #define FREQ_MUL 16 |
53 | |
54 | /* TEA5764 registers */ |
55 | #define TEA5764_MANID 0x002b |
56 | #define TEA5764_CHIPID 0x5764 |
57 | |
58 | #define TEA5764_INTREG_BLMSK 0x0001 |
59 | #define TEA5764_INTREG_FRRMSK 0x0002 |
60 | #define TEA5764_INTREG_LEVMSK 0x0008 |
61 | #define TEA5764_INTREG_IFMSK 0x0010 |
62 | #define TEA5764_INTREG_BLMFLAG 0x0100 |
63 | #define TEA5764_INTREG_FRRFLAG 0x0200 |
64 | #define TEA5764_INTREG_LEVFLAG 0x0800 |
65 | #define TEA5764_INTREG_IFFLAG 0x1000 |
66 | |
67 | #define TEA5764_FRQSET_SUD 0x8000 |
68 | #define TEA5764_FRQSET_SM 0x4000 |
69 | |
70 | #define TEA5764_TNCTRL_PUPD1 0x8000 |
71 | #define TEA5764_TNCTRL_PUPD0 0x4000 |
72 | #define TEA5764_TNCTRL_BLIM 0x2000 |
73 | #define TEA5764_TNCTRL_SWPM 0x1000 |
74 | #define TEA5764_TNCTRL_IFCTC 0x0800 |
75 | #define TEA5764_TNCTRL_AFM 0x0400 |
76 | #define TEA5764_TNCTRL_SMUTE 0x0200 |
77 | #define TEA5764_TNCTRL_SNC 0x0100 |
78 | #define TEA5764_TNCTRL_MU 0x0080 |
79 | #define TEA5764_TNCTRL_SSL1 0x0040 |
80 | #define TEA5764_TNCTRL_SSL0 0x0020 |
81 | #define TEA5764_TNCTRL_HLSI 0x0010 |
82 | #define TEA5764_TNCTRL_MST 0x0008 |
83 | #define TEA5764_TNCTRL_SWP 0x0004 |
84 | #define TEA5764_TNCTRL_DTC 0x0002 |
85 | #define TEA5764_TNCTRL_AHLSI 0x0001 |
86 | |
87 | #define TEA5764_TUNCHK_LEVEL(x) (((x) & 0x00F0) >> 4) |
88 | #define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9) |
89 | #define TEA5764_TUNCHK_TUNTO 0x0100 |
90 | #define TEA5764_TUNCHK_LD 0x0008 |
91 | #define TEA5764_TUNCHK_STEREO 0x0004 |
92 | |
93 | #define TEA5764_TESTREG_TRIGFR 0x0800 |
94 | |
95 | struct tea5764_regs { |
96 | u16 intreg; /* INTFLAG & INTMSK */ |
97 | u16 frqset; /* FRQSETMSB & FRQSETLSB */ |
98 | u16 tnctrl; /* TNCTRL1 & TNCTRL2 */ |
99 | u16 frqchk; /* FRQCHKMSB & FRQCHKLSB */ |
100 | u16 tunchk; /* IFCHK & LEVCHK */ |
101 | u16 testreg; /* TESTBITS & TESTMODE */ |
102 | u16 rdsstat; /* RDSSTAT1 & RDSSTAT2 */ |
103 | u16 rdslb; /* RDSLBMSB & RDSLBLSB */ |
104 | u16 rdspb; /* RDSPBMSB & RDSPBLSB */ |
105 | u16 rdsbc; /* RDSBBC & RDSGBC */ |
106 | u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ |
107 | u16 rdsbbl; /* PAUSEDET & RDSBBL */ |
108 | u16 manid; /* MANID1 & MANID2 */ |
109 | u16 chipid; /* CHIPID1 & CHIPID2 */ |
110 | } __attribute__ ((packed)); |
111 | |
112 | struct tea5764_write_regs { |
113 | u8 intreg; /* INTMSK */ |
114 | __be16 frqset; /* FRQSETMSB & FRQSETLSB */ |
115 | __be16 tnctrl; /* TNCTRL1 & TNCTRL2 */ |
116 | __be16 testreg; /* TESTBITS & TESTMODE */ |
117 | __be16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ |
118 | __be16 rdsbbl; /* PAUSEDET & RDSBBL */ |
119 | } __attribute__ ((packed)); |
120 | |
121 | #ifdef CONFIG_RADIO_TEA5764_XTAL |
122 | #define RADIO_TEA5764_XTAL 1 |
123 | #else |
124 | #define RADIO_TEA5764_XTAL 0 |
125 | #endif |
126 | |
127 | static int radio_nr = -1; |
128 | static int use_xtal = RADIO_TEA5764_XTAL; |
129 | |
130 | struct tea5764_device { |
131 | struct v4l2_device v4l2_dev; |
132 | struct v4l2_ctrl_handler ctrl_handler; |
133 | struct i2c_client *i2c_client; |
134 | struct video_device vdev; |
135 | struct tea5764_regs regs; |
136 | struct mutex mutex; |
137 | }; |
138 | |
139 | /* I2C code related */ |
140 | static int tea5764_i2c_read(struct tea5764_device *radio) |
141 | { |
142 | int i; |
143 | u16 *p = (u16 *) &radio->regs; |
144 | |
145 | struct i2c_msg msgs[1] = { |
146 | { .addr = radio->i2c_client->addr, |
147 | .flags = I2C_M_RD, |
148 | .len = sizeof(radio->regs), |
149 | .buf = (void *)&radio->regs |
150 | }, |
151 | }; |
152 | if (i2c_transfer(adap: radio->i2c_client->adapter, msgs, num: 1) != 1) |
153 | return -EIO; |
154 | for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++) |
155 | p[i] = __be16_to_cpu((__force __be16)p[i]); |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | static int tea5764_i2c_write(struct tea5764_device *radio) |
161 | { |
162 | struct tea5764_write_regs wr; |
163 | struct tea5764_regs *r = &radio->regs; |
164 | struct i2c_msg msgs[1] = { |
165 | { |
166 | .addr = radio->i2c_client->addr, |
167 | .len = sizeof(wr), |
168 | .buf = (void *)&wr |
169 | }, |
170 | }; |
171 | wr.intreg = r->intreg & 0xff; |
172 | wr.frqset = __cpu_to_be16(r->frqset); |
173 | wr.tnctrl = __cpu_to_be16(r->tnctrl); |
174 | wr.testreg = __cpu_to_be16(r->testreg); |
175 | wr.rdsctrl = __cpu_to_be16(r->rdsctrl); |
176 | wr.rdsbbl = __cpu_to_be16(r->rdsbbl); |
177 | if (i2c_transfer(adap: radio->i2c_client->adapter, msgs, num: 1) != 1) |
178 | return -EIO; |
179 | return 0; |
180 | } |
181 | |
182 | static void tea5764_power_up(struct tea5764_device *radio) |
183 | { |
184 | struct tea5764_regs *r = &radio->regs; |
185 | |
186 | if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) { |
187 | r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU | |
188 | TEA5764_TNCTRL_HLSI); |
189 | if (!use_xtal) |
190 | r->testreg |= TEA5764_TESTREG_TRIGFR; |
191 | else |
192 | r->testreg &= ~TEA5764_TESTREG_TRIGFR; |
193 | |
194 | r->tnctrl |= TEA5764_TNCTRL_PUPD0; |
195 | tea5764_i2c_write(radio); |
196 | } |
197 | } |
198 | |
199 | static void tea5764_power_down(struct tea5764_device *radio) |
200 | { |
201 | struct tea5764_regs *r = &radio->regs; |
202 | |
203 | if (r->tnctrl & TEA5764_TNCTRL_PUPD0) { |
204 | r->tnctrl &= ~TEA5764_TNCTRL_PUPD0; |
205 | tea5764_i2c_write(radio); |
206 | } |
207 | } |
208 | |
209 | static void tea5764_set_freq(struct tea5764_device *radio, int freq) |
210 | { |
211 | struct tea5764_regs *r = &radio->regs; |
212 | |
213 | /* formula: (freq [+ or -] 225000) / 8192 */ |
214 | if (r->tnctrl & TEA5764_TNCTRL_HLSI) |
215 | r->frqset = (freq + 225000) / 8192; |
216 | else |
217 | r->frqset = (freq - 225000) / 8192; |
218 | } |
219 | |
220 | static int tea5764_get_freq(struct tea5764_device *radio) |
221 | { |
222 | struct tea5764_regs *r = &radio->regs; |
223 | |
224 | if (r->tnctrl & TEA5764_TNCTRL_HLSI) |
225 | return (r->frqchk * 8192) - 225000; |
226 | else |
227 | return (r->frqchk * 8192) + 225000; |
228 | } |
229 | |
230 | /* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ |
231 | static void tea5764_tune(struct tea5764_device *radio, int freq) |
232 | { |
233 | tea5764_set_freq(radio, freq); |
234 | if (tea5764_i2c_write(radio)) |
235 | PWARN("Could not set frequency!" ); |
236 | } |
237 | |
238 | static void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode) |
239 | { |
240 | struct tea5764_regs *r = &radio->regs; |
241 | int tnctrl = r->tnctrl; |
242 | |
243 | if (audmode == V4L2_TUNER_MODE_MONO) |
244 | r->tnctrl |= TEA5764_TNCTRL_MST; |
245 | else |
246 | r->tnctrl &= ~TEA5764_TNCTRL_MST; |
247 | if (tnctrl != r->tnctrl) |
248 | tea5764_i2c_write(radio); |
249 | } |
250 | |
251 | static int tea5764_get_audout_mode(struct tea5764_device *radio) |
252 | { |
253 | struct tea5764_regs *r = &radio->regs; |
254 | |
255 | if (r->tnctrl & TEA5764_TNCTRL_MST) |
256 | return V4L2_TUNER_MODE_MONO; |
257 | else |
258 | return V4L2_TUNER_MODE_STEREO; |
259 | } |
260 | |
261 | static void tea5764_mute(struct tea5764_device *radio, int on) |
262 | { |
263 | struct tea5764_regs *r = &radio->regs; |
264 | int tnctrl = r->tnctrl; |
265 | |
266 | if (on) |
267 | r->tnctrl |= TEA5764_TNCTRL_MU; |
268 | else |
269 | r->tnctrl &= ~TEA5764_TNCTRL_MU; |
270 | if (tnctrl != r->tnctrl) |
271 | tea5764_i2c_write(radio); |
272 | } |
273 | |
274 | /* V4L2 vidioc */ |
275 | static int vidioc_querycap(struct file *file, void *priv, |
276 | struct v4l2_capability *v) |
277 | { |
278 | struct tea5764_device *radio = video_drvdata(file); |
279 | struct video_device *dev = &radio->vdev; |
280 | |
281 | strscpy(v->driver, dev->dev.driver->name, sizeof(v->driver)); |
282 | strscpy(v->card, dev->name, sizeof(v->card)); |
283 | snprintf(buf: v->bus_info, size: sizeof(v->bus_info), |
284 | fmt: "I2C:%s" , dev_name(dev: &dev->dev)); |
285 | return 0; |
286 | } |
287 | |
288 | static int vidioc_g_tuner(struct file *file, void *priv, |
289 | struct v4l2_tuner *v) |
290 | { |
291 | struct tea5764_device *radio = video_drvdata(file); |
292 | struct tea5764_regs *r = &radio->regs; |
293 | |
294 | if (v->index > 0) |
295 | return -EINVAL; |
296 | |
297 | strscpy(v->name, "FM" , sizeof(v->name)); |
298 | v->type = V4L2_TUNER_RADIO; |
299 | tea5764_i2c_read(radio); |
300 | v->rangelow = FREQ_MIN * FREQ_MUL; |
301 | v->rangehigh = FREQ_MAX * FREQ_MUL; |
302 | v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; |
303 | if (r->tunchk & TEA5764_TUNCHK_STEREO) |
304 | v->rxsubchans = V4L2_TUNER_SUB_STEREO; |
305 | else |
306 | v->rxsubchans = V4L2_TUNER_SUB_MONO; |
307 | v->audmode = tea5764_get_audout_mode(radio); |
308 | v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf; |
309 | v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk); |
310 | |
311 | return 0; |
312 | } |
313 | |
314 | static int vidioc_s_tuner(struct file *file, void *priv, |
315 | const struct v4l2_tuner *v) |
316 | { |
317 | struct tea5764_device *radio = video_drvdata(file); |
318 | |
319 | if (v->index > 0) |
320 | return -EINVAL; |
321 | |
322 | tea5764_set_audout_mode(radio, audmode: v->audmode); |
323 | return 0; |
324 | } |
325 | |
326 | static int vidioc_s_frequency(struct file *file, void *priv, |
327 | const struct v4l2_frequency *f) |
328 | { |
329 | struct tea5764_device *radio = video_drvdata(file); |
330 | unsigned freq = f->frequency; |
331 | |
332 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) |
333 | return -EINVAL; |
334 | if (freq == 0) { |
335 | /* We special case this as a power down control. */ |
336 | tea5764_power_down(radio); |
337 | /* Yes, that's what is returned in this case. This |
338 | whole special case is non-compliant and should really |
339 | be replaced with something better, but changing this |
340 | might well break code that depends on this behavior. |
341 | So we keep it as-is. */ |
342 | return -EINVAL; |
343 | } |
344 | freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); |
345 | tea5764_power_up(radio); |
346 | tea5764_tune(radio, freq: (freq * 125) / 2); |
347 | return 0; |
348 | } |
349 | |
350 | static int vidioc_g_frequency(struct file *file, void *priv, |
351 | struct v4l2_frequency *f) |
352 | { |
353 | struct tea5764_device *radio = video_drvdata(file); |
354 | struct tea5764_regs *r = &radio->regs; |
355 | |
356 | if (f->tuner != 0) |
357 | return -EINVAL; |
358 | tea5764_i2c_read(radio); |
359 | f->type = V4L2_TUNER_RADIO; |
360 | if (r->tnctrl & TEA5764_TNCTRL_PUPD0) |
361 | f->frequency = (tea5764_get_freq(radio) * 2) / 125; |
362 | else |
363 | f->frequency = 0; |
364 | |
365 | return 0; |
366 | } |
367 | |
368 | static int tea5764_s_ctrl(struct v4l2_ctrl *ctrl) |
369 | { |
370 | struct tea5764_device *radio = |
371 | container_of(ctrl->handler, struct tea5764_device, ctrl_handler); |
372 | |
373 | switch (ctrl->id) { |
374 | case V4L2_CID_AUDIO_MUTE: |
375 | tea5764_mute(radio, on: ctrl->val); |
376 | return 0; |
377 | } |
378 | return -EINVAL; |
379 | } |
380 | |
381 | static const struct v4l2_ctrl_ops tea5764_ctrl_ops = { |
382 | .s_ctrl = tea5764_s_ctrl, |
383 | }; |
384 | |
385 | /* File system interface */ |
386 | static const struct v4l2_file_operations tea5764_fops = { |
387 | .owner = THIS_MODULE, |
388 | .open = v4l2_fh_open, |
389 | .release = v4l2_fh_release, |
390 | .poll = v4l2_ctrl_poll, |
391 | .unlocked_ioctl = video_ioctl2, |
392 | }; |
393 | |
394 | static const struct v4l2_ioctl_ops tea5764_ioctl_ops = { |
395 | .vidioc_querycap = vidioc_querycap, |
396 | .vidioc_g_tuner = vidioc_g_tuner, |
397 | .vidioc_s_tuner = vidioc_s_tuner, |
398 | .vidioc_g_frequency = vidioc_g_frequency, |
399 | .vidioc_s_frequency = vidioc_s_frequency, |
400 | .vidioc_log_status = v4l2_ctrl_log_status, |
401 | .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
402 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
403 | }; |
404 | |
405 | /* V4L2 interface */ |
406 | static const struct video_device tea5764_radio_template = { |
407 | .name = "TEA5764 FM-Radio" , |
408 | .fops = &tea5764_fops, |
409 | .ioctl_ops = &tea5764_ioctl_ops, |
410 | .release = video_device_release_empty, |
411 | }; |
412 | |
413 | /* I2C probe: check if the device exists and register with v4l if it is */ |
414 | static int tea5764_i2c_probe(struct i2c_client *client) |
415 | { |
416 | struct tea5764_device *radio; |
417 | struct v4l2_device *v4l2_dev; |
418 | struct v4l2_ctrl_handler *hdl; |
419 | struct tea5764_regs *r; |
420 | int ret; |
421 | |
422 | PDEBUG("probe" ); |
423 | radio = kzalloc(size: sizeof(struct tea5764_device), GFP_KERNEL); |
424 | if (!radio) |
425 | return -ENOMEM; |
426 | |
427 | v4l2_dev = &radio->v4l2_dev; |
428 | ret = v4l2_device_register(dev: &client->dev, v4l2_dev); |
429 | if (ret < 0) { |
430 | v4l2_err(v4l2_dev, "could not register v4l2_device\n" ); |
431 | goto errfr; |
432 | } |
433 | |
434 | hdl = &radio->ctrl_handler; |
435 | v4l2_ctrl_handler_init(hdl, 1); |
436 | v4l2_ctrl_new_std(hdl, ops: &tea5764_ctrl_ops, |
437 | V4L2_CID_AUDIO_MUTE, min: 0, max: 1, step: 1, def: 1); |
438 | v4l2_dev->ctrl_handler = hdl; |
439 | if (hdl->error) { |
440 | ret = hdl->error; |
441 | v4l2_err(v4l2_dev, "Could not register controls\n" ); |
442 | goto errunreg; |
443 | } |
444 | |
445 | mutex_init(&radio->mutex); |
446 | radio->i2c_client = client; |
447 | ret = tea5764_i2c_read(radio); |
448 | if (ret) |
449 | goto errunreg; |
450 | r = &radio->regs; |
451 | PDEBUG("chipid = %04X, manid = %04X" , r->chipid, r->manid); |
452 | if (r->chipid != TEA5764_CHIPID || |
453 | (r->manid & 0x0fff) != TEA5764_MANID) { |
454 | PWARN("This chip is not a TEA5764!" ); |
455 | ret = -EINVAL; |
456 | goto errunreg; |
457 | } |
458 | |
459 | radio->vdev = tea5764_radio_template; |
460 | |
461 | i2c_set_clientdata(client, data: radio); |
462 | video_set_drvdata(vdev: &radio->vdev, data: radio); |
463 | radio->vdev.lock = &radio->mutex; |
464 | radio->vdev.v4l2_dev = v4l2_dev; |
465 | radio->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; |
466 | |
467 | /* initialize and power off the chip */ |
468 | tea5764_i2c_read(radio); |
469 | tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO); |
470 | tea5764_mute(radio, on: 1); |
471 | tea5764_power_down(radio); |
472 | |
473 | ret = video_register_device(vdev: &radio->vdev, type: VFL_TYPE_RADIO, nr: radio_nr); |
474 | if (ret < 0) { |
475 | PWARN("Could not register video device!" ); |
476 | goto errunreg; |
477 | } |
478 | |
479 | PINFO("registered." ); |
480 | return 0; |
481 | errunreg: |
482 | v4l2_ctrl_handler_free(hdl); |
483 | v4l2_device_unregister(v4l2_dev); |
484 | errfr: |
485 | kfree(objp: radio); |
486 | return ret; |
487 | } |
488 | |
489 | static void tea5764_i2c_remove(struct i2c_client *client) |
490 | { |
491 | struct tea5764_device *radio = i2c_get_clientdata(client); |
492 | |
493 | PDEBUG("remove" ); |
494 | if (radio) { |
495 | tea5764_power_down(radio); |
496 | video_unregister_device(vdev: &radio->vdev); |
497 | v4l2_ctrl_handler_free(hdl: &radio->ctrl_handler); |
498 | v4l2_device_unregister(v4l2_dev: &radio->v4l2_dev); |
499 | kfree(objp: radio); |
500 | } |
501 | } |
502 | |
503 | /* I2C subsystem interface */ |
504 | static const struct i2c_device_id tea5764_id[] = { |
505 | { "radio-tea5764" , 0 }, |
506 | { } /* Terminating entry */ |
507 | }; |
508 | MODULE_DEVICE_TABLE(i2c, tea5764_id); |
509 | |
510 | static struct i2c_driver tea5764_i2c_driver = { |
511 | .driver = { |
512 | .name = "radio-tea5764" , |
513 | }, |
514 | .probe = tea5764_i2c_probe, |
515 | .remove = tea5764_i2c_remove, |
516 | .id_table = tea5764_id, |
517 | }; |
518 | |
519 | module_i2c_driver(tea5764_i2c_driver); |
520 | |
521 | MODULE_AUTHOR(DRIVER_AUTHOR); |
522 | MODULE_DESCRIPTION(DRIVER_DESC); |
523 | MODULE_LICENSE("GPL" ); |
524 | MODULE_VERSION(DRIVER_VERSION); |
525 | |
526 | module_param(use_xtal, int, 0); |
527 | MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board" ); |
528 | module_param(radio_nr, int, 0); |
529 | MODULE_PARM_DESC(radio_nr, "video4linux device number to use" ); |
530 | |