Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* Terratec ActiveRadio ISA Standalone card driver for Linux radio support |
2 | * (c) 1999 R. Offermanns (rolf@offermanns.de) | |
3 | * based on the aimslab radio driver from M. Kirkwood | |
4 | * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) | |
4286c6f6 | 5 | * |
1da177e4 LT |
6 | * |
7 | * History: | |
8 | * 1999-05-21 First preview release | |
4286c6f6 | 9 | * |
1da177e4 LT |
10 | * Notes on the hardware: |
11 | * There are two "main" chips on the card: | |
12 | * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) | |
13 | * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) | |
14 | * (you can get the datasheet at the above links) | |
15 | * | |
16 | * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | |
17 | * Volume Control is done digitally | |
18 | * | |
19 | * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday | |
20 | * (as soon i have understand how to get started :) | |
21 | * If you can help me out with that, please contact me!! | |
22 | * | |
4286c6f6 | 23 | * |
55ac7b69 | 24 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> |
1da177e4 LT |
25 | */ |
26 | ||
27 | #include <linux/module.h> /* Modules */ | |
28 | #include <linux/init.h> /* Initdata */ | |
fb911ee8 | 29 | #include <linux/ioport.h> /* request_region */ |
55ac7b69 | 30 | #include <linux/videodev2.h> /* kernel radio structs */ |
5ac3d5bf HV |
31 | #include <linux/mutex.h> |
32 | #include <linux/version.h> /* for KERNEL_VERSION MACRO */ | |
33 | #include <linux/io.h> /* outb, outb_p */ | |
5ac3d5bf | 34 | #include <media/v4l2-device.h> |
35ea11ff | 35 | #include <media/v4l2-ioctl.h> |
1da177e4 | 36 | |
5ac3d5bf HV |
37 | MODULE_AUTHOR("R.OFFERMANNS & others"); |
38 | MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); | |
39 | MODULE_LICENSE("GPL"); | |
40 | ||
41 | #ifndef CONFIG_RADIO_TERRATEC_PORT | |
42 | #define CONFIG_RADIO_TERRATEC_PORT 0x590 | |
43 | #endif | |
44 | ||
45 | static int io = CONFIG_RADIO_TERRATEC_PORT; | |
46 | static int radio_nr = -1; | |
47 | ||
48 | module_param(io, int, 0); | |
49 | MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); | |
50 | module_param(radio_nr, int, 0); | |
51 | ||
52 | #define RADIO_VERSION KERNEL_VERSION(0, 0, 2) | |
55ac7b69 MCC |
53 | |
54 | static struct v4l2_queryctrl radio_qctrl[] = { | |
55 | { | |
56 | .id = V4L2_CID_AUDIO_MUTE, | |
57 | .name = "Mute", | |
58 | .minimum = 0, | |
59 | .maximum = 1, | |
60 | .default_value = 1, | |
61 | .type = V4L2_CTRL_TYPE_BOOLEAN, | |
62 | },{ | |
63 | .id = V4L2_CID_AUDIO_VOLUME, | |
64 | .name = "Volume", | |
65 | .minimum = 0, | |
66 | .maximum = 0xff, | |
67 | .step = 1, | |
68 | .default_value = 0xff, | |
69 | .type = V4L2_CTRL_TYPE_INTEGER, | |
70 | } | |
71 | }; | |
72 | ||
1da177e4 LT |
73 | #define WRT_DIS 0x00 |
74 | #define CLK_OFF 0x00 | |
75 | #define IIC_DATA 0x01 | |
76 | #define IIC_CLK 0x02 | |
77 | #define DATA 0x04 | |
78 | #define CLK_ON 0x08 | |
79 | #define WRT_EN 0x10 | |
1da177e4 | 80 | |
5ac3d5bf | 81 | struct terratec |
1da177e4 | 82 | { |
5ac3d5bf HV |
83 | struct v4l2_device v4l2_dev; |
84 | struct video_device vdev; | |
85 | int io; | |
1da177e4 LT |
86 | int curvol; |
87 | unsigned long curfreq; | |
88 | int muted; | |
5ac3d5bf | 89 | struct mutex lock; |
1da177e4 LT |
90 | }; |
91 | ||
5ac3d5bf | 92 | static struct terratec terratec_card; |
1da177e4 LT |
93 | |
94 | /* local things */ | |
95 | ||
5ac3d5bf | 96 | static void tt_write_vol(struct terratec *tt, int volume) |
1da177e4 LT |
97 | { |
98 | int i; | |
5ac3d5bf HV |
99 | |
100 | volume = volume + (volume * 32); /* change both channels */ | |
101 | mutex_lock(&tt->lock); | |
102 | for (i = 0; i < 8; i++) { | |
103 | if (volume & (0x80 >> i)) | |
104 | outb(0x80, tt->io + 1); | |
105 | else | |
106 | outb(0x00, tt->io + 1); | |
1da177e4 | 107 | } |
5ac3d5bf | 108 | mutex_unlock(&tt->lock); |
1da177e4 LT |
109 | } |
110 | ||
111 | ||
112 | ||
5ac3d5bf | 113 | static void tt_mute(struct terratec *tt) |
1da177e4 | 114 | { |
5ac3d5bf HV |
115 | tt->muted = 1; |
116 | tt_write_vol(tt, 0); | |
1da177e4 LT |
117 | } |
118 | ||
5ac3d5bf | 119 | static int tt_setvol(struct terratec *tt, int vol) |
1da177e4 | 120 | { |
5ac3d5bf HV |
121 | if (vol == tt->curvol) { /* requested volume = current */ |
122 | if (tt->muted) { /* user is unmuting the card */ | |
123 | tt->muted = 0; | |
124 | tt_write_vol(tt, vol); /* enable card */ | |
4286c6f6 | 125 | } |
1da177e4 LT |
126 | return 0; |
127 | } | |
128 | ||
5ac3d5bf HV |
129 | if (vol == 0) { /* volume = 0 means mute the card */ |
130 | tt_write_vol(tt, 0); /* "turn off card" by setting vol to 0 */ | |
131 | tt->curvol = vol; /* track the volume state! */ | |
1da177e4 LT |
132 | return 0; |
133 | } | |
134 | ||
5ac3d5bf HV |
135 | tt->muted = 0; |
136 | tt_write_vol(tt, vol); | |
137 | tt->curvol = vol; | |
1da177e4 | 138 | return 0; |
1da177e4 LT |
139 | } |
140 | ||
141 | ||
142 | /* this is the worst part in this driver */ | |
143 | /* many more or less strange things are going on here, but hey, it works :) */ | |
144 | ||
5ac3d5bf | 145 | static int tt_setfreq(struct terratec *tt, unsigned long freq1) |
4286c6f6 | 146 | { |
1da177e4 LT |
147 | int freq; |
148 | int i; | |
149 | int p; | |
150 | int temp; | |
151 | long rest; | |
1da177e4 | 152 | unsigned char buffer[25]; /* we have to bit shift 25 registers */ |
1da177e4 | 153 | |
5ac3d5bf HV |
154 | mutex_lock(&tt->lock); |
155 | ||
156 | tt->curfreq = freq1; | |
157 | ||
158 | freq = freq1 / 160; /* convert the freq. to a nice to handle value */ | |
159 | memset(buffer, 0, sizeof(buffer)); | |
160 | ||
161 | rest = freq * 10 + 10700; /* I once had understood what is going on here */ | |
1da177e4 | 162 | /* maybe some wise guy (friedhelm?) can comment this stuff */ |
5ac3d5bf HV |
163 | i = 13; |
164 | p = 10; | |
165 | temp = 102400; | |
166 | while (rest != 0) { | |
167 | if (rest % temp == rest) | |
1da177e4 | 168 | buffer[i] = 0; |
5ac3d5bf | 169 | else { |
4286c6f6 | 170 | buffer[i] = 1; |
5ac3d5bf | 171 | rest = rest - temp; |
1da177e4 LT |
172 | } |
173 | i--; | |
174 | p--; | |
5ac3d5bf | 175 | temp = temp / 2; |
b930e1d8 | 176 | } |
1da177e4 | 177 | |
5ac3d5bf HV |
178 | for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ |
179 | if (buffer[i] == 1) { | |
180 | outb(WRT_EN | DATA, tt->io); | |
181 | outb(WRT_EN | DATA | CLK_ON, tt->io); | |
182 | outb(WRT_EN | DATA, tt->io); | |
183 | } else { | |
184 | outb(WRT_EN | 0x00, tt->io); | |
185 | outb(WRT_EN | 0x00 | CLK_ON, tt->io); | |
1da177e4 LT |
186 | } |
187 | } | |
5ac3d5bf | 188 | outb(0x00, tt->io); |
4286c6f6 | 189 | |
5ac3d5bf | 190 | mutex_unlock(&tt->lock); |
4286c6f6 MCC |
191 | |
192 | return 0; | |
1da177e4 LT |
193 | } |
194 | ||
5ac3d5bf | 195 | static int tt_getsigstr(struct terratec *tt) |
1da177e4 | 196 | { |
5ac3d5bf | 197 | if (inb(tt->io) & 2) /* bit set = no signal present */ |
1da177e4 LT |
198 | return 0; |
199 | return 1; /* signal present */ | |
200 | } | |
201 | ||
1de69238 DL |
202 | static int vidioc_querycap(struct file *file, void *priv, |
203 | struct v4l2_capability *v) | |
204 | { | |
205 | strlcpy(v->driver, "radio-terratec", sizeof(v->driver)); | |
206 | strlcpy(v->card, "ActiveRadio", sizeof(v->card)); | |
5ac3d5bf | 207 | strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); |
1de69238 | 208 | v->version = RADIO_VERSION; |
5ac3d5bf | 209 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; |
1de69238 DL |
210 | return 0; |
211 | } | |
1da177e4 | 212 | |
1de69238 DL |
213 | static int vidioc_g_tuner(struct file *file, void *priv, |
214 | struct v4l2_tuner *v) | |
1da177e4 | 215 | { |
5ac3d5bf | 216 | struct terratec *tt = video_drvdata(file); |
4286c6f6 | 217 | |
1de69238 DL |
218 | if (v->index > 0) |
219 | return -EINVAL; | |
55ac7b69 | 220 | |
5ac3d5bf | 221 | strlcpy(v->name, "FM", sizeof(v->name)); |
1de69238 | 222 | v->type = V4L2_TUNER_RADIO; |
5ac3d5bf HV |
223 | v->rangelow = 87 * 16000; |
224 | v->rangehigh = 108 * 16000; | |
1de69238 DL |
225 | v->rxsubchans = V4L2_TUNER_SUB_MONO; |
226 | v->capability = V4L2_TUNER_CAP_LOW; | |
227 | v->audmode = V4L2_TUNER_MODE_MONO; | |
5ac3d5bf | 228 | v->signal = 0xFFFF * tt_getsigstr(tt); |
1de69238 DL |
229 | return 0; |
230 | } | |
55ac7b69 | 231 | |
1de69238 DL |
232 | static int vidioc_s_tuner(struct file *file, void *priv, |
233 | struct v4l2_tuner *v) | |
234 | { | |
5ac3d5bf | 235 | return v->index ? -EINVAL : 0; |
1de69238 | 236 | } |
55ac7b69 | 237 | |
1de69238 DL |
238 | static int vidioc_s_frequency(struct file *file, void *priv, |
239 | struct v4l2_frequency *f) | |
240 | { | |
5ac3d5bf | 241 | struct terratec *tt = video_drvdata(file); |
55ac7b69 | 242 | |
a3a9e287 HV |
243 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) |
244 | return -EINVAL; | |
5ac3d5bf | 245 | tt_setfreq(tt, f->frequency); |
1de69238 DL |
246 | return 0; |
247 | } | |
55ac7b69 | 248 | |
1de69238 DL |
249 | static int vidioc_g_frequency(struct file *file, void *priv, |
250 | struct v4l2_frequency *f) | |
251 | { | |
5ac3d5bf | 252 | struct terratec *tt = video_drvdata(file); |
55ac7b69 | 253 | |
a3a9e287 HV |
254 | if (f->tuner != 0) |
255 | return -EINVAL; | |
1de69238 DL |
256 | f->type = V4L2_TUNER_RADIO; |
257 | f->frequency = tt->curfreq; | |
258 | return 0; | |
259 | } | |
55ac7b69 | 260 | |
1de69238 DL |
261 | static int vidioc_queryctrl(struct file *file, void *priv, |
262 | struct v4l2_queryctrl *qc) | |
263 | { | |
264 | int i; | |
55ac7b69 | 265 | |
1de69238 DL |
266 | for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { |
267 | if (qc->id && qc->id == radio_qctrl[i].id) { | |
5ac3d5bf | 268 | memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); |
1da177e4 LT |
269 | return 0; |
270 | } | |
1de69238 DL |
271 | } |
272 | return -EINVAL; | |
273 | } | |
55ac7b69 | 274 | |
1de69238 DL |
275 | static int vidioc_g_ctrl(struct file *file, void *priv, |
276 | struct v4l2_control *ctrl) | |
277 | { | |
5ac3d5bf | 278 | struct terratec *tt = video_drvdata(file); |
55ac7b69 | 279 | |
1de69238 DL |
280 | switch (ctrl->id) { |
281 | case V4L2_CID_AUDIO_MUTE: | |
282 | if (tt->muted) | |
283 | ctrl->value = 1; | |
284 | else | |
285 | ctrl->value = 0; | |
286 | return 0; | |
287 | case V4L2_CID_AUDIO_VOLUME: | |
288 | ctrl->value = tt->curvol * 6554; | |
289 | return 0; | |
290 | } | |
291 | return -EINVAL; | |
292 | } | |
55ac7b69 | 293 | |
1de69238 DL |
294 | static int vidioc_s_ctrl(struct file *file, void *priv, |
295 | struct v4l2_control *ctrl) | |
296 | { | |
5ac3d5bf | 297 | struct terratec *tt = video_drvdata(file); |
1de69238 DL |
298 | |
299 | switch (ctrl->id) { | |
300 | case V4L2_CID_AUDIO_MUTE: | |
301 | if (ctrl->value) | |
302 | tt_mute(tt); | |
303 | else | |
304 | tt_setvol(tt,tt->curvol); | |
305 | return 0; | |
306 | case V4L2_CID_AUDIO_VOLUME: | |
307 | tt_setvol(tt,ctrl->value); | |
308 | return 0; | |
1da177e4 | 309 | } |
1de69238 DL |
310 | return -EINVAL; |
311 | } | |
312 | ||
1de69238 DL |
313 | static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) |
314 | { | |
315 | *i = 0; | |
316 | return 0; | |
1da177e4 LT |
317 | } |
318 | ||
1de69238 | 319 | static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) |
1da177e4 | 320 | { |
5ac3d5bf | 321 | return i ? -EINVAL : 0; |
1de69238 DL |
322 | } |
323 | ||
5ac3d5bf | 324 | static int vidioc_g_audio(struct file *file, void *priv, |
1de69238 DL |
325 | struct v4l2_audio *a) |
326 | { | |
5ac3d5bf HV |
327 | a->index = 0; |
328 | strlcpy(a->name, "Radio", sizeof(a->name)); | |
329 | a->capability = V4L2_AUDCAP_STEREO; | |
1de69238 | 330 | return 0; |
1da177e4 LT |
331 | } |
332 | ||
5ac3d5bf HV |
333 | static int vidioc_s_audio(struct file *file, void *priv, |
334 | struct v4l2_audio *a) | |
335 | { | |
336 | return a->index ? -EINVAL : 0; | |
337 | } | |
1da177e4 | 338 | |
bec43661 | 339 | static const struct v4l2_file_operations terratec_fops = { |
1da177e4 | 340 | .owner = THIS_MODULE, |
32958fdd | 341 | .unlocked_ioctl = video_ioctl2, |
1da177e4 LT |
342 | }; |
343 | ||
a399810c | 344 | static const struct v4l2_ioctl_ops terratec_ioctl_ops = { |
1de69238 DL |
345 | .vidioc_querycap = vidioc_querycap, |
346 | .vidioc_g_tuner = vidioc_g_tuner, | |
347 | .vidioc_s_tuner = vidioc_s_tuner, | |
348 | .vidioc_g_frequency = vidioc_g_frequency, | |
349 | .vidioc_s_frequency = vidioc_s_frequency, | |
350 | .vidioc_queryctrl = vidioc_queryctrl, | |
351 | .vidioc_g_ctrl = vidioc_g_ctrl, | |
352 | .vidioc_s_ctrl = vidioc_s_ctrl, | |
353 | .vidioc_g_audio = vidioc_g_audio, | |
354 | .vidioc_s_audio = vidioc_s_audio, | |
355 | .vidioc_g_input = vidioc_g_input, | |
356 | .vidioc_s_input = vidioc_s_input, | |
1da177e4 LT |
357 | }; |
358 | ||
359 | static int __init terratec_init(void) | |
360 | { | |
5ac3d5bf HV |
361 | struct terratec *tt = &terratec_card; |
362 | struct v4l2_device *v4l2_dev = &tt->v4l2_dev; | |
363 | int res; | |
364 | ||
365 | strlcpy(v4l2_dev->name, "terratec", sizeof(v4l2_dev->name)); | |
366 | tt->io = io; | |
367 | if (tt->io == -1) { | |
b24c20cc | 368 | v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n"); |
1da177e4 LT |
369 | return -EINVAL; |
370 | } | |
5ac3d5bf HV |
371 | if (!request_region(tt->io, 2, "terratec")) { |
372 | v4l2_err(v4l2_dev, "port 0x%x already in use\n", io); | |
1da177e4 LT |
373 | return -EBUSY; |
374 | } | |
375 | ||
5ac3d5bf HV |
376 | res = v4l2_device_register(NULL, v4l2_dev); |
377 | if (res < 0) { | |
378 | release_region(tt->io, 2); | |
379 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | |
380 | return res; | |
381 | } | |
382 | ||
383 | strlcpy(tt->vdev.name, v4l2_dev->name, sizeof(tt->vdev.name)); | |
384 | tt->vdev.v4l2_dev = v4l2_dev; | |
385 | tt->vdev.fops = &terratec_fops; | |
386 | tt->vdev.ioctl_ops = &terratec_ioctl_ops; | |
387 | tt->vdev.release = video_device_release_empty; | |
388 | video_set_drvdata(&tt->vdev, tt); | |
4286c6f6 | 389 | |
5ac3d5bf | 390 | mutex_init(&tt->lock); |
4286c6f6 | 391 | |
32958fdd HV |
392 | /* mute card - prevents noisy bootups */ |
393 | tt_write_vol(tt, 0); | |
394 | ||
5ac3d5bf HV |
395 | if (video_register_device(&tt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { |
396 | v4l2_device_unregister(&tt->v4l2_dev); | |
397 | release_region(tt->io, 2); | |
1da177e4 LT |
398 | return -EINVAL; |
399 | } | |
4286c6f6 | 400 | |
5ac3d5bf | 401 | v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n"); |
1da177e4 LT |
402 | return 0; |
403 | } | |
404 | ||
5ac3d5bf | 405 | static void __exit terratec_exit(void) |
1da177e4 | 406 | { |
5ac3d5bf HV |
407 | struct terratec *tt = &terratec_card; |
408 | struct v4l2_device *v4l2_dev = &tt->v4l2_dev; | |
409 | ||
410 | video_unregister_device(&tt->vdev); | |
411 | v4l2_device_unregister(&tt->v4l2_dev); | |
412 | release_region(tt->io, 2); | |
413 | v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver unloaded.\n"); | |
1da177e4 LT |
414 | } |
415 | ||
416 | module_init(terratec_init); | |
5ac3d5bf | 417 | module_exit(terratec_exit); |
1da177e4 | 418 |