1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Typhoon Radio Card driver for radio support
3 * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
5 * Notes on the hardware
7 * This card has two output sockets, one for speakers and one for line.
8 * The speaker output has volume control, but only in four discrete
9 * steps. The line output has neither volume control nor mute.
11 * The card has auto-stereo according to its manual, although it all
12 * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
13 * antenna - I really don't know for sure.
15 * Frequency control is done digitally.
17 * Volume control is done digitally, but there are only four different
18 * possible values. So you should better always turn the volume up and
19 * use line control. I got the best results by connecting line output
20 * to the sound card microphone input. For such a configuration the
21 * volume control has no effect, since volume control only influences
24 * There is no explicit mute/unmute. So I set the radio frequency to a
25 * value where I do expect just noise and turn the speaker volume down.
26 * The frequency change is necessary since the card never seems to be
29 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
32 #include <linux/module.h> /* Modules */
33 #include <linux/init.h> /* Initdata */
34 #include <linux/ioport.h> /* request_region */
35 #include <linux/videodev2.h> /* kernel radio structs */
36 #include <linux/io.h> /* outb, outb_p */
37 #include <linux/slab.h>
38 #include <media/v4l2-device.h>
39 #include <media/v4l2-ioctl.h>
40 #include "radio-isa.h"
42 #define DRIVER_VERSION "0.1.2"
44 MODULE_AUTHOR("Dr. Henrik Seidel");
45 MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
46 MODULE_LICENSE("GPL");
47 MODULE_VERSION("0.1.99");
49 #ifndef CONFIG_RADIO_TYPHOON_PORT
50 #define CONFIG_RADIO_TYPHOON_PORT -1
53 #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
54 #define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000
59 static int io
[TYPHOON_MAX
] = { [0] = CONFIG_RADIO_TYPHOON_PORT
,
60 [1 ... (TYPHOON_MAX
- 1)] = -1 };
61 static int radio_nr
[TYPHOON_MAX
] = { [0 ... (TYPHOON_MAX
- 1)] = -1 };
62 static unsigned long mutefreq
= CONFIG_RADIO_TYPHOON_MUTEFREQ
;
64 module_param_array(io
, int, NULL
, 0444);
65 MODULE_PARM_DESC(io
, "I/O addresses of the Typhoon card (0x316 or 0x336)");
66 module_param_array(radio_nr
, int, NULL
, 0444);
67 MODULE_PARM_DESC(radio_nr
, "Radio device numbers");
68 module_param(mutefreq
, ulong
, 0);
69 MODULE_PARM_DESC(mutefreq
, "Frequency used when muting the card (in kHz)");
72 struct radio_isa_card isa
;
76 static struct radio_isa_card
*typhoon_alloc(void)
78 struct typhoon
*ty
= kzalloc(sizeof(*ty
), GFP_KERNEL
);
80 return ty
? &ty
->isa
: NULL
;
83 static int typhoon_s_frequency(struct radio_isa_card
*isa
, u32 freq
)
89 * The frequency transfer curve is not linear. The best fit I could
92 * outval = -155 + exp((f + 15.55) * 0.057))
94 * where frequency f is in MHz. Since we don't have exp in the kernel,
95 * I approximate this function by a third order polynomial.
100 outval
= (x
* x
+ 2500) / 5000;
101 outval
= (outval
* x
+ 5000) / 10000;
102 outval
-= (10 * x
* x
+ 10433) / 20866;
103 outval
+= 4 * x
- 11505;
105 outb_p((outval
>> 8) & 0x01, isa
->io
+ 4);
106 outb_p(outval
>> 9, isa
->io
+ 6);
107 outb_p(outval
& 0xff, isa
->io
+ 8);
111 static int typhoon_s_mute_volume(struct radio_isa_card
*isa
, bool mute
, int vol
)
113 struct typhoon
*ty
= container_of(isa
, struct typhoon
, isa
);
117 vol
>>= 14; /* Map 16 bit to 2 bit */
119 outb_p(vol
/ 2, isa
->io
); /* Set the volume, high bit. */
120 outb_p(vol
% 2, isa
->io
+ 2); /* Set the volume, low bit. */
122 if (vol
== 0 && !ty
->muted
) {
124 return typhoon_s_frequency(isa
, mutefreq
<< 4);
126 if (vol
&& ty
->muted
) {
128 return typhoon_s_frequency(isa
, isa
->freq
);
133 static const struct radio_isa_ops typhoon_ops
= {
134 .alloc
= typhoon_alloc
,
135 .s_mute_volume
= typhoon_s_mute_volume
,
136 .s_frequency
= typhoon_s_frequency
,
139 static const int typhoon_ioports
[] = { 0x316, 0x336 };
141 static struct radio_isa_driver typhoon_driver
= {
143 .match
= radio_isa_match
,
144 .probe
= radio_isa_probe
,
145 .remove
= radio_isa_remove
,
147 .name
= "radio-typhoon",
151 .radio_nr_params
= radio_nr
,
152 .io_ports
= typhoon_ioports
,
153 .num_of_io_ports
= ARRAY_SIZE(typhoon_ioports
),
155 .card
= "Typhoon Radio",
161 static int __init
typhoon_init(void)
163 if (mutefreq
< 87000 || mutefreq
> 108000) {
164 printk(KERN_ERR
"%s: You must set a frequency (in kHz) used when muting the card,\n",
165 typhoon_driver
.driver
.driver
.name
);
166 printk(KERN_ERR
"%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n",
167 typhoon_driver
.driver
.driver
.name
);
170 return isa_register_driver(&typhoon_driver
.driver
, TYPHOON_MAX
);
173 static void __exit
typhoon_exit(void)
175 isa_unregister_driver(&typhoon_driver
.driver
);
179 module_init(typhoon_init
);
180 module_exit(typhoon_exit
);