Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/drivers/media/radio/radio-typhoon.c
15112 views
1
/* Typhoon Radio Card driver for radio support
2
* (c) 1999 Dr. Henrik Seidel <[email protected]>
3
*
4
* Notes on the hardware
5
*
6
* This card has two output sockets, one for speakers and one for line.
7
* The speaker output has volume control, but only in four discrete
8
* steps. The line output has neither volume control nor mute.
9
*
10
* The card has auto-stereo according to its manual, although it all
11
* sounds mono to me (even with the Win/DOS drivers). Maybe it's my
12
* antenna - I really don't know for sure.
13
*
14
* Frequency control is done digitally.
15
*
16
* Volume control is done digitally, but there are only four different
17
* possible values. So you should better always turn the volume up and
18
* use line control. I got the best results by connecting line output
19
* to the sound card microphone input. For such a configuration the
20
* volume control has no effect, since volume control only influences
21
* the speaker output.
22
*
23
* There is no explicit mute/unmute. So I set the radio frequency to a
24
* value where I do expect just noise and turn the speaker volume down.
25
* The frequency change is necessary since the card never seems to be
26
* completely silent.
27
*
28
* Converted to V4L2 API by Mauro Carvalho Chehab <[email protected]>
29
*/
30
31
#include <linux/module.h> /* Modules */
32
#include <linux/init.h> /* Initdata */
33
#include <linux/ioport.h> /* request_region */
34
#include <linux/version.h> /* for KERNEL_VERSION MACRO */
35
#include <linux/videodev2.h> /* kernel radio structs */
36
#include <linux/io.h> /* outb, outb_p */
37
#include <media/v4l2-device.h>
38
#include <media/v4l2-ioctl.h>
39
40
MODULE_AUTHOR("Dr. Henrik Seidel");
41
MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
42
MODULE_LICENSE("GPL");
43
44
#ifndef CONFIG_RADIO_TYPHOON_PORT
45
#define CONFIG_RADIO_TYPHOON_PORT -1
46
#endif
47
48
#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
49
#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
50
#endif
51
52
static int io = CONFIG_RADIO_TYPHOON_PORT;
53
static int radio_nr = -1;
54
55
module_param(io, int, 0);
56
MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
57
58
module_param(radio_nr, int, 0);
59
60
static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;
61
module_param(mutefreq, ulong, 0);
62
MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
63
64
#define RADIO_VERSION KERNEL_VERSION(0, 1, 1)
65
66
#define BANNER "Typhoon Radio Card driver v0.1.1\n"
67
68
struct typhoon {
69
struct v4l2_device v4l2_dev;
70
struct video_device vdev;
71
int io;
72
int curvol;
73
int muted;
74
unsigned long curfreq;
75
unsigned long mutefreq;
76
struct mutex lock;
77
};
78
79
static struct typhoon typhoon_card;
80
81
static void typhoon_setvol_generic(struct typhoon *dev, int vol)
82
{
83
mutex_lock(&dev->lock);
84
vol >>= 14; /* Map 16 bit to 2 bit */
85
vol &= 3;
86
outb_p(vol / 2, dev->io); /* Set the volume, high bit. */
87
outb_p(vol % 2, dev->io + 2); /* Set the volume, low bit. */
88
mutex_unlock(&dev->lock);
89
}
90
91
static int typhoon_setfreq_generic(struct typhoon *dev,
92
unsigned long frequency)
93
{
94
unsigned long outval;
95
unsigned long x;
96
97
/*
98
* The frequency transfer curve is not linear. The best fit I could
99
* get is
100
*
101
* outval = -155 + exp((f + 15.55) * 0.057))
102
*
103
* where frequency f is in MHz. Since we don't have exp in the kernel,
104
* I approximate this function by a third order polynomial.
105
*
106
*/
107
108
mutex_lock(&dev->lock);
109
x = frequency / 160;
110
outval = (x * x + 2500) / 5000;
111
outval = (outval * x + 5000) / 10000;
112
outval -= (10 * x * x + 10433) / 20866;
113
outval += 4 * x - 11505;
114
115
outb_p((outval >> 8) & 0x01, dev->io + 4);
116
outb_p(outval >> 9, dev->io + 6);
117
outb_p(outval & 0xff, dev->io + 8);
118
mutex_unlock(&dev->lock);
119
120
return 0;
121
}
122
123
static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency)
124
{
125
typhoon_setfreq_generic(dev, frequency);
126
dev->curfreq = frequency;
127
return 0;
128
}
129
130
static void typhoon_mute(struct typhoon *dev)
131
{
132
if (dev->muted == 1)
133
return;
134
typhoon_setvol_generic(dev, 0);
135
typhoon_setfreq_generic(dev, dev->mutefreq);
136
dev->muted = 1;
137
}
138
139
static void typhoon_unmute(struct typhoon *dev)
140
{
141
if (dev->muted == 0)
142
return;
143
typhoon_setfreq_generic(dev, dev->curfreq);
144
typhoon_setvol_generic(dev, dev->curvol);
145
dev->muted = 0;
146
}
147
148
static int typhoon_setvol(struct typhoon *dev, int vol)
149
{
150
if (dev->muted && vol != 0) { /* user is unmuting the card */
151
dev->curvol = vol;
152
typhoon_unmute(dev);
153
return 0;
154
}
155
if (vol == dev->curvol) /* requested volume == current */
156
return 0;
157
158
if (vol == 0) { /* volume == 0 means mute the card */
159
typhoon_mute(dev);
160
dev->curvol = vol;
161
return 0;
162
}
163
typhoon_setvol_generic(dev, vol);
164
dev->curvol = vol;
165
return 0;
166
}
167
168
static int vidioc_querycap(struct file *file, void *priv,
169
struct v4l2_capability *v)
170
{
171
strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));
172
strlcpy(v->card, "Typhoon Radio", sizeof(v->card));
173
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
174
v->version = RADIO_VERSION;
175
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
176
return 0;
177
}
178
179
static int vidioc_g_tuner(struct file *file, void *priv,
180
struct v4l2_tuner *v)
181
{
182
if (v->index > 0)
183
return -EINVAL;
184
185
strlcpy(v->name, "FM", sizeof(v->name));
186
v->type = V4L2_TUNER_RADIO;
187
v->rangelow = 87.5 * 16000;
188
v->rangehigh = 108 * 16000;
189
v->rxsubchans = V4L2_TUNER_SUB_MONO;
190
v->capability = V4L2_TUNER_CAP_LOW;
191
v->audmode = V4L2_TUNER_MODE_MONO;
192
v->signal = 0xFFFF; /* We can't get the signal strength */
193
return 0;
194
}
195
196
static int vidioc_s_tuner(struct file *file, void *priv,
197
struct v4l2_tuner *v)
198
{
199
return v->index ? -EINVAL : 0;
200
}
201
202
static int vidioc_g_frequency(struct file *file, void *priv,
203
struct v4l2_frequency *f)
204
{
205
struct typhoon *dev = video_drvdata(file);
206
207
if (f->tuner != 0)
208
return -EINVAL;
209
f->type = V4L2_TUNER_RADIO;
210
f->frequency = dev->curfreq;
211
return 0;
212
}
213
214
static int vidioc_s_frequency(struct file *file, void *priv,
215
struct v4l2_frequency *f)
216
{
217
struct typhoon *dev = video_drvdata(file);
218
219
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
220
return -EINVAL;
221
dev->curfreq = f->frequency;
222
typhoon_setfreq(dev, dev->curfreq);
223
return 0;
224
}
225
226
static int vidioc_queryctrl(struct file *file, void *priv,
227
struct v4l2_queryctrl *qc)
228
{
229
switch (qc->id) {
230
case V4L2_CID_AUDIO_MUTE:
231
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
232
case V4L2_CID_AUDIO_VOLUME:
233
return v4l2_ctrl_query_fill(qc, 0, 65535, 16384, 65535);
234
}
235
return -EINVAL;
236
}
237
238
static int vidioc_g_ctrl(struct file *file, void *priv,
239
struct v4l2_control *ctrl)
240
{
241
struct typhoon *dev = video_drvdata(file);
242
243
switch (ctrl->id) {
244
case V4L2_CID_AUDIO_MUTE:
245
ctrl->value = dev->muted;
246
return 0;
247
case V4L2_CID_AUDIO_VOLUME:
248
ctrl->value = dev->curvol;
249
return 0;
250
}
251
return -EINVAL;
252
}
253
254
static int vidioc_s_ctrl (struct file *file, void *priv,
255
struct v4l2_control *ctrl)
256
{
257
struct typhoon *dev = video_drvdata(file);
258
259
switch (ctrl->id) {
260
case V4L2_CID_AUDIO_MUTE:
261
if (ctrl->value)
262
typhoon_mute(dev);
263
else
264
typhoon_unmute(dev);
265
return 0;
266
case V4L2_CID_AUDIO_VOLUME:
267
typhoon_setvol(dev, ctrl->value);
268
return 0;
269
}
270
return -EINVAL;
271
}
272
273
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
274
{
275
*i = 0;
276
return 0;
277
}
278
279
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
280
{
281
return i ? -EINVAL : 0;
282
}
283
284
static int vidioc_g_audio(struct file *file, void *priv,
285
struct v4l2_audio *a)
286
{
287
a->index = 0;
288
strlcpy(a->name, "Radio", sizeof(a->name));
289
a->capability = V4L2_AUDCAP_STEREO;
290
return 0;
291
}
292
293
static int vidioc_s_audio(struct file *file, void *priv,
294
struct v4l2_audio *a)
295
{
296
return a->index ? -EINVAL : 0;
297
}
298
299
static int vidioc_log_status(struct file *file, void *priv)
300
{
301
struct typhoon *dev = video_drvdata(file);
302
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
303
304
v4l2_info(v4l2_dev, BANNER);
305
#ifdef MODULE
306
v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n");
307
#else
308
v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n");
309
#endif
310
v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4);
311
v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol);
312
v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ? "on" : "off");
313
v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io);
314
v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4);
315
return 0;
316
}
317
318
static const struct v4l2_file_operations typhoon_fops = {
319
.owner = THIS_MODULE,
320
.unlocked_ioctl = video_ioctl2,
321
};
322
323
static const struct v4l2_ioctl_ops typhoon_ioctl_ops = {
324
.vidioc_log_status = vidioc_log_status,
325
.vidioc_querycap = vidioc_querycap,
326
.vidioc_g_tuner = vidioc_g_tuner,
327
.vidioc_s_tuner = vidioc_s_tuner,
328
.vidioc_g_audio = vidioc_g_audio,
329
.vidioc_s_audio = vidioc_s_audio,
330
.vidioc_g_input = vidioc_g_input,
331
.vidioc_s_input = vidioc_s_input,
332
.vidioc_g_frequency = vidioc_g_frequency,
333
.vidioc_s_frequency = vidioc_s_frequency,
334
.vidioc_queryctrl = vidioc_queryctrl,
335
.vidioc_g_ctrl = vidioc_g_ctrl,
336
.vidioc_s_ctrl = vidioc_s_ctrl,
337
};
338
339
static int __init typhoon_init(void)
340
{
341
struct typhoon *dev = &typhoon_card;
342
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
343
int res;
344
345
strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name));
346
dev->io = io;
347
348
if (dev->io == -1) {
349
v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n");
350
return -EINVAL;
351
}
352
353
if (mutefreq < 87000 || mutefreq > 108500) {
354
v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n");
355
v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
356
return -EINVAL;
357
}
358
dev->curfreq = dev->mutefreq = mutefreq << 4;
359
360
mutex_init(&dev->lock);
361
if (!request_region(dev->io, 8, "typhoon")) {
362
v4l2_err(v4l2_dev, "port 0x%x already in use\n",
363
dev->io);
364
return -EBUSY;
365
}
366
367
res = v4l2_device_register(NULL, v4l2_dev);
368
if (res < 0) {
369
release_region(dev->io, 8);
370
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
371
return res;
372
}
373
v4l2_info(v4l2_dev, BANNER);
374
375
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
376
dev->vdev.v4l2_dev = v4l2_dev;
377
dev->vdev.fops = &typhoon_fops;
378
dev->vdev.ioctl_ops = &typhoon_ioctl_ops;
379
dev->vdev.release = video_device_release_empty;
380
video_set_drvdata(&dev->vdev, dev);
381
382
/* mute card - prevents noisy bootups */
383
typhoon_mute(dev);
384
385
if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
386
v4l2_device_unregister(&dev->v4l2_dev);
387
release_region(dev->io, 8);
388
return -EINVAL;
389
}
390
v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io);
391
v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq);
392
393
return 0;
394
}
395
396
static void __exit typhoon_exit(void)
397
{
398
struct typhoon *dev = &typhoon_card;
399
400
video_unregister_device(&dev->vdev);
401
v4l2_device_unregister(&dev->v4l2_dev);
402
release_region(dev->io, 8);
403
}
404
405
module_init(typhoon_init);
406
module_exit(typhoon_exit);
407
408
409