Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/drivers/media/radio/radio-terratec.c
15111 views
1
/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
2
* (c) 1999 R. Offermanns ([email protected])
3
* based on the aimslab radio driver from M. Kirkwood
4
* many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
5
*
6
*
7
* History:
8
* 1999-05-21 First preview release
9
*
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
*
23
*
24
* Converted to V4L2 API by Mauro Carvalho Chehab <[email protected]>
25
*/
26
27
#include <linux/module.h> /* Modules */
28
#include <linux/init.h> /* Initdata */
29
#include <linux/ioport.h> /* request_region */
30
#include <linux/videodev2.h> /* kernel radio structs */
31
#include <linux/mutex.h>
32
#include <linux/version.h> /* for KERNEL_VERSION MACRO */
33
#include <linux/io.h> /* outb, outb_p */
34
#include <media/v4l2-device.h>
35
#include <media/v4l2-ioctl.h>
36
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)
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
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
80
81
struct terratec
82
{
83
struct v4l2_device v4l2_dev;
84
struct video_device vdev;
85
int io;
86
int curvol;
87
unsigned long curfreq;
88
int muted;
89
struct mutex lock;
90
};
91
92
static struct terratec terratec_card;
93
94
/* local things */
95
96
static void tt_write_vol(struct terratec *tt, int volume)
97
{
98
int i;
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);
107
}
108
mutex_unlock(&tt->lock);
109
}
110
111
112
113
static void tt_mute(struct terratec *tt)
114
{
115
tt->muted = 1;
116
tt_write_vol(tt, 0);
117
}
118
119
static int tt_setvol(struct terratec *tt, int vol)
120
{
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 */
125
}
126
return 0;
127
}
128
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! */
132
return 0;
133
}
134
135
tt->muted = 0;
136
tt_write_vol(tt, vol);
137
tt->curvol = vol;
138
return 0;
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
145
static int tt_setfreq(struct terratec *tt, unsigned long freq1)
146
{
147
int freq;
148
int i;
149
int p;
150
int temp;
151
long rest;
152
unsigned char buffer[25]; /* we have to bit shift 25 registers */
153
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 */
162
/* maybe some wise guy (friedhelm?) can comment this stuff */
163
i = 13;
164
p = 10;
165
temp = 102400;
166
while (rest != 0) {
167
if (rest % temp == rest)
168
buffer[i] = 0;
169
else {
170
buffer[i] = 1;
171
rest = rest - temp;
172
}
173
i--;
174
p--;
175
temp = temp / 2;
176
}
177
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);
186
}
187
}
188
outb(0x00, tt->io);
189
190
mutex_unlock(&tt->lock);
191
192
return 0;
193
}
194
195
static int tt_getsigstr(struct terratec *tt)
196
{
197
if (inb(tt->io) & 2) /* bit set = no signal present */
198
return 0;
199
return 1; /* signal present */
200
}
201
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));
207
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
208
v->version = RADIO_VERSION;
209
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
210
return 0;
211
}
212
213
static int vidioc_g_tuner(struct file *file, void *priv,
214
struct v4l2_tuner *v)
215
{
216
struct terratec *tt = video_drvdata(file);
217
218
if (v->index > 0)
219
return -EINVAL;
220
221
strlcpy(v->name, "FM", sizeof(v->name));
222
v->type = V4L2_TUNER_RADIO;
223
v->rangelow = 87 * 16000;
224
v->rangehigh = 108 * 16000;
225
v->rxsubchans = V4L2_TUNER_SUB_MONO;
226
v->capability = V4L2_TUNER_CAP_LOW;
227
v->audmode = V4L2_TUNER_MODE_MONO;
228
v->signal = 0xFFFF * tt_getsigstr(tt);
229
return 0;
230
}
231
232
static int vidioc_s_tuner(struct file *file, void *priv,
233
struct v4l2_tuner *v)
234
{
235
return v->index ? -EINVAL : 0;
236
}
237
238
static int vidioc_s_frequency(struct file *file, void *priv,
239
struct v4l2_frequency *f)
240
{
241
struct terratec *tt = video_drvdata(file);
242
243
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
244
return -EINVAL;
245
tt_setfreq(tt, f->frequency);
246
return 0;
247
}
248
249
static int vidioc_g_frequency(struct file *file, void *priv,
250
struct v4l2_frequency *f)
251
{
252
struct terratec *tt = video_drvdata(file);
253
254
if (f->tuner != 0)
255
return -EINVAL;
256
f->type = V4L2_TUNER_RADIO;
257
f->frequency = tt->curfreq;
258
return 0;
259
}
260
261
static int vidioc_queryctrl(struct file *file, void *priv,
262
struct v4l2_queryctrl *qc)
263
{
264
int i;
265
266
for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
267
if (qc->id && qc->id == radio_qctrl[i].id) {
268
memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
269
return 0;
270
}
271
}
272
return -EINVAL;
273
}
274
275
static int vidioc_g_ctrl(struct file *file, void *priv,
276
struct v4l2_control *ctrl)
277
{
278
struct terratec *tt = video_drvdata(file);
279
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
}
293
294
static int vidioc_s_ctrl(struct file *file, void *priv,
295
struct v4l2_control *ctrl)
296
{
297
struct terratec *tt = video_drvdata(file);
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;
309
}
310
return -EINVAL;
311
}
312
313
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
314
{
315
*i = 0;
316
return 0;
317
}
318
319
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
320
{
321
return i ? -EINVAL : 0;
322
}
323
324
static int vidioc_g_audio(struct file *file, void *priv,
325
struct v4l2_audio *a)
326
{
327
a->index = 0;
328
strlcpy(a->name, "Radio", sizeof(a->name));
329
a->capability = V4L2_AUDCAP_STEREO;
330
return 0;
331
}
332
333
static int vidioc_s_audio(struct file *file, void *priv,
334
struct v4l2_audio *a)
335
{
336
return a->index ? -EINVAL : 0;
337
}
338
339
static const struct v4l2_file_operations terratec_fops = {
340
.owner = THIS_MODULE,
341
.unlocked_ioctl = video_ioctl2,
342
};
343
344
static const struct v4l2_ioctl_ops terratec_ioctl_ops = {
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,
357
};
358
359
static int __init terratec_init(void)
360
{
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) {
368
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n");
369
return -EINVAL;
370
}
371
if (!request_region(tt->io, 2, "terratec")) {
372
v4l2_err(v4l2_dev, "port 0x%x already in use\n", io);
373
return -EBUSY;
374
}
375
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);
389
390
mutex_init(&tt->lock);
391
392
/* mute card - prevents noisy bootups */
393
tt_write_vol(tt, 0);
394
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);
398
return -EINVAL;
399
}
400
401
v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n");
402
return 0;
403
}
404
405
static void __exit terratec_exit(void)
406
{
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");
414
}
415
416
module_init(terratec_init);
417
module_exit(terratec_exit);
418
419
420