diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/radio |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/media/radio')
-rw-r--r-- | drivers/media/radio/Kconfig | 354 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 22 | ||||
-rw-r--r-- | drivers/media/radio/miropcm20-radio.c | 264 | ||||
-rw-r--r-- | drivers/media/radio/miropcm20-rds-core.c | 210 | ||||
-rw-r--r-- | drivers/media/radio/miropcm20-rds-core.h | 19 | ||||
-rw-r--r-- | drivers/media/radio/miropcm20-rds.c | 133 | ||||
-rw-r--r-- | drivers/media/radio/radio-aimslab.c | 368 | ||||
-rw-r--r-- | drivers/media/radio/radio-aztech.c | 315 | ||||
-rw-r--r-- | drivers/media/radio/radio-cadet.c | 620 | ||||
-rw-r--r-- | drivers/media/radio/radio-gemtek-pci.c | 416 | ||||
-rw-r--r-- | drivers/media/radio/radio-gemtek.c | 304 | ||||
-rw-r--r-- | drivers/media/radio/radio-maestro.c | 332 | ||||
-rw-r--r-- | drivers/media/radio/radio-maxiradio.c | 349 | ||||
-rw-r--r-- | drivers/media/radio/radio-rtrack2.c | 266 | ||||
-rw-r--r-- | drivers/media/radio/radio-sf16fmi.c | 328 | ||||
-rw-r--r-- | drivers/media/radio/radio-sf16fmr2.c | 434 | ||||
-rw-r--r-- | drivers/media/radio/radio-terratec.c | 341 | ||||
-rw-r--r-- | drivers/media/radio/radio-trust.c | 320 | ||||
-rw-r--r-- | drivers/media/radio/radio-typhoon.c | 383 | ||||
-rw-r--r-- | drivers/media/radio/radio-zoltrix.c | 385 |
20 files changed, 6163 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig new file mode 100644 index 000000000000..d318be383de6 --- /dev/null +++ b/drivers/media/radio/Kconfig | |||
@@ -0,0 +1,354 @@ | |||
1 | # | ||
2 | # Multimedia Video device configuration | ||
3 | # | ||
4 | |||
5 | menu "Radio Adapters" | ||
6 | depends on VIDEO_DEV!=n | ||
7 | |||
8 | config RADIO_CADET | ||
9 | tristate "ADS Cadet AM/FM Tuner" | ||
10 | depends on ISA && VIDEO_DEV | ||
11 | ---help--- | ||
12 | Choose Y here if you have one of these AM/FM radio cards, and then | ||
13 | fill in the port address below. | ||
14 | |||
15 | In order to control your radio card, you will need to use programs | ||
16 | that are compatible with the Video For Linux API. Information on | ||
17 | this API and pointers to "v4l" programs may be found at | ||
18 | <file:Documentation/video4linux/API.html>. | ||
19 | |||
20 | Further documentation on this driver can be found on the WWW at | ||
21 | <http://linux.blackhawke.net/cadet/>. | ||
22 | |||
23 | To compile this driver as a module, choose M here: the | ||
24 | module will be called radio-cadet. | ||
25 | |||
26 | config RADIO_RTRACK | ||
27 | tristate "AIMSlab RadioTrack (aka RadioReveal) support" | ||
28 | depends on ISA && VIDEO_DEV | ||
29 | ---help--- | ||
30 | Choose Y here if you have one of these FM radio cards, and then fill | ||
31 | in the port address below. | ||
32 | |||
33 | Note that newer AIMSlab RadioTrack cards have a different chipset | ||
34 | and are not supported by this driver. For these cards, use the | ||
35 | RadioTrack II driver below. | ||
36 | |||
37 | If you have a GemTeks combined (PnP) sound- and radio card you must | ||
38 | use this driver as a module and setup the card with isapnptools. | ||
39 | You must also pass the module a suitable io parameter, 0x248 has | ||
40 | been reported to be used by these cards. | ||
41 | |||
42 | In order to control your radio card, you will need to use programs | ||
43 | that are compatible with the Video For Linux API. Information on | ||
44 | this API and pointers to "v4l" programs may be found at | ||
45 | <file:Documentation/video4linux/API.html>. More information is | ||
46 | contained in the file | ||
47 | <file:Documentation/video4linux/radiotrack.txt>. | ||
48 | |||
49 | To compile this driver as a module, choose M here: the | ||
50 | module will be called radio-aimslab. | ||
51 | |||
52 | config RADIO_RTRACK_PORT | ||
53 | hex "RadioTrack i/o port (0x20f or 0x30f)" | ||
54 | depends on RADIO_RTRACK=y | ||
55 | default "20f" | ||
56 | help | ||
57 | Enter either 0x30f or 0x20f here. The card default is 0x30f, if you | ||
58 | haven't changed the jumper setting on the card. | ||
59 | |||
60 | config RADIO_RTRACK2 | ||
61 | tristate "AIMSlab RadioTrack II support" | ||
62 | depends on ISA && VIDEO_DEV | ||
63 | ---help--- | ||
64 | Choose Y here if you have this FM radio card, and then fill in the | ||
65 | port address below. | ||
66 | |||
67 | In order to control your radio card, you will need to use programs | ||
68 | that are compatible with the Video For Linux API. Information on | ||
69 | this API and pointers to "v4l" programs may be found at | ||
70 | <file:Documentation/video4linux/API.html>. | ||
71 | |||
72 | To compile this driver as a module, choose M here: the | ||
73 | module will be called radio-rtrack2. | ||
74 | |||
75 | config RADIO_RTRACK2_PORT | ||
76 | hex "RadioTrack II i/o port (0x20c or 0x30c)" | ||
77 | depends on RADIO_RTRACK2=y | ||
78 | default "30c" | ||
79 | help | ||
80 | Enter either 0x30c or 0x20c here. The card default is 0x30c, if you | ||
81 | haven't changed the jumper setting on the card. | ||
82 | |||
83 | config RADIO_AZTECH | ||
84 | tristate "Aztech/Packard Bell Radio" | ||
85 | depends on ISA && VIDEO_DEV | ||
86 | ---help--- | ||
87 | Choose Y here if you have one of these FM radio cards, and then fill | ||
88 | in the port address below. | ||
89 | |||
90 | In order to control your radio card, you will need to use programs | ||
91 | that are compatible with the Video For Linux API. Information on | ||
92 | this API and pointers to "v4l" programs may be found at | ||
93 | <file:Documentation/video4linux/API.html>. | ||
94 | |||
95 | To compile this driver as a module, choose M here: the | ||
96 | module will be called radio-aztech. | ||
97 | |||
98 | config RADIO_AZTECH_PORT | ||
99 | hex "Aztech/Packard Bell I/O port (0x350 or 0x358)" | ||
100 | depends on RADIO_AZTECH=y | ||
101 | default "350" | ||
102 | help | ||
103 | Enter either 0x350 or 0x358 here. The card default is 0x350, if you | ||
104 | haven't changed the setting of jumper JP3 on the card. Removing the | ||
105 | jumper sets the card to 0x358. | ||
106 | |||
107 | config RADIO_GEMTEK | ||
108 | tristate "GemTek Radio Card support" | ||
109 | depends on ISA && VIDEO_DEV | ||
110 | ---help--- | ||
111 | Choose Y here if you have this FM radio card, and then fill in the | ||
112 | port address below. | ||
113 | |||
114 | In order to control your radio card, you will need to use programs | ||
115 | that are compatible with the Video For Linux API. Information on | ||
116 | this API and pointers to "v4l" programs may be found at | ||
117 | <file:Documentation/video4linux/API.html>. | ||
118 | |||
119 | To compile this driver as a module, choose M here: the | ||
120 | module will be called radio-gemtek. | ||
121 | |||
122 | config RADIO_GEMTEK_PORT | ||
123 | hex "GemTek i/o port (0x20c, 0x30c, 0x24c or 0x34c)" | ||
124 | depends on RADIO_GEMTEK=y | ||
125 | default "34c" | ||
126 | help | ||
127 | Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is | ||
128 | 0x34c, if you haven't changed the jumper setting on the card. On | ||
129 | Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O | ||
130 | port is 0x28c. | ||
131 | |||
132 | config RADIO_GEMTEK_PCI | ||
133 | tristate "GemTek PCI Radio Card support" | ||
134 | depends on VIDEO_DEV && PCI | ||
135 | ---help--- | ||
136 | Choose Y here if you have this PCI FM radio card. | ||
137 | |||
138 | In order to control your radio card, you will need to use programs | ||
139 | that are compatible with the Video for Linux API. Information on | ||
140 | this API and pointers to "v4l" programs may be found at | ||
141 | <file:Documentation/video4linux/API.html>. | ||
142 | |||
143 | To compile this driver as a module, choose M here: the | ||
144 | module will be called radio-gemtek-pci. | ||
145 | |||
146 | config RADIO_MAXIRADIO | ||
147 | tristate "Guillemot MAXI Radio FM 2000 radio" | ||
148 | depends on VIDEO_DEV && PCI | ||
149 | ---help--- | ||
150 | Choose Y here if you have this radio card. This card may also be | ||
151 | found as Gemtek PCI FM. | ||
152 | |||
153 | In order to control your radio card, you will need to use programs | ||
154 | that are compatible with the Video For Linux API. Information on | ||
155 | this API and pointers to "v4l" programs may be found at | ||
156 | <file:Documentation/video4linux/API.html>. | ||
157 | |||
158 | To compile this driver as a module, choose M here: the | ||
159 | module will be called radio-maxiradio. | ||
160 | |||
161 | config RADIO_MAESTRO | ||
162 | tristate "Maestro on board radio" | ||
163 | depends on VIDEO_DEV | ||
164 | ---help--- | ||
165 | Say Y here to directly support the on-board radio tuner on the | ||
166 | Maestro 2 or 2E sound card. | ||
167 | |||
168 | In order to control your radio card, you will need to use programs | ||
169 | that are compatible with the Video For Linux API. Information on | ||
170 | this API and pointers to "v4l" programs may be found at | ||
171 | <file:Documentation/video4linux/API.html>. | ||
172 | |||
173 | To compile this driver as a module, choose M here: the | ||
174 | module will be called radio-maestro. | ||
175 | |||
176 | config RADIO_MIROPCM20 | ||
177 | tristate "miroSOUND PCM20 radio" | ||
178 | depends on ISA && VIDEO_DEV && SOUND_ACI_MIXER | ||
179 | ---help--- | ||
180 | Choose Y here if you have this FM radio card. You also need to say Y | ||
181 | to "ACI mixer (miroSOUND PCM1-pro/PCM12/PCM20 radio)" (in "Sound") | ||
182 | for this to work. | ||
183 | |||
184 | In order to control your radio card, you will need to use programs | ||
185 | that are compatible with the Video For Linux API. Information on | ||
186 | this API and pointers to "v4l" programs may be found at | ||
187 | <file:Documentation/video4linux/API.html>. | ||
188 | |||
189 | To compile this driver as a module, choose M here: the | ||
190 | module will be called miropcm20. | ||
191 | |||
192 | config RADIO_MIROPCM20_RDS | ||
193 | tristate "miroSOUND PCM20 radio RDS user interface (EXPERIMENTAL)" | ||
194 | depends on RADIO_MIROPCM20 && EXPERIMENTAL | ||
195 | ---help--- | ||
196 | Choose Y here if you want to see RDS/RBDS information like | ||
197 | RadioText, Programme Service name, Clock Time and date, Programme | ||
198 | TYpe and Traffic Announcement/Programme identification. You also | ||
199 | need to say Y to "miroSOUND PCM20 radio" and devfs! | ||
200 | |||
201 | It's not possible to read the raw RDS packets from the device, so | ||
202 | the driver cant provide an V4L interface for this. But the | ||
203 | availability of RDS is reported over V4L by the basic driver | ||
204 | already. Here RDS can be read from files in /dev/v4l/rds. | ||
205 | |||
206 | To compile this driver as a module, choose M here: the | ||
207 | module will be called miropcm20-rds. | ||
208 | |||
209 | config RADIO_SF16FMI | ||
210 | tristate "SF16FMI Radio" | ||
211 | depends on ISA && VIDEO_DEV | ||
212 | ---help--- | ||
213 | Choose Y here if you have one of these FM radio cards. If you | ||
214 | compile the driver into the kernel and your card is not PnP one, you | ||
215 | have to add "sf16fm=<io>" to the kernel command line (I/O address is | ||
216 | 0x284 or 0x384). | ||
217 | |||
218 | In order to control your radio card, you will need to use programs | ||
219 | that are compatible with the Video For Linux API. Information on | ||
220 | this API and pointers to "v4l" programs may be found at | ||
221 | <file:Documentation/video4linux/API.html>. | ||
222 | |||
223 | To compile this driver as a module, choose M here: the | ||
224 | module will be called radio-sf16fmi. | ||
225 | |||
226 | config RADIO_SF16FMR2 | ||
227 | tristate "SF16FMR2 Radio" | ||
228 | depends on ISA && VIDEO_DEV | ||
229 | ---help--- | ||
230 | Choose Y here if you have one of these FM radio cards. | ||
231 | |||
232 | In order to control your radio card, you will need to use programs | ||
233 | that are compatible with the Video For Linux API. Information on | ||
234 | this API and pointers to "v4l" programs may be found on the WWW at | ||
235 | <http://roadrunner.swansea.uk.linux.org/v4l.shtml>. | ||
236 | |||
237 | To compile this driver as a module, choose M here: the | ||
238 | module will be called radio-sf16fmr2. | ||
239 | |||
240 | config RADIO_TERRATEC | ||
241 | tristate "TerraTec ActiveRadio ISA Standalone" | ||
242 | depends on ISA && VIDEO_DEV | ||
243 | ---help--- | ||
244 | Choose Y here if you have this FM radio card, and then fill in the | ||
245 | port address below. (TODO) | ||
246 | |||
247 | Note: This driver is in its early stages. Right now volume and | ||
248 | frequency control and muting works at least for me, but | ||
249 | unfortunately I have not found anybody who wants to use this card | ||
250 | with Linux. So if it is this what YOU are trying to do right now, | ||
251 | PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>. | ||
252 | |||
253 | In order to control your radio card, you will need to use programs | ||
254 | that are compatible with the Video For Linux API. Information on | ||
255 | this API and pointers to "v4l" programs may be found at | ||
256 | <file:Documentation/video4linux/API.html>. | ||
257 | |||
258 | To compile this driver as a module, choose M here: the | ||
259 | module will be called radio-terratec. | ||
260 | |||
261 | config RADIO_TERRATEC_PORT | ||
262 | hex "Terratec i/o port (normally 0x590)" | ||
263 | depends on RADIO_TERRATEC=y | ||
264 | default "590" | ||
265 | help | ||
266 | Fill in the I/O port of your TerraTec FM radio card. If unsure, go | ||
267 | with the default. | ||
268 | |||
269 | config RADIO_TRUST | ||
270 | tristate "Trust FM radio card" | ||
271 | depends on ISA && VIDEO_DEV | ||
272 | help | ||
273 | This is a driver for the Trust FM radio cards. Say Y if you have | ||
274 | such a card and want to use it under Linux. | ||
275 | |||
276 | To compile this driver as a module, choose M here: the | ||
277 | module will be called radio-trust. | ||
278 | |||
279 | config RADIO_TRUST_PORT | ||
280 | hex "Trust i/o port (usually 0x350 or 0x358)" | ||
281 | depends on RADIO_TRUST=y | ||
282 | default "350" | ||
283 | help | ||
284 | Enter the I/O port of your Trust FM radio card. If unsure, try the | ||
285 | values "0x350" or "0x358". | ||
286 | |||
287 | config RADIO_TYPHOON | ||
288 | tristate "Typhoon Radio (a.k.a. EcoRadio)" | ||
289 | depends on ISA && VIDEO_DEV | ||
290 | ---help--- | ||
291 | Choose Y here if you have one of these FM radio cards, and then fill | ||
292 | in the port address and the frequency used for muting below. | ||
293 | |||
294 | In order to control your radio card, you will need to use programs | ||
295 | that are compatible with the Video For Linux API. Information on | ||
296 | this API and pointers to "v4l" programs may be found at | ||
297 | <file:Documentation/video4linux/API.html>. | ||
298 | |||
299 | To compile this driver as a module, choose M here: the | ||
300 | module will be called radio-typhoon. | ||
301 | |||
302 | config RADIO_TYPHOON_PROC_FS | ||
303 | bool "Support for /proc/radio-typhoon" | ||
304 | depends on PROC_FS && RADIO_TYPHOON | ||
305 | help | ||
306 | Say Y here if you want the typhoon radio card driver to write | ||
307 | status information (frequency, volume, muted, mute frequency, | ||
308 | base address) to /proc/radio-typhoon. The file can be viewed with | ||
309 | your favorite pager (i.e. use "more /proc/radio-typhoon" or "less | ||
310 | /proc/radio-typhoon" or simply "cat /proc/radio-typhoon"). | ||
311 | |||
312 | config RADIO_TYPHOON_PORT | ||
313 | hex "Typhoon I/O port (0x316 or 0x336)" | ||
314 | depends on RADIO_TYPHOON=y | ||
315 | default "316" | ||
316 | help | ||
317 | Enter the I/O port of your Typhoon or EcoRadio radio card. | ||
318 | |||
319 | config RADIO_TYPHOON_MUTEFREQ | ||
320 | int "Typhoon frequency set when muting the device (kHz)" | ||
321 | depends on RADIO_TYPHOON=y | ||
322 | default "87500" | ||
323 | help | ||
324 | Enter the frequency used for muting the radio. The device is never | ||
325 | completely silent. If the volume is just turned down, you can still | ||
326 | hear silent voices and music. For that reason, the frequency of the | ||
327 | radio device is set to the frequency you can enter here whenever | ||
328 | the device is muted. There should be no local radio station at that | ||
329 | frequency. | ||
330 | |||
331 | config RADIO_ZOLTRIX | ||
332 | tristate "Zoltrix Radio" | ||
333 | depends on ISA && VIDEO_DEV | ||
334 | ---help--- | ||
335 | Choose Y here if you have one of these FM radio cards, and then fill | ||
336 | in the port address below. | ||
337 | |||
338 | In order to control your radio card, you will need to use programs | ||
339 | that are compatible with the Video For Linux API. Information on | ||
340 | this API and pointers to "v4l" programs may be found at | ||
341 | <file:Documentation/video4linux/API.html>. | ||
342 | |||
343 | To compile this driver as a module, choose M here: the | ||
344 | module will be called radio-zoltrix. | ||
345 | |||
346 | config RADIO_ZOLTRIX_PORT | ||
347 | hex "ZOLTRIX I/O port (0x20c or 0x30c)" | ||
348 | depends on RADIO_ZOLTRIX=y | ||
349 | default "20c" | ||
350 | help | ||
351 | Enter the I/O port of your Zoltrix radio card. | ||
352 | |||
353 | endmenu | ||
354 | |||
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile new file mode 100644 index 000000000000..8b351945d066 --- /dev/null +++ b/drivers/media/radio/Makefile | |||
@@ -0,0 +1,22 @@ | |||
1 | # | ||
2 | # Makefile for the kernel character device drivers. | ||
3 | # | ||
4 | |||
5 | miropcm20-objs := miropcm20-rds-core.o miropcm20-radio.o | ||
6 | |||
7 | obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o | ||
8 | obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o | ||
9 | obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o | ||
10 | obj-$(CONFIG_RADIO_SF16FMR2) += radio-sf16fmr2.o | ||
11 | obj-$(CONFIG_RADIO_CADET) += radio-cadet.o | ||
12 | obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o | ||
13 | obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o | ||
14 | obj-$(CONFIG_RADIO_MAXIRADIO) += radio-maxiradio.o | ||
15 | obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o | ||
16 | obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o | ||
17 | obj-$(CONFIG_RADIO_MIROPCM20) += miropcm20.o | ||
18 | obj-$(CONFIG_RADIO_MIROPCM20_RDS) += miropcm20-rds.o | ||
19 | obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o | ||
20 | obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o | ||
21 | obj-$(CONFIG_RADIO_TRUST) += radio-trust.o | ||
22 | obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o | ||
diff --git a/drivers/media/radio/miropcm20-radio.c b/drivers/media/radio/miropcm20-radio.c new file mode 100644 index 000000000000..c2ebe8754a95 --- /dev/null +++ b/drivers/media/radio/miropcm20-radio.c | |||
@@ -0,0 +1,264 @@ | |||
1 | /* Miro PCM20 radio driver for Linux radio support | ||
2 | * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | ||
3 | * Thanks to Norberto Pellici for the ACI device interface specification | ||
4 | * The API part is based on the radiotrack driver by M. Kirkwood | ||
5 | * This driver relies on the aci mixer (drivers/sound/aci.c) | ||
6 | * Look there for further info... | ||
7 | */ | ||
8 | |||
9 | /* Revision history: | ||
10 | * | ||
11 | * 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | ||
12 | * 2000-09-05 Robert Siemer <Robert.Siemer@gmx.de> | ||
13 | * removed unfinished volume control (maybe adding it later again) | ||
14 | * use OSS-mixer; added stereo control | ||
15 | */ | ||
16 | |||
17 | /* What ever you think about the ACI, version 0x07 is not very well! | ||
18 | * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono | ||
19 | * conditions... Robert | ||
20 | */ | ||
21 | |||
22 | #include <linux/module.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/videodev.h> | ||
25 | #include "../../../sound/oss/aci.h" | ||
26 | #include "miropcm20-rds-core.h" | ||
27 | |||
28 | static int radio_nr = -1; | ||
29 | module_param(radio_nr, int, 0); | ||
30 | |||
31 | struct pcm20_device { | ||
32 | unsigned long freq; | ||
33 | int muted; | ||
34 | int stereo; | ||
35 | }; | ||
36 | |||
37 | |||
38 | static int pcm20_mute(struct pcm20_device *dev, unsigned char mute) | ||
39 | { | ||
40 | dev->muted = mute; | ||
41 | return aci_write_cmd(ACI_SET_TUNERMUTE, mute); | ||
42 | } | ||
43 | |||
44 | static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo) | ||
45 | { | ||
46 | dev->stereo = stereo; | ||
47 | return aci_write_cmd(ACI_SET_TUNERMONO, !stereo); | ||
48 | } | ||
49 | |||
50 | static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq) | ||
51 | { | ||
52 | unsigned char freql; | ||
53 | unsigned char freqh; | ||
54 | |||
55 | dev->freq=freq; | ||
56 | |||
57 | freq /= 160; | ||
58 | if (!(aci_version==0x07 || aci_version>=0xb0)) | ||
59 | freq /= 10; /* I don't know exactly which version | ||
60 | * needs this hack */ | ||
61 | freql = freq & 0xff; | ||
62 | freqh = freq >> 8; | ||
63 | |||
64 | aci_rds_cmd(RDS_RESET, NULL, 0); | ||
65 | pcm20_stereo(dev, 1); | ||
66 | |||
67 | return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh); | ||
68 | } | ||
69 | |||
70 | static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal) | ||
71 | { | ||
72 | /* okay, check for signal, stereo and rds here... */ | ||
73 | int i; | ||
74 | unsigned char buf; | ||
75 | |||
76 | if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0) | ||
77 | return i; | ||
78 | pr_debug("check_sig: 0x%x\n", i); | ||
79 | if (i & 0x80) { | ||
80 | /* no signal from tuner */ | ||
81 | *flags=0; | ||
82 | *signal=0; | ||
83 | return 0; | ||
84 | } else | ||
85 | *signal=0xffff; | ||
86 | |||
87 | if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0) | ||
88 | return i; | ||
89 | if (i & 0x40) { | ||
90 | *flags=0; | ||
91 | } else { | ||
92 | /* stereo */ | ||
93 | *flags=VIDEO_TUNER_STEREO_ON; | ||
94 | /* I can't see stereo, when forced to mono */ | ||
95 | dev->stereo=1; | ||
96 | } | ||
97 | |||
98 | if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0) | ||
99 | return i; | ||
100 | if (buf & 1) | ||
101 | /* RDS available */ | ||
102 | *flags|=VIDEO_TUNER_RDS_ON; | ||
103 | else | ||
104 | return 0; | ||
105 | |||
106 | if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0) | ||
107 | return i; | ||
108 | pr_debug("rds-signal: %d\n", buf); | ||
109 | if (buf > 15) { | ||
110 | printk("miropcm20-radio: RX strengths unexpected high...\n"); | ||
111 | buf=15; | ||
112 | } | ||
113 | /* refine signal */ | ||
114 | if ((*signal=SCALE(15, 0xffff, buf))==0) | ||
115 | *signal = 1; | ||
116 | |||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int pcm20_do_ioctl(struct inode *inode, struct file *file, | ||
121 | unsigned int cmd, void *arg) | ||
122 | { | ||
123 | struct video_device *dev = video_devdata(file); | ||
124 | struct pcm20_device *pcm20 = dev->priv; | ||
125 | int i; | ||
126 | |||
127 | switch(cmd) | ||
128 | { | ||
129 | case VIDIOCGCAP: | ||
130 | { | ||
131 | struct video_capability *v = arg; | ||
132 | memset(v,0,sizeof(*v)); | ||
133 | v->type=VID_TYPE_TUNER; | ||
134 | strcpy(v->name, "Miro PCM20"); | ||
135 | v->channels=1; | ||
136 | v->audios=1; | ||
137 | return 0; | ||
138 | } | ||
139 | case VIDIOCGTUNER: | ||
140 | { | ||
141 | struct video_tuner *v = arg; | ||
142 | if(v->tuner) /* Only 1 tuner */ | ||
143 | return -EINVAL; | ||
144 | v->rangelow=87*16000; | ||
145 | v->rangehigh=108*16000; | ||
146 | pcm20_getflags(pcm20, &v->flags, &v->signal); | ||
147 | v->flags|=VIDEO_TUNER_LOW; | ||
148 | v->mode=VIDEO_MODE_AUTO; | ||
149 | strcpy(v->name, "FM"); | ||
150 | return 0; | ||
151 | } | ||
152 | case VIDIOCSTUNER: | ||
153 | { | ||
154 | struct video_tuner *v = arg; | ||
155 | if(v->tuner!=0) | ||
156 | return -EINVAL; | ||
157 | /* Only 1 tuner so no setting needed ! */ | ||
158 | return 0; | ||
159 | } | ||
160 | case VIDIOCGFREQ: | ||
161 | { | ||
162 | unsigned long *freq = arg; | ||
163 | *freq = pcm20->freq; | ||
164 | return 0; | ||
165 | } | ||
166 | case VIDIOCSFREQ: | ||
167 | { | ||
168 | unsigned long *freq = arg; | ||
169 | pcm20->freq = *freq; | ||
170 | i=pcm20_setfreq(pcm20, pcm20->freq); | ||
171 | pr_debug("First view (setfreq): 0x%x\n", i); | ||
172 | return i; | ||
173 | } | ||
174 | case VIDIOCGAUDIO: | ||
175 | { | ||
176 | struct video_audio *v = arg; | ||
177 | memset(v,0, sizeof(*v)); | ||
178 | v->flags=VIDEO_AUDIO_MUTABLE; | ||
179 | if (pcm20->muted) | ||
180 | v->flags|=VIDEO_AUDIO_MUTE; | ||
181 | v->mode=VIDEO_SOUND_STEREO; | ||
182 | if (pcm20->stereo) | ||
183 | v->mode|=VIDEO_SOUND_MONO; | ||
184 | /* v->step=2048; */ | ||
185 | strcpy(v->name, "Radio"); | ||
186 | return 0; | ||
187 | } | ||
188 | case VIDIOCSAUDIO: | ||
189 | { | ||
190 | struct video_audio *v = arg; | ||
191 | if(v->audio) | ||
192 | return -EINVAL; | ||
193 | |||
194 | pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE)); | ||
195 | if(v->flags&VIDEO_SOUND_MONO) | ||
196 | pcm20_stereo(pcm20, 0); | ||
197 | if(v->flags&VIDEO_SOUND_STEREO) | ||
198 | pcm20_stereo(pcm20, 1); | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | default: | ||
203 | return -ENOIOCTLCMD; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | static int pcm20_ioctl(struct inode *inode, struct file *file, | ||
208 | unsigned int cmd, unsigned long arg) | ||
209 | { | ||
210 | return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl); | ||
211 | } | ||
212 | |||
213 | static struct pcm20_device pcm20_unit = { | ||
214 | .freq = 87*16000, | ||
215 | .muted = 1, | ||
216 | }; | ||
217 | |||
218 | static struct file_operations pcm20_fops = { | ||
219 | .owner = THIS_MODULE, | ||
220 | .open = video_exclusive_open, | ||
221 | .release = video_exclusive_release, | ||
222 | .ioctl = pcm20_ioctl, | ||
223 | .llseek = no_llseek, | ||
224 | }; | ||
225 | |||
226 | static struct video_device pcm20_radio = { | ||
227 | .owner = THIS_MODULE, | ||
228 | .name = "Miro PCM 20 radio", | ||
229 | .type = VID_TYPE_TUNER, | ||
230 | .hardware = VID_HARDWARE_RTRACK, | ||
231 | .fops = &pcm20_fops, | ||
232 | .priv = &pcm20_unit | ||
233 | }; | ||
234 | |||
235 | static int __init pcm20_init(void) | ||
236 | { | ||
237 | if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
238 | goto video_register_device; | ||
239 | |||
240 | if(attach_aci_rds()<0) | ||
241 | goto attach_aci_rds; | ||
242 | |||
243 | printk(KERN_INFO "Miro PCM20 radio card driver.\n"); | ||
244 | |||
245 | return 0; | ||
246 | |||
247 | attach_aci_rds: | ||
248 | video_unregister_device(&pcm20_radio); | ||
249 | video_register_device: | ||
250 | return -EINVAL; | ||
251 | } | ||
252 | |||
253 | MODULE_AUTHOR("Ruurd Reitsma"); | ||
254 | MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); | ||
255 | MODULE_LICENSE("GPL"); | ||
256 | |||
257 | static void __exit pcm20_cleanup(void) | ||
258 | { | ||
259 | unload_aci_rds(); | ||
260 | video_unregister_device(&pcm20_radio); | ||
261 | } | ||
262 | |||
263 | module_init(pcm20_init); | ||
264 | module_exit(pcm20_cleanup); | ||
diff --git a/drivers/media/radio/miropcm20-rds-core.c b/drivers/media/radio/miropcm20-rds-core.c new file mode 100644 index 000000000000..a917a90cb5dc --- /dev/null +++ b/drivers/media/radio/miropcm20-rds-core.c | |||
@@ -0,0 +1,210 @@ | |||
1 | /* | ||
2 | * Many thanks to Fred Seidel <seidel@metabox.de>, the | ||
3 | * designer of the RDS decoder hardware. With his help | ||
4 | * I was able to code this driver. | ||
5 | * Thanks also to Norberto Pellicci, Dominic Mounteney | ||
6 | * <DMounteney@pinnaclesys.com> and www.teleauskunft.de | ||
7 | * for good hints on finding Fred. It was somewhat hard | ||
8 | * to locate him here in Germany... [: | ||
9 | * | ||
10 | * Revision history: | ||
11 | * | ||
12 | * 2000-08-09 Robert Siemer <Robert.Siemer@gmx.de> | ||
13 | * RDS support for MiroSound PCM20 radio | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/errno.h> | ||
18 | #include <linux/string.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/slab.h> | ||
21 | #include <asm/semaphore.h> | ||
22 | #include <asm/io.h> | ||
23 | #include "../../../sound/oss/aci.h" | ||
24 | #include "miropcm20-rds-core.h" | ||
25 | |||
26 | #define DEBUG 0 | ||
27 | |||
28 | static struct semaphore aci_rds_sem; | ||
29 | |||
30 | #define RDS_DATASHIFT 2 /* Bit 2 */ | ||
31 | #define RDS_DATAMASK (1 << RDS_DATASHIFT) | ||
32 | #define RDS_BUSYMASK 0x10 /* Bit 4 */ | ||
33 | #define RDS_CLOCKMASK 0x08 /* Bit 3 */ | ||
34 | |||
35 | #define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) | ||
36 | |||
37 | |||
38 | #if DEBUG | ||
39 | static void print_matrix(char array[], unsigned int length) | ||
40 | { | ||
41 | int i, j; | ||
42 | |||
43 | for (i=0; i<length; i++) { | ||
44 | printk(KERN_DEBUG "aci-rds: "); | ||
45 | for (j=7; j>=0; j--) { | ||
46 | printk("%d", (array[i] >> j) & 0x1); | ||
47 | } | ||
48 | if (i%8 == 0) | ||
49 | printk(" byte-border\n"); | ||
50 | else | ||
51 | printk("\n"); | ||
52 | } | ||
53 | } | ||
54 | #endif /* DEBUG */ | ||
55 | |||
56 | static int byte2trans(unsigned char byte, unsigned char sendbuffer[], int size) | ||
57 | { | ||
58 | int i; | ||
59 | |||
60 | if (size != 8) | ||
61 | return -1; | ||
62 | for (i = 7; i >= 0; i--) | ||
63 | sendbuffer[7-i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; | ||
64 | sendbuffer[0] |= RDS_CLOCKMASK; | ||
65 | |||
66 | return 0; | ||
67 | } | ||
68 | |||
69 | static int rds_waitread(void) | ||
70 | { | ||
71 | unsigned char byte; | ||
72 | int i=2000; | ||
73 | |||
74 | do { | ||
75 | byte=inb(RDS_REGISTER); | ||
76 | i--; | ||
77 | } | ||
78 | while ((byte & RDS_BUSYMASK) && i); | ||
79 | |||
80 | if (i) { | ||
81 | #if DEBUG | ||
82 | printk(KERN_DEBUG "rds_waitread()"); | ||
83 | print_matrix(&byte, 1); | ||
84 | #endif | ||
85 | return (byte); | ||
86 | } else { | ||
87 | printk(KERN_WARNING "aci-rds: rds_waitread() timeout...\n"); | ||
88 | return -1; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | /* don't use any ..._nowait() function if you are not sure what you do... */ | ||
93 | |||
94 | static inline void rds_rawwrite_nowait(unsigned char byte) | ||
95 | { | ||
96 | #if DEBUG | ||
97 | printk(KERN_DEBUG "rds_rawwrite()"); | ||
98 | print_matrix(&byte, 1); | ||
99 | #endif | ||
100 | outb(byte, RDS_REGISTER); | ||
101 | } | ||
102 | |||
103 | static int rds_rawwrite(unsigned char byte) | ||
104 | { | ||
105 | if (rds_waitread() >= 0) { | ||
106 | rds_rawwrite_nowait(byte); | ||
107 | return 0; | ||
108 | } else | ||
109 | return -1; | ||
110 | } | ||
111 | |||
112 | static int rds_write(unsigned char cmd) | ||
113 | { | ||
114 | unsigned char sendbuffer[8]; | ||
115 | int i; | ||
116 | |||
117 | if (byte2trans(cmd, sendbuffer, 8) != 0){ | ||
118 | return -1; | ||
119 | } else { | ||
120 | for (i=0; i<8; i++) { | ||
121 | rds_rawwrite(sendbuffer[i]); | ||
122 | } | ||
123 | } | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static int rds_readcycle_nowait(void) | ||
128 | { | ||
129 | rds_rawwrite_nowait(0); | ||
130 | return rds_waitread(); | ||
131 | } | ||
132 | |||
133 | static int rds_readcycle(void) | ||
134 | { | ||
135 | if (rds_rawwrite(0) < 0) | ||
136 | return -1; | ||
137 | return rds_waitread(); | ||
138 | } | ||
139 | |||
140 | static int rds_read(unsigned char databuffer[], int datasize) | ||
141 | { | ||
142 | #define READSIZE (8*datasize) | ||
143 | |||
144 | int i,j; | ||
145 | |||
146 | if (datasize < 1) /* nothing to read */ | ||
147 | return 0; | ||
148 | |||
149 | /* to be able to use rds_readcycle_nowait() | ||
150 | I have to waitread() here */ | ||
151 | if (rds_waitread() < 0) | ||
152 | return -1; | ||
153 | |||
154 | memset(databuffer, 0, datasize); | ||
155 | |||
156 | for (i=0; i< READSIZE; i++) | ||
157 | if((j=rds_readcycle_nowait()) < 0) { | ||
158 | return -1; | ||
159 | } else { | ||
160 | databuffer[i/8]|=(RDS_DATA(j) << (7-(i%8))); | ||
161 | } | ||
162 | |||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static int rds_ack(void) | ||
167 | { | ||
168 | int i=rds_readcycle(); | ||
169 | |||
170 | if (i < 0) | ||
171 | return -1; | ||
172 | if (i & RDS_DATAMASK) { | ||
173 | return 0; /* ACK */ | ||
174 | } else { | ||
175 | printk(KERN_DEBUG "aci-rds: NACK\n"); | ||
176 | return 1; /* NACK */ | ||
177 | } | ||
178 | } | ||
179 | |||
180 | int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize) | ||
181 | { | ||
182 | int ret; | ||
183 | |||
184 | if (down_interruptible(&aci_rds_sem)) | ||
185 | return -EINTR; | ||
186 | |||
187 | rds_write(cmd); | ||
188 | |||
189 | /* RDS_RESET doesn't need further processing */ | ||
190 | if (cmd!=RDS_RESET && (rds_ack() || rds_read(databuffer, datasize))) | ||
191 | ret = -1; | ||
192 | else | ||
193 | ret = 0; | ||
194 | |||
195 | up(&aci_rds_sem); | ||
196 | |||
197 | return ret; | ||
198 | } | ||
199 | EXPORT_SYMBOL(aci_rds_cmd); | ||
200 | |||
201 | int __init attach_aci_rds(void) | ||
202 | { | ||
203 | init_MUTEX(&aci_rds_sem); | ||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | void __exit unload_aci_rds(void) | ||
208 | { | ||
209 | } | ||
210 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/media/radio/miropcm20-rds-core.h b/drivers/media/radio/miropcm20-rds-core.h new file mode 100644 index 000000000000..aeb5761f0469 --- /dev/null +++ b/drivers/media/radio/miropcm20-rds-core.h | |||
@@ -0,0 +1,19 @@ | |||
1 | #ifndef _MIROPCM20_RDS_CORE_H_ | ||
2 | #define _MIROPCM20_RDS_CORE_H_ | ||
3 | |||
4 | extern int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize); | ||
5 | |||
6 | #define RDS_STATUS 0x01 | ||
7 | #define RDS_STATIONNAME 0x02 | ||
8 | #define RDS_TEXT 0x03 | ||
9 | #define RDS_ALTFREQ 0x04 | ||
10 | #define RDS_TIMEDATE 0x05 | ||
11 | #define RDS_PI_CODE 0x06 | ||
12 | #define RDS_PTYTATP 0x07 | ||
13 | #define RDS_RESET 0x08 | ||
14 | #define RDS_RXVALUE 0x09 | ||
15 | |||
16 | extern void __exit unload_aci_rds(void); | ||
17 | extern int __init attach_aci_rds(void); | ||
18 | |||
19 | #endif /* _MIROPCM20_RDS_CORE_H_ */ | ||
diff --git a/drivers/media/radio/miropcm20-rds.c b/drivers/media/radio/miropcm20-rds.c new file mode 100644 index 000000000000..df79d5e0aaed --- /dev/null +++ b/drivers/media/radio/miropcm20-rds.c | |||
@@ -0,0 +1,133 @@ | |||
1 | /* MiroSOUND PCM20 radio rds interface driver | ||
2 | * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de> | ||
3 | * Thanks to Fred Seidel. See miropcm20-rds-core.c for further information. | ||
4 | */ | ||
5 | |||
6 | /* Revision history: | ||
7 | * | ||
8 | * 2001-04-18 Robert Siemer <Robert.Siemer@gmx.de> | ||
9 | * separate file for user interface driver | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/fs.h> | ||
16 | #include <linux/miscdevice.h> | ||
17 | #include <linux/delay.h> | ||
18 | #include <asm/uaccess.h> | ||
19 | #include "miropcm20-rds-core.h" | ||
20 | |||
21 | static char * text_buffer; | ||
22 | static int rds_users = 0; | ||
23 | |||
24 | |||
25 | static int rds_f_open(struct inode *in, struct file *fi) | ||
26 | { | ||
27 | if (rds_users) | ||
28 | return -EBUSY; | ||
29 | |||
30 | rds_users++; | ||
31 | if ((text_buffer=kmalloc(66, GFP_KERNEL)) == 0) { | ||
32 | rds_users--; | ||
33 | printk(KERN_NOTICE "aci-rds: Out of memory by open()...\n"); | ||
34 | return -ENOMEM; | ||
35 | } | ||
36 | |||
37 | return 0; | ||
38 | } | ||
39 | |||
40 | static int rds_f_release(struct inode *in, struct file *fi) | ||
41 | { | ||
42 | kfree(text_buffer); | ||
43 | |||
44 | rds_users--; | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | static void print_matrix(char *ch, char out[]) | ||
49 | { | ||
50 | int j; | ||
51 | |||
52 | for (j=7; j>=0; j--) { | ||
53 | out[7-j] = ((*ch >> j) & 0x1) + '0'; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | static ssize_t rds_f_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) | ||
58 | { | ||
59 | // i = sprintf(text_buffer, "length: %d, offset: %d\n", length, *offset); | ||
60 | |||
61 | char c; | ||
62 | char bits[8]; | ||
63 | |||
64 | msleep(2000); | ||
65 | aci_rds_cmd(RDS_STATUS, &c, 1); | ||
66 | print_matrix(&c, bits); | ||
67 | if (copy_to_user(buffer, bits, 8)) | ||
68 | return -EFAULT; | ||
69 | |||
70 | /* if ((c >> 3) & 1) { | ||
71 | aci_rds_cmd(RDS_STATIONNAME, text_buffer+1, 8); | ||
72 | text_buffer[0] = ' ' ; | ||
73 | text_buffer[9] = '\n'; | ||
74 | return copy_to_user(buffer+8, text_buffer, 10) ? -EFAULT: 18; | ||
75 | } | ||
76 | */ | ||
77 | /* if ((c >> 6) & 1) { | ||
78 | aci_rds_cmd(RDS_PTYTATP, &c, 1); | ||
79 | if ( c & 1) | ||
80 | sprintf(text_buffer, " M"); | ||
81 | else | ||
82 | sprintf(text_buffer, " S"); | ||
83 | if ((c >> 1) & 1) | ||
84 | sprintf(text_buffer+2, " TA"); | ||
85 | else | ||
86 | sprintf(text_buffer+2, " --"); | ||
87 | if ((c >> 7) & 1) | ||
88 | sprintf(text_buffer+5, " TP"); | ||
89 | else | ||
90 | sprintf(text_buffer+5, " --"); | ||
91 | sprintf(text_buffer+8, " %2d\n", (c >> 2) & 0x1f); | ||
92 | return copy_to_user(buffer+8, text_buffer, 12) ? -EFAULT: 20; | ||
93 | } | ||
94 | */ | ||
95 | |||
96 | if ((c >> 4) & 1) { | ||
97 | aci_rds_cmd(RDS_TEXT, text_buffer, 65); | ||
98 | text_buffer[0] = ' ' ; | ||
99 | text_buffer[65] = '\n'; | ||
100 | return copy_to_user(buffer+8, text_buffer,66) ? -EFAULT : 66+8; | ||
101 | } else { | ||
102 | put_user('\n', buffer+8); | ||
103 | return 9; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | static struct file_operations rds_fops = { | ||
108 | .owner = THIS_MODULE, | ||
109 | .read = rds_f_read, | ||
110 | .open = rds_f_open, | ||
111 | .release = rds_f_release | ||
112 | }; | ||
113 | |||
114 | static struct miscdevice rds_miscdev = { | ||
115 | .minor = MISC_DYNAMIC_MINOR, | ||
116 | .name = "radiotext", | ||
117 | .devfs_name = "v4l/rds/radiotext", | ||
118 | .fops = &rds_fops, | ||
119 | }; | ||
120 | |||
121 | static int __init miropcm20_rds_init(void) | ||
122 | { | ||
123 | return misc_register(&rds_miscdev); | ||
124 | } | ||
125 | |||
126 | static void __exit miropcm20_rds_cleanup(void) | ||
127 | { | ||
128 | misc_deregister(&rds_miscdev); | ||
129 | } | ||
130 | |||
131 | module_init(miropcm20_rds_init); | ||
132 | module_exit(miropcm20_rds_cleanup); | ||
133 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c new file mode 100644 index 000000000000..8b4ad70dd1b2 --- /dev/null +++ b/drivers/media/radio/radio-aimslab.c | |||
@@ -0,0 +1,368 @@ | |||
1 | /* radiotrack (radioreveal) driver for Linux radio support | ||
2 | * (c) 1997 M. Kirkwood | ||
3 | * Converted to new API by Alan Cox <Alan.Cox@linux.org> | ||
4 | * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> | ||
5 | * | ||
6 | * History: | ||
7 | * 1999-02-24 Russell Kroll <rkroll@exploits.org> | ||
8 | * Fine tuning/VIDEO_TUNER_LOW | ||
9 | * Frequency range expanded to start at 87 MHz | ||
10 | * | ||
11 | * TODO: Allow for more than one of these foolish entities :-) | ||
12 | * | ||
13 | * Notes on the hardware (reverse engineered from other peoples' | ||
14 | * reverse engineering of AIMS' code :-) | ||
15 | * | ||
16 | * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | ||
17 | * | ||
18 | * The signal strength query is unsurprisingly inaccurate. And it seems | ||
19 | * to indicate that (on my card, at least) the frequency setting isn't | ||
20 | * too great. (I have to tune up .025MHz from what the freq should be | ||
21 | * to get a report that the thing is tuned.) | ||
22 | * | ||
23 | * Volume control is (ugh) analogue: | ||
24 | * out(port, start_increasing_volume); | ||
25 | * wait(a_wee_while); | ||
26 | * out(port, stop_changing_the_volume); | ||
27 | * | ||
28 | */ | ||
29 | |||
30 | #include <linux/module.h> /* Modules */ | ||
31 | #include <linux/init.h> /* Initdata */ | ||
32 | #include <linux/ioport.h> /* check_region, request_region */ | ||
33 | #include <linux/delay.h> /* udelay */ | ||
34 | #include <asm/io.h> /* outb, outb_p */ | ||
35 | #include <asm/uaccess.h> /* copy to/from user */ | ||
36 | #include <linux/videodev.h> /* kernel radio structs */ | ||
37 | #include <linux/config.h> /* CONFIG_RADIO_RTRACK_PORT */ | ||
38 | #include <asm/semaphore.h> /* Lock for the I/O */ | ||
39 | |||
40 | #ifndef CONFIG_RADIO_RTRACK_PORT | ||
41 | #define CONFIG_RADIO_RTRACK_PORT -1 | ||
42 | #endif | ||
43 | |||
44 | static int io = CONFIG_RADIO_RTRACK_PORT; | ||
45 | static int radio_nr = -1; | ||
46 | static struct semaphore lock; | ||
47 | |||
48 | struct rt_device | ||
49 | { | ||
50 | int port; | ||
51 | int curvol; | ||
52 | unsigned long curfreq; | ||
53 | int muted; | ||
54 | }; | ||
55 | |||
56 | |||
57 | /* local things */ | ||
58 | |||
59 | static void sleep_delay(long n) | ||
60 | { | ||
61 | /* Sleep nicely for 'n' uS */ | ||
62 | int d=n/(1000000/HZ); | ||
63 | if(!d) | ||
64 | udelay(n); | ||
65 | else | ||
66 | msleep(jiffies_to_msecs(d)); | ||
67 | } | ||
68 | |||
69 | static void rt_decvol(void) | ||
70 | { | ||
71 | outb(0x58, io); /* volume down + sigstr + on */ | ||
72 | sleep_delay(100000); | ||
73 | outb(0xd8, io); /* volume steady + sigstr + on */ | ||
74 | } | ||
75 | |||
76 | static void rt_incvol(void) | ||
77 | { | ||
78 | outb(0x98, io); /* volume up + sigstr + on */ | ||
79 | sleep_delay(100000); | ||
80 | outb(0xd8, io); /* volume steady + sigstr + on */ | ||
81 | } | ||
82 | |||
83 | static void rt_mute(struct rt_device *dev) | ||
84 | { | ||
85 | dev->muted = 1; | ||
86 | down(&lock); | ||
87 | outb(0xd0, io); /* volume steady, off */ | ||
88 | up(&lock); | ||
89 | } | ||
90 | |||
91 | static int rt_setvol(struct rt_device *dev, int vol) | ||
92 | { | ||
93 | int i; | ||
94 | |||
95 | down(&lock); | ||
96 | |||
97 | if(vol == dev->curvol) { /* requested volume = current */ | ||
98 | if (dev->muted) { /* user is unmuting the card */ | ||
99 | dev->muted = 0; | ||
100 | outb (0xd8, io); /* enable card */ | ||
101 | } | ||
102 | up(&lock); | ||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | if(vol == 0) { /* volume = 0 means mute the card */ | ||
107 | outb(0x48, io); /* volume down but still "on" */ | ||
108 | sleep_delay(2000000); /* make sure it's totally down */ | ||
109 | outb(0xd0, io); /* volume steady, off */ | ||
110 | dev->curvol = 0; /* track the volume state! */ | ||
111 | up(&lock); | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | dev->muted = 0; | ||
116 | if(vol > dev->curvol) | ||
117 | for(i = dev->curvol; i < vol; i++) | ||
118 | rt_incvol(); | ||
119 | else | ||
120 | for(i = dev->curvol; i > vol; i--) | ||
121 | rt_decvol(); | ||
122 | |||
123 | dev->curvol = vol; | ||
124 | up(&lock); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | /* the 128+64 on these outb's is to keep the volume stable while tuning | ||
129 | * without them, the volume _will_ creep up with each frequency change | ||
130 | * and bit 4 (+16) is to keep the signal strength meter enabled | ||
131 | */ | ||
132 | |||
133 | static void send_0_byte(int port, struct rt_device *dev) | ||
134 | { | ||
135 | if ((dev->curvol == 0) || (dev->muted)) { | ||
136 | outb_p(128+64+16+ 1, port); /* wr-enable + data low */ | ||
137 | outb_p(128+64+16+2+1, port); /* clock */ | ||
138 | } | ||
139 | else { | ||
140 | outb_p(128+64+16+8+ 1, port); /* on + wr-enable + data low */ | ||
141 | outb_p(128+64+16+8+2+1, port); /* clock */ | ||
142 | } | ||
143 | sleep_delay(1000); | ||
144 | } | ||
145 | |||
146 | static void send_1_byte(int port, struct rt_device *dev) | ||
147 | { | ||
148 | if ((dev->curvol == 0) || (dev->muted)) { | ||
149 | outb_p(128+64+16+4 +1, port); /* wr-enable+data high */ | ||
150 | outb_p(128+64+16+4+2+1, port); /* clock */ | ||
151 | } | ||
152 | else { | ||
153 | outb_p(128+64+16+8+4 +1, port); /* on+wr-enable+data high */ | ||
154 | outb_p(128+64+16+8+4+2+1, port); /* clock */ | ||
155 | } | ||
156 | |||
157 | sleep_delay(1000); | ||
158 | } | ||
159 | |||
160 | static int rt_setfreq(struct rt_device *dev, unsigned long freq) | ||
161 | { | ||
162 | int i; | ||
163 | |||
164 | /* adapted from radio-aztech.c */ | ||
165 | |||
166 | /* now uses VIDEO_TUNER_LOW for fine tuning */ | ||
167 | |||
168 | freq += 171200; /* Add 10.7 MHz IF */ | ||
169 | freq /= 800; /* Convert to 50 kHz units */ | ||
170 | |||
171 | down(&lock); /* Stop other ops interfering */ | ||
172 | |||
173 | send_0_byte (io, dev); /* 0: LSB of frequency */ | ||
174 | |||
175 | for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ | ||
176 | if (freq & (1 << i)) | ||
177 | send_1_byte (io, dev); | ||
178 | else | ||
179 | send_0_byte (io, dev); | ||
180 | |||
181 | send_0_byte (io, dev); /* 14: test bit - always 0 */ | ||
182 | send_0_byte (io, dev); /* 15: test bit - always 0 */ | ||
183 | |||
184 | send_0_byte (io, dev); /* 16: band data 0 - always 0 */ | ||
185 | send_0_byte (io, dev); /* 17: band data 1 - always 0 */ | ||
186 | send_0_byte (io, dev); /* 18: band data 2 - always 0 */ | ||
187 | send_0_byte (io, dev); /* 19: time base - always 0 */ | ||
188 | |||
189 | send_0_byte (io, dev); /* 20: spacing (0 = 25 kHz) */ | ||
190 | send_1_byte (io, dev); /* 21: spacing (1 = 25 kHz) */ | ||
191 | send_0_byte (io, dev); /* 22: spacing (0 = 25 kHz) */ | ||
192 | send_1_byte (io, dev); /* 23: AM/FM (FM = 1, always) */ | ||
193 | |||
194 | if ((dev->curvol == 0) || (dev->muted)) | ||
195 | outb (0xd0, io); /* volume steady + sigstr */ | ||
196 | else | ||
197 | outb (0xd8, io); /* volume steady + sigstr + on */ | ||
198 | |||
199 | up(&lock); | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int rt_getsigstr(struct rt_device *dev) | ||
205 | { | ||
206 | if (inb(io) & 2) /* bit set = no signal present */ | ||
207 | return 0; | ||
208 | return 1; /* signal present */ | ||
209 | } | ||
210 | |||
211 | static int rt_do_ioctl(struct inode *inode, struct file *file, | ||
212 | unsigned int cmd, void *arg) | ||
213 | { | ||
214 | struct video_device *dev = video_devdata(file); | ||
215 | struct rt_device *rt=dev->priv; | ||
216 | |||
217 | switch(cmd) | ||
218 | { | ||
219 | case VIDIOCGCAP: | ||
220 | { | ||
221 | struct video_capability *v = arg; | ||
222 | memset(v,0,sizeof(*v)); | ||
223 | v->type=VID_TYPE_TUNER; | ||
224 | v->channels=1; | ||
225 | v->audios=1; | ||
226 | strcpy(v->name, "RadioTrack"); | ||
227 | return 0; | ||
228 | } | ||
229 | case VIDIOCGTUNER: | ||
230 | { | ||
231 | struct video_tuner *v = arg; | ||
232 | if(v->tuner) /* Only 1 tuner */ | ||
233 | return -EINVAL; | ||
234 | v->rangelow=(87*16000); | ||
235 | v->rangehigh=(108*16000); | ||
236 | v->flags=VIDEO_TUNER_LOW; | ||
237 | v->mode=VIDEO_MODE_AUTO; | ||
238 | strcpy(v->name, "FM"); | ||
239 | v->signal=0xFFFF*rt_getsigstr(rt); | ||
240 | return 0; | ||
241 | } | ||
242 | case VIDIOCSTUNER: | ||
243 | { | ||
244 | struct video_tuner *v = arg; | ||
245 | if(v->tuner!=0) | ||
246 | return -EINVAL; | ||
247 | /* Only 1 tuner so no setting needed ! */ | ||
248 | return 0; | ||
249 | } | ||
250 | case VIDIOCGFREQ: | ||
251 | { | ||
252 | unsigned long *freq = arg; | ||
253 | *freq = rt->curfreq; | ||
254 | return 0; | ||
255 | } | ||
256 | case VIDIOCSFREQ: | ||
257 | { | ||
258 | unsigned long *freq = arg; | ||
259 | rt->curfreq = *freq; | ||
260 | rt_setfreq(rt, rt->curfreq); | ||
261 | return 0; | ||
262 | } | ||
263 | case VIDIOCGAUDIO: | ||
264 | { | ||
265 | struct video_audio *v = arg; | ||
266 | memset(v,0, sizeof(*v)); | ||
267 | v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; | ||
268 | v->volume=rt->curvol * 6554; | ||
269 | v->step=6554; | ||
270 | strcpy(v->name, "Radio"); | ||
271 | return 0; | ||
272 | } | ||
273 | case VIDIOCSAUDIO: | ||
274 | { | ||
275 | struct video_audio *v = arg; | ||
276 | if(v->audio) | ||
277 | return -EINVAL; | ||
278 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
279 | rt_mute(rt); | ||
280 | else | ||
281 | rt_setvol(rt,v->volume/6554); | ||
282 | return 0; | ||
283 | } | ||
284 | default: | ||
285 | return -ENOIOCTLCMD; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | static int rt_ioctl(struct inode *inode, struct file *file, | ||
290 | unsigned int cmd, unsigned long arg) | ||
291 | { | ||
292 | return video_usercopy(inode, file, cmd, arg, rt_do_ioctl); | ||
293 | } | ||
294 | |||
295 | static struct rt_device rtrack_unit; | ||
296 | |||
297 | static struct file_operations rtrack_fops = { | ||
298 | .owner = THIS_MODULE, | ||
299 | .open = video_exclusive_open, | ||
300 | .release = video_exclusive_release, | ||
301 | .ioctl = rt_ioctl, | ||
302 | .llseek = no_llseek, | ||
303 | }; | ||
304 | |||
305 | static struct video_device rtrack_radio= | ||
306 | { | ||
307 | .owner = THIS_MODULE, | ||
308 | .name = "RadioTrack radio", | ||
309 | .type = VID_TYPE_TUNER, | ||
310 | .hardware = VID_HARDWARE_RTRACK, | ||
311 | .fops = &rtrack_fops, | ||
312 | }; | ||
313 | |||
314 | static int __init rtrack_init(void) | ||
315 | { | ||
316 | if(io==-1) | ||
317 | { | ||
318 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
319 | return -EINVAL; | ||
320 | } | ||
321 | |||
322 | if (!request_region(io, 2, "rtrack")) | ||
323 | { | ||
324 | printk(KERN_ERR "rtrack: port 0x%x already in use\n", io); | ||
325 | return -EBUSY; | ||
326 | } | ||
327 | |||
328 | rtrack_radio.priv=&rtrack_unit; | ||
329 | |||
330 | if(video_register_device(&rtrack_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
331 | { | ||
332 | release_region(io, 2); | ||
333 | return -EINVAL; | ||
334 | } | ||
335 | printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n"); | ||
336 | |||
337 | /* Set up the I/O locking */ | ||
338 | |||
339 | init_MUTEX(&lock); | ||
340 | |||
341 | /* mute card - prevents noisy bootups */ | ||
342 | |||
343 | /* this ensures that the volume is all the way down */ | ||
344 | outb(0x48, io); /* volume down but still "on" */ | ||
345 | sleep_delay(2000000); /* make sure it's totally down */ | ||
346 | outb(0xc0, io); /* steady volume, mute card */ | ||
347 | rtrack_unit.curvol = 0; | ||
348 | |||
349 | return 0; | ||
350 | } | ||
351 | |||
352 | MODULE_AUTHOR("M.Kirkwood"); | ||
353 | MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); | ||
354 | MODULE_LICENSE("GPL"); | ||
355 | |||
356 | module_param(io, int, 0); | ||
357 | MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); | ||
358 | module_param(radio_nr, int, 0); | ||
359 | |||
360 | static void __exit cleanup_rtrack_module(void) | ||
361 | { | ||
362 | video_unregister_device(&rtrack_radio); | ||
363 | release_region(io,2); | ||
364 | } | ||
365 | |||
366 | module_init(rtrack_init); | ||
367 | module_exit(cleanup_rtrack_module); | ||
368 | |||
diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c new file mode 100644 index 000000000000..013c835ed910 --- /dev/null +++ b/drivers/media/radio/radio-aztech.c | |||
@@ -0,0 +1,315 @@ | |||
1 | /* radio-aztech.c - Aztech radio card driver for Linux 2.2 | ||
2 | * | ||
3 | * Adapted to support the Video for Linux API by | ||
4 | * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: | ||
5 | * | ||
6 | * Quay Ly | ||
7 | * Donald Song | ||
8 | * Jason Lewis (jlewis@twilight.vtc.vsc.edu) | ||
9 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) | ||
10 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) | ||
11 | * | ||
12 | * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ | ||
13 | * along with more information on the card itself. | ||
14 | * | ||
15 | * History: | ||
16 | * 1999-02-24 Russell Kroll <rkroll@exploits.org> | ||
17 | * Fine tuning/VIDEO_TUNER_LOW | ||
18 | * Range expanded to 87-108 MHz (from 87.9-107.8) | ||
19 | * | ||
20 | * Notable changes from the original source: | ||
21 | * - includes stripped down to the essentials | ||
22 | * - for loops used as delays replaced with udelay() | ||
23 | * - #defines removed, changed to static values | ||
24 | * - tuning structure changed - no more character arrays, other changes | ||
25 | */ | ||
26 | |||
27 | #include <linux/module.h> /* Modules */ | ||
28 | #include <linux/init.h> /* Initdata */ | ||
29 | #include <linux/ioport.h> /* check_region, request_region */ | ||
30 | #include <linux/delay.h> /* udelay */ | ||
31 | #include <asm/io.h> /* outb, outb_p */ | ||
32 | #include <asm/uaccess.h> /* copy to/from user */ | ||
33 | #include <linux/videodev.h> /* kernel radio structs */ | ||
34 | #include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */ | ||
35 | |||
36 | /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ | ||
37 | |||
38 | #ifndef CONFIG_RADIO_AZTECH_PORT | ||
39 | #define CONFIG_RADIO_AZTECH_PORT -1 | ||
40 | #endif | ||
41 | |||
42 | static int io = CONFIG_RADIO_AZTECH_PORT; | ||
43 | static int radio_nr = -1; | ||
44 | static int radio_wait_time = 1000; | ||
45 | static struct semaphore lock; | ||
46 | |||
47 | struct az_device | ||
48 | { | ||
49 | int curvol; | ||
50 | unsigned long curfreq; | ||
51 | int stereo; | ||
52 | }; | ||
53 | |||
54 | static int volconvert(int level) | ||
55 | { | ||
56 | level>>=14; /* Map 16bits down to 2 bit */ | ||
57 | level&=3; | ||
58 | |||
59 | /* convert to card-friendly values */ | ||
60 | switch (level) | ||
61 | { | ||
62 | case 0: | ||
63 | return 0; | ||
64 | case 1: | ||
65 | return 1; | ||
66 | case 2: | ||
67 | return 4; | ||
68 | case 3: | ||
69 | return 5; | ||
70 | } | ||
71 | return 0; /* Quieten gcc */ | ||
72 | } | ||
73 | |||
74 | static void send_0_byte (struct az_device *dev) | ||
75 | { | ||
76 | udelay(radio_wait_time); | ||
77 | outb_p(2+volconvert(dev->curvol), io); | ||
78 | outb_p(64+2+volconvert(dev->curvol), io); | ||
79 | } | ||
80 | |||
81 | static void send_1_byte (struct az_device *dev) | ||
82 | { | ||
83 | udelay (radio_wait_time); | ||
84 | outb_p(128+2+volconvert(dev->curvol), io); | ||
85 | outb_p(128+64+2+volconvert(dev->curvol), io); | ||
86 | } | ||
87 | |||
88 | static int az_setvol(struct az_device *dev, int vol) | ||
89 | { | ||
90 | down(&lock); | ||
91 | outb (volconvert(vol), io); | ||
92 | up(&lock); | ||
93 | return 0; | ||
94 | } | ||
95 | |||
96 | /* thanks to Michael Dwyer for giving me a dose of clues in | ||
97 | * the signal strength department.. | ||
98 | * | ||
99 | * This card has a stereo bit - bit 0 set = mono, not set = stereo | ||
100 | * It also has a "signal" bit - bit 1 set = bad signal, not set = good | ||
101 | * | ||
102 | */ | ||
103 | |||
104 | static int az_getsigstr(struct az_device *dev) | ||
105 | { | ||
106 | if (inb(io) & 2) /* bit set = no signal present */ | ||
107 | return 0; | ||
108 | return 1; /* signal present */ | ||
109 | } | ||
110 | |||
111 | static int az_getstereo(struct az_device *dev) | ||
112 | { | ||
113 | if (inb(io) & 1) /* bit set = mono */ | ||
114 | return 0; | ||
115 | return 1; /* stereo */ | ||
116 | } | ||
117 | |||
118 | static int az_setfreq(struct az_device *dev, unsigned long frequency) | ||
119 | { | ||
120 | int i; | ||
121 | |||
122 | frequency += 171200; /* Add 10.7 MHz IF */ | ||
123 | frequency /= 800; /* Convert to 50 kHz units */ | ||
124 | |||
125 | down(&lock); | ||
126 | |||
127 | send_0_byte (dev); /* 0: LSB of frequency */ | ||
128 | |||
129 | for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ | ||
130 | if (frequency & (1 << i)) | ||
131 | send_1_byte (dev); | ||
132 | else | ||
133 | send_0_byte (dev); | ||
134 | |||
135 | send_0_byte (dev); /* 14: test bit - always 0 */ | ||
136 | send_0_byte (dev); /* 15: test bit - always 0 */ | ||
137 | send_0_byte (dev); /* 16: band data 0 - always 0 */ | ||
138 | if (dev->stereo) /* 17: stereo (1 to enable) */ | ||
139 | send_1_byte (dev); | ||
140 | else | ||
141 | send_0_byte (dev); | ||
142 | |||
143 | send_1_byte (dev); /* 18: band data 1 - unknown */ | ||
144 | send_0_byte (dev); /* 19: time base - always 0 */ | ||
145 | send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */ | ||
146 | send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */ | ||
147 | send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */ | ||
148 | send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */ | ||
149 | |||
150 | /* latch frequency */ | ||
151 | |||
152 | udelay (radio_wait_time); | ||
153 | outb_p(128+64+volconvert(dev->curvol), io); | ||
154 | |||
155 | up(&lock); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int az_do_ioctl(struct inode *inode, struct file *file, | ||
161 | unsigned int cmd, void *arg) | ||
162 | { | ||
163 | struct video_device *dev = video_devdata(file); | ||
164 | struct az_device *az = dev->priv; | ||
165 | |||
166 | switch(cmd) | ||
167 | { | ||
168 | case VIDIOCGCAP: | ||
169 | { | ||
170 | struct video_capability *v = arg; | ||
171 | memset(v,0,sizeof(*v)); | ||
172 | v->type=VID_TYPE_TUNER; | ||
173 | v->channels=1; | ||
174 | v->audios=1; | ||
175 | strcpy(v->name, "Aztech Radio"); | ||
176 | return 0; | ||
177 | } | ||
178 | case VIDIOCGTUNER: | ||
179 | { | ||
180 | struct video_tuner *v = arg; | ||
181 | if(v->tuner) /* Only 1 tuner */ | ||
182 | return -EINVAL; | ||
183 | v->rangelow=(87*16000); | ||
184 | v->rangehigh=(108*16000); | ||
185 | v->flags=VIDEO_TUNER_LOW; | ||
186 | v->mode=VIDEO_MODE_AUTO; | ||
187 | v->signal=0xFFFF*az_getsigstr(az); | ||
188 | if(az_getstereo(az)) | ||
189 | v->flags|=VIDEO_TUNER_STEREO_ON; | ||
190 | strcpy(v->name, "FM"); | ||
191 | return 0; | ||
192 | } | ||
193 | case VIDIOCSTUNER: | ||
194 | { | ||
195 | struct video_tuner *v = arg; | ||
196 | if(v->tuner!=0) | ||
197 | return -EINVAL; | ||
198 | return 0; | ||
199 | } | ||
200 | case VIDIOCGFREQ: | ||
201 | { | ||
202 | unsigned long *freq = arg; | ||
203 | *freq = az->curfreq; | ||
204 | return 0; | ||
205 | } | ||
206 | case VIDIOCSFREQ: | ||
207 | { | ||
208 | unsigned long *freq = arg; | ||
209 | az->curfreq = *freq; | ||
210 | az_setfreq(az, az->curfreq); | ||
211 | return 0; | ||
212 | } | ||
213 | case VIDIOCGAUDIO: | ||
214 | { | ||
215 | struct video_audio *v = arg; | ||
216 | memset(v,0, sizeof(*v)); | ||
217 | v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; | ||
218 | if(az->stereo) | ||
219 | v->mode=VIDEO_SOUND_STEREO; | ||
220 | else | ||
221 | v->mode=VIDEO_SOUND_MONO; | ||
222 | v->volume=az->curvol; | ||
223 | v->step=16384; | ||
224 | strcpy(v->name, "Radio"); | ||
225 | return 0; | ||
226 | } | ||
227 | case VIDIOCSAUDIO: | ||
228 | { | ||
229 | struct video_audio *v = arg; | ||
230 | if(v->audio) | ||
231 | return -EINVAL; | ||
232 | az->curvol=v->volume; | ||
233 | |||
234 | az->stereo=(v->mode&VIDEO_SOUND_STEREO)?1:0; | ||
235 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
236 | az_setvol(az,0); | ||
237 | else | ||
238 | az_setvol(az,az->curvol); | ||
239 | return 0; | ||
240 | } | ||
241 | default: | ||
242 | return -ENOIOCTLCMD; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | static int az_ioctl(struct inode *inode, struct file *file, | ||
247 | unsigned int cmd, unsigned long arg) | ||
248 | { | ||
249 | return video_usercopy(inode, file, cmd, arg, az_do_ioctl); | ||
250 | } | ||
251 | |||
252 | static struct az_device aztech_unit; | ||
253 | |||
254 | static struct file_operations aztech_fops = { | ||
255 | .owner = THIS_MODULE, | ||
256 | .open = video_exclusive_open, | ||
257 | .release = video_exclusive_release, | ||
258 | .ioctl = az_ioctl, | ||
259 | .llseek = no_llseek, | ||
260 | }; | ||
261 | |||
262 | static struct video_device aztech_radio= | ||
263 | { | ||
264 | .owner = THIS_MODULE, | ||
265 | .name = "Aztech radio", | ||
266 | .type = VID_TYPE_TUNER, | ||
267 | .hardware = VID_HARDWARE_AZTECH, | ||
268 | .fops = &aztech_fops, | ||
269 | }; | ||
270 | |||
271 | static int __init aztech_init(void) | ||
272 | { | ||
273 | if(io==-1) | ||
274 | { | ||
275 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
276 | return -EINVAL; | ||
277 | } | ||
278 | |||
279 | if (!request_region(io, 2, "aztech")) | ||
280 | { | ||
281 | printk(KERN_ERR "aztech: port 0x%x already in use\n", io); | ||
282 | return -EBUSY; | ||
283 | } | ||
284 | |||
285 | init_MUTEX(&lock); | ||
286 | aztech_radio.priv=&aztech_unit; | ||
287 | |||
288 | if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
289 | { | ||
290 | release_region(io,2); | ||
291 | return -EINVAL; | ||
292 | } | ||
293 | |||
294 | printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); | ||
295 | /* mute card - prevents noisy bootups */ | ||
296 | outb (0, io); | ||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); | ||
301 | MODULE_DESCRIPTION("A driver for the Aztech radio card."); | ||
302 | MODULE_LICENSE("GPL"); | ||
303 | |||
304 | module_param(io, int, 0); | ||
305 | module_param(radio_nr, int, 0); | ||
306 | MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); | ||
307 | |||
308 | static void __exit aztech_cleanup(void) | ||
309 | { | ||
310 | video_unregister_device(&aztech_radio); | ||
311 | release_region(io,2); | ||
312 | } | ||
313 | |||
314 | module_init(aztech_init); | ||
315 | module_exit(aztech_cleanup); | ||
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c new file mode 100644 index 000000000000..53d399b6652b --- /dev/null +++ b/drivers/media/radio/radio-cadet.c | |||
@@ -0,0 +1,620 @@ | |||
1 | /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card | ||
2 | * | ||
3 | * by Fred Gleason <fredg@wava.com> | ||
4 | * Version 0.3.3 | ||
5 | * | ||
6 | * (Loosely) based on code for the Aztech radio card by | ||
7 | * | ||
8 | * Russell Kroll (rkroll@exploits.org) | ||
9 | * Quay Ly | ||
10 | * Donald Song | ||
11 | * Jason Lewis (jlewis@twilight.vtc.vsc.edu) | ||
12 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) | ||
13 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) | ||
14 | * | ||
15 | * History: | ||
16 | * 2000-04-29 Russell Kroll <rkroll@exploits.org> | ||
17 | * Added ISAPnP detection for Linux 2.3/2.4 | ||
18 | * | ||
19 | * 2001-01-10 Russell Kroll <rkroll@exploits.org> | ||
20 | * Removed dead CONFIG_RADIO_CADET_PORT code | ||
21 | * PnP detection on load is now default (no args necessary) | ||
22 | * | ||
23 | * 2002-01-17 Adam Belay <ambx1@neo.rr.com> | ||
24 | * Updated to latest pnp code | ||
25 | * | ||
26 | * 2003-01-31 Alan Cox <alan@redhat.com> | ||
27 | * Cleaned up locking, delay code, general odds and ends | ||
28 | */ | ||
29 | |||
30 | #include <linux/module.h> /* Modules */ | ||
31 | #include <linux/init.h> /* Initdata */ | ||
32 | #include <linux/ioport.h> /* check_region, request_region */ | ||
33 | #include <linux/delay.h> /* udelay */ | ||
34 | #include <asm/io.h> /* outb, outb_p */ | ||
35 | #include <asm/uaccess.h> /* copy to/from user */ | ||
36 | #include <linux/videodev.h> /* kernel radio structs */ | ||
37 | #include <linux/param.h> | ||
38 | #include <linux/pnp.h> | ||
39 | |||
40 | #define RDS_BUFFER 256 | ||
41 | |||
42 | static int io=-1; /* default to isapnp activation */ | ||
43 | static int radio_nr = -1; | ||
44 | static int users=0; | ||
45 | static int curtuner=0; | ||
46 | static int tunestat=0; | ||
47 | static int sigstrength=0; | ||
48 | static wait_queue_head_t read_queue; | ||
49 | static struct timer_list readtimer; | ||
50 | static __u8 rdsin=0,rdsout=0,rdsstat=0; | ||
51 | static unsigned char rdsbuf[RDS_BUFFER]; | ||
52 | static spinlock_t cadet_io_lock; | ||
53 | |||
54 | static int cadet_probe(void); | ||
55 | |||
56 | /* | ||
57 | * Signal Strength Threshold Values | ||
58 | * The V4L API spec does not define any particular unit for the signal | ||
59 | * strength value. These values are in microvolts of RF at the tuner's input. | ||
60 | */ | ||
61 | static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}}; | ||
62 | |||
63 | static int cadet_getrds(void) | ||
64 | { | ||
65 | int rdsstat=0; | ||
66 | |||
67 | spin_lock(&cadet_io_lock); | ||
68 | outb(3,io); /* Select Decoder Control/Status */ | ||
69 | outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */ | ||
70 | spin_unlock(&cadet_io_lock); | ||
71 | |||
72 | msleep(100); | ||
73 | |||
74 | spin_lock(&cadet_io_lock); | ||
75 | outb(3,io); /* Select Decoder Control/Status */ | ||
76 | if((inb(io+1)&0x80)!=0) { | ||
77 | rdsstat|=VIDEO_TUNER_RDS_ON; | ||
78 | } | ||
79 | if((inb(io+1)&0x10)!=0) { | ||
80 | rdsstat|=VIDEO_TUNER_MBS_ON; | ||
81 | } | ||
82 | spin_unlock(&cadet_io_lock); | ||
83 | return rdsstat; | ||
84 | } | ||
85 | |||
86 | static int cadet_getstereo(void) | ||
87 | { | ||
88 | int ret = 0; | ||
89 | if(curtuner != 0) /* Only FM has stereo capability! */ | ||
90 | return 0; | ||
91 | |||
92 | spin_lock(&cadet_io_lock); | ||
93 | outb(7,io); /* Select tuner control */ | ||
94 | if( (inb(io+1) & 0x40) == 0) | ||
95 | ret = 1; | ||
96 | spin_unlock(&cadet_io_lock); | ||
97 | return ret; | ||
98 | } | ||
99 | |||
100 | static unsigned cadet_gettune(void) | ||
101 | { | ||
102 | int curvol,i; | ||
103 | unsigned fifo=0; | ||
104 | |||
105 | /* | ||
106 | * Prepare for read | ||
107 | */ | ||
108 | |||
109 | spin_lock(&cadet_io_lock); | ||
110 | |||
111 | outb(7,io); /* Select tuner control */ | ||
112 | curvol=inb(io+1); /* Save current volume/mute setting */ | ||
113 | outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */ | ||
114 | tunestat=0xffff; | ||
115 | |||
116 | /* | ||
117 | * Read the shift register | ||
118 | */ | ||
119 | for(i=0;i<25;i++) { | ||
120 | fifo=(fifo<<1)|((inb(io+1)>>7)&0x01); | ||
121 | if(i<24) { | ||
122 | outb(0x01,io+1); | ||
123 | tunestat&=inb(io+1); | ||
124 | outb(0x00,io+1); | ||
125 | } | ||
126 | } | ||
127 | |||
128 | /* | ||
129 | * Restore volume/mute setting | ||
130 | */ | ||
131 | outb(curvol,io+1); | ||
132 | spin_unlock(&cadet_io_lock); | ||
133 | |||
134 | return fifo; | ||
135 | } | ||
136 | |||
137 | static unsigned cadet_getfreq(void) | ||
138 | { | ||
139 | int i; | ||
140 | unsigned freq=0,test,fifo=0; | ||
141 | |||
142 | /* | ||
143 | * Read current tuning | ||
144 | */ | ||
145 | fifo=cadet_gettune(); | ||
146 | |||
147 | /* | ||
148 | * Convert to actual frequency | ||
149 | */ | ||
150 | if(curtuner==0) { /* FM */ | ||
151 | test=12500; | ||
152 | for(i=0;i<14;i++) { | ||
153 | if((fifo&0x01)!=0) { | ||
154 | freq+=test; | ||
155 | } | ||
156 | test=test<<1; | ||
157 | fifo=fifo>>1; | ||
158 | } | ||
159 | freq-=10700000; /* IF frequency is 10.7 MHz */ | ||
160 | freq=(freq*16)/1000000; /* Make it 1/16 MHz */ | ||
161 | } | ||
162 | if(curtuner==1) { /* AM */ | ||
163 | freq=((fifo&0x7fff)-2010)*16; | ||
164 | } | ||
165 | |||
166 | return freq; | ||
167 | } | ||
168 | |||
169 | static void cadet_settune(unsigned fifo) | ||
170 | { | ||
171 | int i; | ||
172 | unsigned test; | ||
173 | |||
174 | spin_lock(&cadet_io_lock); | ||
175 | |||
176 | outb(7,io); /* Select tuner control */ | ||
177 | /* | ||
178 | * Write the shift register | ||
179 | */ | ||
180 | test=0; | ||
181 | test=(fifo>>23)&0x02; /* Align data for SDO */ | ||
182 | test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */ | ||
183 | outb(7,io); /* Select tuner control */ | ||
184 | outb(test,io+1); /* Initialize for write */ | ||
185 | for(i=0;i<25;i++) { | ||
186 | test|=0x01; /* Toggle SCK High */ | ||
187 | outb(test,io+1); | ||
188 | test&=0xfe; /* Toggle SCK Low */ | ||
189 | outb(test,io+1); | ||
190 | fifo=fifo<<1; /* Prepare the next bit */ | ||
191 | test=0x1c|((fifo>>23)&0x02); | ||
192 | outb(test,io+1); | ||
193 | } | ||
194 | spin_unlock(&cadet_io_lock); | ||
195 | } | ||
196 | |||
197 | static void cadet_setfreq(unsigned freq) | ||
198 | { | ||
199 | unsigned fifo; | ||
200 | int i,j,test; | ||
201 | int curvol; | ||
202 | |||
203 | /* | ||
204 | * Formulate a fifo command | ||
205 | */ | ||
206 | fifo=0; | ||
207 | if(curtuner==0) { /* FM */ | ||
208 | test=102400; | ||
209 | freq=(freq*1000)/16; /* Make it kHz */ | ||
210 | freq+=10700; /* IF is 10700 kHz */ | ||
211 | for(i=0;i<14;i++) { | ||
212 | fifo=fifo<<1; | ||
213 | if(freq>=test) { | ||
214 | fifo|=0x01; | ||
215 | freq-=test; | ||
216 | } | ||
217 | test=test>>1; | ||
218 | } | ||
219 | } | ||
220 | if(curtuner==1) { /* AM */ | ||
221 | fifo=(freq/16)+2010; /* Make it kHz */ | ||
222 | fifo|=0x100000; /* Select AM Band */ | ||
223 | } | ||
224 | |||
225 | /* | ||
226 | * Save current volume/mute setting | ||
227 | */ | ||
228 | |||
229 | spin_lock(&cadet_io_lock); | ||
230 | outb(7,io); /* Select tuner control */ | ||
231 | curvol=inb(io+1); | ||
232 | spin_unlock(&cadet_io_lock); | ||
233 | |||
234 | /* | ||
235 | * Tune the card | ||
236 | */ | ||
237 | for(j=3;j>-1;j--) { | ||
238 | cadet_settune(fifo|(j<<16)); | ||
239 | |||
240 | spin_lock(&cadet_io_lock); | ||
241 | outb(7,io); /* Select tuner control */ | ||
242 | outb(curvol,io+1); | ||
243 | spin_unlock(&cadet_io_lock); | ||
244 | |||
245 | msleep(100); | ||
246 | |||
247 | cadet_gettune(); | ||
248 | if((tunestat & 0x40) == 0) { /* Tuned */ | ||
249 | sigstrength=sigtable[curtuner][j]; | ||
250 | return; | ||
251 | } | ||
252 | } | ||
253 | sigstrength=0; | ||
254 | } | ||
255 | |||
256 | |||
257 | static int cadet_getvol(void) | ||
258 | { | ||
259 | int ret = 0; | ||
260 | |||
261 | spin_lock(&cadet_io_lock); | ||
262 | |||
263 | outb(7,io); /* Select tuner control */ | ||
264 | if((inb(io + 1) & 0x20) != 0) | ||
265 | ret = 0xffff; | ||
266 | |||
267 | spin_unlock(&cadet_io_lock); | ||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | |||
272 | static void cadet_setvol(int vol) | ||
273 | { | ||
274 | spin_lock(&cadet_io_lock); | ||
275 | outb(7,io); /* Select tuner control */ | ||
276 | if(vol>0) | ||
277 | outb(0x20,io+1); | ||
278 | else | ||
279 | outb(0x00,io+1); | ||
280 | spin_unlock(&cadet_io_lock); | ||
281 | } | ||
282 | |||
283 | static void cadet_handler(unsigned long data) | ||
284 | { | ||
285 | /* | ||
286 | * Service the RDS fifo | ||
287 | */ | ||
288 | |||
289 | if(spin_trylock(&cadet_io_lock)) | ||
290 | { | ||
291 | outb(0x3,io); /* Select RDS Decoder Control */ | ||
292 | if((inb(io+1)&0x20)!=0) { | ||
293 | printk(KERN_CRIT "cadet: RDS fifo overflow\n"); | ||
294 | } | ||
295 | outb(0x80,io); /* Select RDS fifo */ | ||
296 | while((inb(io)&0x80)!=0) { | ||
297 | rdsbuf[rdsin]=inb(io+1); | ||
298 | if(rdsin==rdsout) | ||
299 | printk(KERN_WARNING "cadet: RDS buffer overflow\n"); | ||
300 | else | ||
301 | rdsin++; | ||
302 | } | ||
303 | spin_unlock(&cadet_io_lock); | ||
304 | } | ||
305 | |||
306 | /* | ||
307 | * Service pending read | ||
308 | */ | ||
309 | if( rdsin!=rdsout) | ||
310 | wake_up_interruptible(&read_queue); | ||
311 | |||
312 | /* | ||
313 | * Clean up and exit | ||
314 | */ | ||
315 | init_timer(&readtimer); | ||
316 | readtimer.function=cadet_handler; | ||
317 | readtimer.data=(unsigned long)0; | ||
318 | readtimer.expires=jiffies+(HZ/20); | ||
319 | add_timer(&readtimer); | ||
320 | } | ||
321 | |||
322 | |||
323 | |||
324 | static ssize_t cadet_read(struct file *file, char __user *data, | ||
325 | size_t count, loff_t *ppos) | ||
326 | { | ||
327 | int i=0; | ||
328 | unsigned char readbuf[RDS_BUFFER]; | ||
329 | |||
330 | if(rdsstat==0) { | ||
331 | spin_lock(&cadet_io_lock); | ||
332 | rdsstat=1; | ||
333 | outb(0x80,io); /* Select RDS fifo */ | ||
334 | spin_unlock(&cadet_io_lock); | ||
335 | init_timer(&readtimer); | ||
336 | readtimer.function=cadet_handler; | ||
337 | readtimer.data=(unsigned long)0; | ||
338 | readtimer.expires=jiffies+(HZ/20); | ||
339 | add_timer(&readtimer); | ||
340 | } | ||
341 | if(rdsin==rdsout) { | ||
342 | if (file->f_flags & O_NONBLOCK) | ||
343 | return -EWOULDBLOCK; | ||
344 | interruptible_sleep_on(&read_queue); | ||
345 | } | ||
346 | while( i<count && rdsin!=rdsout) | ||
347 | readbuf[i++]=rdsbuf[rdsout++]; | ||
348 | |||
349 | if (copy_to_user(data,readbuf,i)) | ||
350 | return -EFAULT; | ||
351 | return i; | ||
352 | } | ||
353 | |||
354 | |||
355 | |||
356 | static int cadet_do_ioctl(struct inode *inode, struct file *file, | ||
357 | unsigned int cmd, void *arg) | ||
358 | { | ||
359 | switch(cmd) | ||
360 | { | ||
361 | case VIDIOCGCAP: | ||
362 | { | ||
363 | struct video_capability *v = arg; | ||
364 | memset(v,0,sizeof(*v)); | ||
365 | v->type=VID_TYPE_TUNER; | ||
366 | v->channels=2; | ||
367 | v->audios=1; | ||
368 | strcpy(v->name, "ADS Cadet"); | ||
369 | return 0; | ||
370 | } | ||
371 | case VIDIOCGTUNER: | ||
372 | { | ||
373 | struct video_tuner *v = arg; | ||
374 | if((v->tuner<0)||(v->tuner>1)) { | ||
375 | return -EINVAL; | ||
376 | } | ||
377 | switch(v->tuner) { | ||
378 | case 0: | ||
379 | strcpy(v->name,"FM"); | ||
380 | v->rangelow=1400; /* 87.5 MHz */ | ||
381 | v->rangehigh=1728; /* 108.0 MHz */ | ||
382 | v->flags=0; | ||
383 | v->mode=0; | ||
384 | v->mode|=VIDEO_MODE_AUTO; | ||
385 | v->signal=sigstrength; | ||
386 | if(cadet_getstereo()==1) { | ||
387 | v->flags|=VIDEO_TUNER_STEREO_ON; | ||
388 | } | ||
389 | v->flags|=cadet_getrds(); | ||
390 | break; | ||
391 | case 1: | ||
392 | strcpy(v->name,"AM"); | ||
393 | v->rangelow=8320; /* 520 kHz */ | ||
394 | v->rangehigh=26400; /* 1650 kHz */ | ||
395 | v->flags=0; | ||
396 | v->flags|=VIDEO_TUNER_LOW; | ||
397 | v->mode=0; | ||
398 | v->mode|=VIDEO_MODE_AUTO; | ||
399 | v->signal=sigstrength; | ||
400 | break; | ||
401 | } | ||
402 | return 0; | ||
403 | } | ||
404 | case VIDIOCSTUNER: | ||
405 | { | ||
406 | struct video_tuner *v = arg; | ||
407 | if((v->tuner<0)||(v->tuner>1)) { | ||
408 | return -EINVAL; | ||
409 | } | ||
410 | curtuner=v->tuner; | ||
411 | return 0; | ||
412 | } | ||
413 | case VIDIOCGFREQ: | ||
414 | { | ||
415 | unsigned long *freq = arg; | ||
416 | *freq = cadet_getfreq(); | ||
417 | return 0; | ||
418 | } | ||
419 | case VIDIOCSFREQ: | ||
420 | { | ||
421 | unsigned long *freq = arg; | ||
422 | if((curtuner==0)&&((*freq<1400)||(*freq>1728))) { | ||
423 | return -EINVAL; | ||
424 | } | ||
425 | if((curtuner==1)&&((*freq<8320)||(*freq>26400))) { | ||
426 | return -EINVAL; | ||
427 | } | ||
428 | cadet_setfreq(*freq); | ||
429 | return 0; | ||
430 | } | ||
431 | case VIDIOCGAUDIO: | ||
432 | { | ||
433 | struct video_audio *v = arg; | ||
434 | memset(v,0, sizeof(*v)); | ||
435 | v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; | ||
436 | if(cadet_getstereo()==0) { | ||
437 | v->mode=VIDEO_SOUND_MONO; | ||
438 | } else { | ||
439 | v->mode=VIDEO_SOUND_STEREO; | ||
440 | } | ||
441 | v->volume=cadet_getvol(); | ||
442 | v->step=0xffff; | ||
443 | strcpy(v->name, "Radio"); | ||
444 | return 0; | ||
445 | } | ||
446 | case VIDIOCSAUDIO: | ||
447 | { | ||
448 | struct video_audio *v = arg; | ||
449 | if(v->audio) | ||
450 | return -EINVAL; | ||
451 | cadet_setvol(v->volume); | ||
452 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
453 | cadet_setvol(0); | ||
454 | else | ||
455 | cadet_setvol(0xffff); | ||
456 | return 0; | ||
457 | } | ||
458 | default: | ||
459 | return -ENOIOCTLCMD; | ||
460 | } | ||
461 | } | ||
462 | |||
463 | static int cadet_ioctl(struct inode *inode, struct file *file, | ||
464 | unsigned int cmd, unsigned long arg) | ||
465 | { | ||
466 | return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl); | ||
467 | } | ||
468 | |||
469 | static int cadet_open(struct inode *inode, struct file *file) | ||
470 | { | ||
471 | if(users) | ||
472 | return -EBUSY; | ||
473 | users++; | ||
474 | init_waitqueue_head(&read_queue); | ||
475 | return 0; | ||
476 | } | ||
477 | |||
478 | static int cadet_release(struct inode *inode, struct file *file) | ||
479 | { | ||
480 | del_timer_sync(&readtimer); | ||
481 | rdsstat=0; | ||
482 | users--; | ||
483 | return 0; | ||
484 | } | ||
485 | |||
486 | |||
487 | static struct file_operations cadet_fops = { | ||
488 | .owner = THIS_MODULE, | ||
489 | .open = cadet_open, | ||
490 | .release = cadet_release, | ||
491 | .read = cadet_read, | ||
492 | .ioctl = cadet_ioctl, | ||
493 | .llseek = no_llseek, | ||
494 | }; | ||
495 | |||
496 | static struct video_device cadet_radio= | ||
497 | { | ||
498 | .owner = THIS_MODULE, | ||
499 | .name = "Cadet radio", | ||
500 | .type = VID_TYPE_TUNER, | ||
501 | .hardware = VID_HARDWARE_CADET, | ||
502 | .fops = &cadet_fops, | ||
503 | }; | ||
504 | |||
505 | static struct pnp_device_id cadet_pnp_devices[] = { | ||
506 | /* ADS Cadet AM/FM Radio Card */ | ||
507 | {.id = "MSM0c24", .driver_data = 0}, | ||
508 | {.id = ""} | ||
509 | }; | ||
510 | |||
511 | MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); | ||
512 | |||
513 | static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) | ||
514 | { | ||
515 | if (!dev) | ||
516 | return -ENODEV; | ||
517 | /* only support one device */ | ||
518 | if (io > 0) | ||
519 | return -EBUSY; | ||
520 | |||
521 | if (!pnp_port_valid(dev, 0)) { | ||
522 | return -ENODEV; | ||
523 | } | ||
524 | |||
525 | io = pnp_port_start(dev, 0); | ||
526 | |||
527 | printk ("radio-cadet: PnP reports device at %#x\n", io); | ||
528 | |||
529 | return io; | ||
530 | } | ||
531 | |||
532 | static struct pnp_driver cadet_pnp_driver = { | ||
533 | .name = "radio-cadet", | ||
534 | .id_table = cadet_pnp_devices, | ||
535 | .probe = cadet_pnp_probe, | ||
536 | .remove = NULL, | ||
537 | }; | ||
538 | |||
539 | static int cadet_probe(void) | ||
540 | { | ||
541 | static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; | ||
542 | int i; | ||
543 | |||
544 | for(i=0;i<8;i++) { | ||
545 | io=iovals[i]; | ||
546 | if(request_region(io,2, "cadet-probe")>=0) { | ||
547 | cadet_setfreq(1410); | ||
548 | if(cadet_getfreq()==1410) { | ||
549 | release_region(io, 2); | ||
550 | return io; | ||
551 | } | ||
552 | release_region(io, 2); | ||
553 | } | ||
554 | } | ||
555 | return -1; | ||
556 | } | ||
557 | |||
558 | /* | ||
559 | * io should only be set if the user has used something like | ||
560 | * isapnp (the userspace program) to initialize this card for us | ||
561 | */ | ||
562 | |||
563 | static int __init cadet_init(void) | ||
564 | { | ||
565 | spin_lock_init(&cadet_io_lock); | ||
566 | |||
567 | /* | ||
568 | * If a probe was requested then probe ISAPnP first (safest) | ||
569 | */ | ||
570 | if (io < 0) | ||
571 | pnp_register_driver(&cadet_pnp_driver); | ||
572 | /* | ||
573 | * If that fails then probe unsafely if probe is requested | ||
574 | */ | ||
575 | if(io < 0) | ||
576 | io = cadet_probe (); | ||
577 | |||
578 | /* | ||
579 | * Else we bail out | ||
580 | */ | ||
581 | |||
582 | if(io < 0) { | ||
583 | #ifdef MODULE | ||
584 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
585 | #endif | ||
586 | goto fail; | ||
587 | } | ||
588 | if (!request_region(io,2,"cadet")) | ||
589 | goto fail; | ||
590 | if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) { | ||
591 | release_region(io,2); | ||
592 | goto fail; | ||
593 | } | ||
594 | printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); | ||
595 | return 0; | ||
596 | fail: | ||
597 | pnp_unregister_driver(&cadet_pnp_driver); | ||
598 | return -1; | ||
599 | } | ||
600 | |||
601 | |||
602 | |||
603 | MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); | ||
604 | MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); | ||
605 | MODULE_LICENSE("GPL"); | ||
606 | |||
607 | module_param(io, int, 0); | ||
608 | MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); | ||
609 | module_param(radio_nr, int, 0); | ||
610 | |||
611 | static void __exit cadet_cleanup_module(void) | ||
612 | { | ||
613 | video_unregister_device(&cadet_radio); | ||
614 | release_region(io,2); | ||
615 | pnp_unregister_driver(&cadet_pnp_driver); | ||
616 | } | ||
617 | |||
618 | module_init(cadet_init); | ||
619 | module_exit(cadet_cleanup_module); | ||
620 | |||
diff --git a/drivers/media/radio/radio-gemtek-pci.c b/drivers/media/radio/radio-gemtek-pci.c new file mode 100644 index 000000000000..630cc786d0a4 --- /dev/null +++ b/drivers/media/radio/radio-gemtek-pci.c | |||
@@ -0,0 +1,416 @@ | |||
1 | /* | ||
2 | *************************************************************************** | ||
3 | * | ||
4 | * radio-gemtek-pci.c - Gemtek PCI Radio driver | ||
5 | * (C) 2001 Vladimir Shebordaev <vshebordaev@mail.ru> | ||
6 | * | ||
7 | *************************************************************************** | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License as | ||
11 | * published by the Free Software Foundation; either version 2 of | ||
12 | * the License, or (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public | ||
20 | * License along with this program; if not, write to the Free | ||
21 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, | ||
22 | * USA. | ||
23 | * | ||
24 | *************************************************************************** | ||
25 | * | ||
26 | * Gemtek Corp still silently refuses to release any specifications | ||
27 | * of their multimedia devices, so the protocol still has to be | ||
28 | * reverse engineered. | ||
29 | * | ||
30 | * The v4l code was inspired by Jonas Munsin's Gemtek serial line | ||
31 | * radio device driver. | ||
32 | * | ||
33 | * Please, let me know if this piece of code was useful :) | ||
34 | * | ||
35 | * TODO: multiple device support and portability were not tested | ||
36 | * | ||
37 | *************************************************************************** | ||
38 | */ | ||
39 | |||
40 | #include <linux/config.h> | ||
41 | #include <linux/types.h> | ||
42 | #include <linux/list.h> | ||
43 | #include <linux/module.h> | ||
44 | #include <linux/init.h> | ||
45 | #include <linux/pci.h> | ||
46 | #include <linux/videodev.h> | ||
47 | #include <linux/errno.h> | ||
48 | |||
49 | #include <asm/io.h> | ||
50 | #include <asm/uaccess.h> | ||
51 | |||
52 | #ifndef PCI_VENDOR_ID_GEMTEK | ||
53 | #define PCI_VENDOR_ID_GEMTEK 0x5046 | ||
54 | #endif | ||
55 | |||
56 | #ifndef PCI_DEVICE_ID_GEMTEK_PR103 | ||
57 | #define PCI_DEVICE_ID_GEMTEK_PR103 0x1001 | ||
58 | #endif | ||
59 | |||
60 | #ifndef GEMTEK_PCI_RANGE_LOW | ||
61 | #define GEMTEK_PCI_RANGE_LOW (87*16000) | ||
62 | #endif | ||
63 | |||
64 | #ifndef GEMTEK_PCI_RANGE_HIGH | ||
65 | #define GEMTEK_PCI_RANGE_HIGH (108*16000) | ||
66 | #endif | ||
67 | |||
68 | #ifndef TRUE | ||
69 | #define TRUE (1) | ||
70 | #endif | ||
71 | |||
72 | #ifndef FALSE | ||
73 | #define FALSE (0) | ||
74 | #endif | ||
75 | |||
76 | struct gemtek_pci_card { | ||
77 | struct video_device *videodev; | ||
78 | |||
79 | u32 iobase; | ||
80 | u32 length; | ||
81 | u8 chiprev; | ||
82 | u16 model; | ||
83 | |||
84 | u32 current_frequency; | ||
85 | u8 mute; | ||
86 | }; | ||
87 | |||
88 | static const char rcsid[] = "$Id: radio-gemtek-pci.c,v 1.1 2001/07/23 08:08:16 ted Exp ted $"; | ||
89 | |||
90 | static int nr_radio = -1; | ||
91 | |||
92 | static inline u8 gemtek_pci_out( u16 value, u32 port ) | ||
93 | { | ||
94 | outw( value, port ); | ||
95 | |||
96 | return (u8)value; | ||
97 | } | ||
98 | |||
99 | #define _b0( v ) *((u8 *)&v) | ||
100 | static void __gemtek_pci_cmd( u16 value, u32 port, u8 *last_byte, int keep ) | ||
101 | { | ||
102 | register u8 byte = *last_byte; | ||
103 | |||
104 | if ( !value ) { | ||
105 | if ( !keep ) | ||
106 | value = (u16)port; | ||
107 | byte &= 0xfd; | ||
108 | } else | ||
109 | byte |= 2; | ||
110 | |||
111 | _b0( value ) = byte; | ||
112 | outw( value, port ); | ||
113 | byte |= 1; | ||
114 | _b0( value ) = byte; | ||
115 | outw( value, port ); | ||
116 | byte &= 0xfe; | ||
117 | _b0( value ) = byte; | ||
118 | outw( value, port ); | ||
119 | |||
120 | *last_byte = byte; | ||
121 | } | ||
122 | |||
123 | static inline void gemtek_pci_nil( u32 port, u8 *last_byte ) | ||
124 | { | ||
125 | __gemtek_pci_cmd( 0x00, port, last_byte, FALSE ); | ||
126 | } | ||
127 | |||
128 | static inline void gemtek_pci_cmd( u16 cmd, u32 port, u8 *last_byte ) | ||
129 | { | ||
130 | __gemtek_pci_cmd( cmd, port, last_byte, TRUE ); | ||
131 | } | ||
132 | |||
133 | static void gemtek_pci_setfrequency( struct gemtek_pci_card *card, unsigned long frequency ) | ||
134 | { | ||
135 | register int i; | ||
136 | register u32 value = frequency / 200 + 856; | ||
137 | register u16 mask = 0x8000; | ||
138 | u8 last_byte; | ||
139 | u32 port = card->iobase; | ||
140 | |||
141 | last_byte = gemtek_pci_out( 0x06, port ); | ||
142 | |||
143 | i = 0; | ||
144 | do { | ||
145 | gemtek_pci_nil( port, &last_byte ); | ||
146 | i++; | ||
147 | } while ( i < 9 ); | ||
148 | |||
149 | i = 0; | ||
150 | do { | ||
151 | gemtek_pci_cmd( value & mask, port, &last_byte ); | ||
152 | mask >>= 1; | ||
153 | i++; | ||
154 | } while ( i < 16 ); | ||
155 | |||
156 | outw( 0x10, port ); | ||
157 | } | ||
158 | |||
159 | |||
160 | static inline void gemtek_pci_mute( struct gemtek_pci_card *card ) | ||
161 | { | ||
162 | outb( 0x1f, card->iobase ); | ||
163 | card->mute = TRUE; | ||
164 | } | ||
165 | |||
166 | static inline void gemtek_pci_unmute( struct gemtek_pci_card *card ) | ||
167 | { | ||
168 | if ( card->mute ) { | ||
169 | gemtek_pci_setfrequency( card, card->current_frequency ); | ||
170 | card->mute = FALSE; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | static inline unsigned int gemtek_pci_getsignal( struct gemtek_pci_card *card ) | ||
175 | { | ||
176 | return ( inb( card->iobase ) & 0x08 ) ? 0 : 1; | ||
177 | } | ||
178 | |||
179 | static int gemtek_pci_do_ioctl(struct inode *inode, struct file *file, | ||
180 | unsigned int cmd, void *arg) | ||
181 | { | ||
182 | struct video_device *dev = video_devdata(file); | ||
183 | struct gemtek_pci_card *card = dev->priv; | ||
184 | |||
185 | switch ( cmd ) { | ||
186 | case VIDIOCGCAP: | ||
187 | { | ||
188 | struct video_capability *c = arg; | ||
189 | |||
190 | memset(c,0,sizeof(*c)); | ||
191 | c->type = VID_TYPE_TUNER; | ||
192 | c->channels = 1; | ||
193 | c->audios = 1; | ||
194 | strcpy( c->name, "Gemtek PCI Radio" ); | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | case VIDIOCGTUNER: | ||
199 | { | ||
200 | struct video_tuner *t = arg; | ||
201 | |||
202 | if ( t->tuner ) | ||
203 | return -EINVAL; | ||
204 | |||
205 | t->rangelow = GEMTEK_PCI_RANGE_LOW; | ||
206 | t->rangehigh = GEMTEK_PCI_RANGE_HIGH; | ||
207 | t->flags = VIDEO_TUNER_LOW; | ||
208 | t->mode = VIDEO_MODE_AUTO; | ||
209 | t->signal = 0xFFFF * gemtek_pci_getsignal( card ); | ||
210 | strcpy( t->name, "FM" ); | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | case VIDIOCSTUNER: | ||
215 | { | ||
216 | struct video_tuner *t = arg; | ||
217 | if ( t->tuner ) | ||
218 | return -EINVAL; | ||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | case VIDIOCGFREQ: | ||
223 | { | ||
224 | unsigned long *freq = arg; | ||
225 | *freq = card->current_frequency; | ||
226 | return 0; | ||
227 | } | ||
228 | case VIDIOCSFREQ: | ||
229 | { | ||
230 | unsigned long *freq = arg; | ||
231 | |||
232 | if ( (*freq < GEMTEK_PCI_RANGE_LOW) || | ||
233 | (*freq > GEMTEK_PCI_RANGE_HIGH) ) | ||
234 | return -EINVAL; | ||
235 | |||
236 | gemtek_pci_setfrequency( card, *freq ); | ||
237 | card->current_frequency = *freq; | ||
238 | card->mute = FALSE; | ||
239 | |||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | case VIDIOCGAUDIO: | ||
244 | { | ||
245 | struct video_audio *a = arg; | ||
246 | |||
247 | memset( a, 0, sizeof( *a ) ); | ||
248 | a->flags |= VIDEO_AUDIO_MUTABLE; | ||
249 | a->volume = 1; | ||
250 | a->step = 65535; | ||
251 | strcpy( a->name, "Radio" ); | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | case VIDIOCSAUDIO: | ||
256 | { | ||
257 | struct video_audio *a = arg; | ||
258 | |||
259 | if ( a->audio ) | ||
260 | return -EINVAL; | ||
261 | |||
262 | if ( a->flags & VIDEO_AUDIO_MUTE ) | ||
263 | gemtek_pci_mute( card ); | ||
264 | else | ||
265 | gemtek_pci_unmute( card ); | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | default: | ||
270 | return -ENOIOCTLCMD; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | static int gemtek_pci_ioctl(struct inode *inode, struct file *file, | ||
275 | unsigned int cmd, unsigned long arg) | ||
276 | { | ||
277 | return video_usercopy(inode, file, cmd, arg, gemtek_pci_do_ioctl); | ||
278 | } | ||
279 | |||
280 | enum { | ||
281 | GEMTEK_PR103 | ||
282 | }; | ||
283 | |||
284 | static char *card_names[] __devinitdata = { | ||
285 | "GEMTEK_PR103" | ||
286 | }; | ||
287 | |||
288 | static struct pci_device_id gemtek_pci_id[] = | ||
289 | { | ||
290 | { PCI_VENDOR_ID_GEMTEK, PCI_DEVICE_ID_GEMTEK_PR103, | ||
291 | PCI_ANY_ID, PCI_ANY_ID, 0, 0, GEMTEK_PR103 }, | ||
292 | { 0 } | ||
293 | }; | ||
294 | |||
295 | MODULE_DEVICE_TABLE( pci, gemtek_pci_id ); | ||
296 | |||
297 | static int mx = 1; | ||
298 | |||
299 | static struct file_operations gemtek_pci_fops = { | ||
300 | .owner = THIS_MODULE, | ||
301 | .open = video_exclusive_open, | ||
302 | .release = video_exclusive_release, | ||
303 | .ioctl = gemtek_pci_ioctl, | ||
304 | .llseek = no_llseek, | ||
305 | }; | ||
306 | |||
307 | static struct video_device vdev_template = { | ||
308 | .owner = THIS_MODULE, | ||
309 | .name = "Gemtek PCI Radio", | ||
310 | .type = VID_TYPE_TUNER, | ||
311 | .hardware = VID_HARDWARE_GEMTEK, | ||
312 | .fops = &gemtek_pci_fops, | ||
313 | }; | ||
314 | |||
315 | static int __devinit gemtek_pci_probe( struct pci_dev *pci_dev, const struct pci_device_id *pci_id ) | ||
316 | { | ||
317 | struct gemtek_pci_card *card; | ||
318 | struct video_device *devradio; | ||
319 | |||
320 | if ( (card = kmalloc( sizeof( struct gemtek_pci_card ), GFP_KERNEL )) == NULL ) { | ||
321 | printk( KERN_ERR "gemtek_pci: out of memory\n" ); | ||
322 | return -ENOMEM; | ||
323 | } | ||
324 | memset( card, 0, sizeof( struct gemtek_pci_card ) ); | ||
325 | |||
326 | if ( pci_enable_device( pci_dev ) ) | ||
327 | goto err_pci; | ||
328 | |||
329 | card->iobase = pci_resource_start( pci_dev, 0 ); | ||
330 | card->length = pci_resource_len( pci_dev, 0 ); | ||
331 | |||
332 | if ( request_region( card->iobase, card->length, card_names[pci_id->driver_data] ) == NULL ) { | ||
333 | printk( KERN_ERR "gemtek_pci: i/o port already in use\n" ); | ||
334 | goto err_pci; | ||
335 | } | ||
336 | |||
337 | pci_read_config_byte( pci_dev, PCI_REVISION_ID, &card->chiprev ); | ||
338 | pci_read_config_word( pci_dev, PCI_SUBSYSTEM_ID, &card->model ); | ||
339 | |||
340 | pci_set_drvdata( pci_dev, card ); | ||
341 | |||
342 | if ( (devradio = kmalloc( sizeof( struct video_device ), GFP_KERNEL )) == NULL ) { | ||
343 | printk( KERN_ERR "gemtek_pci: out of memory\n" ); | ||
344 | goto err_video; | ||
345 | } | ||
346 | *devradio = vdev_template; | ||
347 | |||
348 | if ( video_register_device( devradio, VFL_TYPE_RADIO , nr_radio) == -1 ) { | ||
349 | kfree( devradio ); | ||
350 | goto err_video; | ||
351 | } | ||
352 | |||
353 | card->videodev = devradio; | ||
354 | devradio->priv = card; | ||
355 | gemtek_pci_mute( card ); | ||
356 | |||
357 | printk( KERN_INFO "Gemtek PCI Radio (rev. %d) found at 0x%04x-0x%04x.\n", | ||
358 | card->chiprev, card->iobase, card->iobase + card->length - 1 ); | ||
359 | |||
360 | return 0; | ||
361 | |||
362 | err_video: | ||
363 | release_region( card->iobase, card->length ); | ||
364 | |||
365 | err_pci: | ||
366 | kfree( card ); | ||
367 | return -ENODEV; | ||
368 | } | ||
369 | |||
370 | static void __devexit gemtek_pci_remove( struct pci_dev *pci_dev ) | ||
371 | { | ||
372 | struct gemtek_pci_card *card = pci_get_drvdata( pci_dev ); | ||
373 | |||
374 | video_unregister_device( card->videodev ); | ||
375 | kfree( card->videodev ); | ||
376 | |||
377 | release_region( card->iobase, card->length ); | ||
378 | |||
379 | if ( mx ) | ||
380 | gemtek_pci_mute( card ); | ||
381 | |||
382 | kfree( card ); | ||
383 | |||
384 | pci_set_drvdata( pci_dev, NULL ); | ||
385 | } | ||
386 | |||
387 | static struct pci_driver gemtek_pci_driver = | ||
388 | { | ||
389 | .name = "gemtek_pci", | ||
390 | .id_table = gemtek_pci_id, | ||
391 | .probe = gemtek_pci_probe, | ||
392 | .remove = __devexit_p(gemtek_pci_remove), | ||
393 | }; | ||
394 | |||
395 | static int __init gemtek_pci_init_module( void ) | ||
396 | { | ||
397 | return pci_module_init( &gemtek_pci_driver ); | ||
398 | } | ||
399 | |||
400 | static void __exit gemtek_pci_cleanup_module( void ) | ||
401 | { | ||
402 | return pci_unregister_driver( &gemtek_pci_driver ); | ||
403 | } | ||
404 | |||
405 | MODULE_AUTHOR( "Vladimir Shebordaev <vshebordaev@mail.ru>" ); | ||
406 | MODULE_DESCRIPTION( "The video4linux driver for the Gemtek PCI Radio Card" ); | ||
407 | MODULE_LICENSE("GPL"); | ||
408 | |||
409 | module_param(mx, bool, 0); | ||
410 | MODULE_PARM_DESC( mx, "single digit: 1 - turn off the turner upon module exit (default), 0 - do not" ); | ||
411 | module_param(nr_radio, int, 0); | ||
412 | MODULE_PARM_DESC( nr_radio, "video4linux device number to use"); | ||
413 | |||
414 | module_init( gemtek_pci_init_module ); | ||
415 | module_exit( gemtek_pci_cleanup_module ); | ||
416 | |||
diff --git a/drivers/media/radio/radio-gemtek.c b/drivers/media/radio/radio-gemtek.c new file mode 100644 index 000000000000..202bfe6819b8 --- /dev/null +++ b/drivers/media/radio/radio-gemtek.c | |||
@@ -0,0 +1,304 @@ | |||
1 | /* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi> | ||
2 | * | ||
3 | * GemTek hasn't released any specs on the card, so the protocol had to | ||
4 | * be reverse engineered with dosemu. | ||
5 | * | ||
6 | * Besides the protocol changes, this is mostly a copy of: | ||
7 | * | ||
8 | * RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff | ||
9 | * | ||
10 | * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood | ||
11 | * Converted to new API by Alan Cox <Alan.Cox@linux.org> | ||
12 | * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> | ||
13 | * | ||
14 | * TODO: Allow for more than one of these foolish entities :-) | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> /* Modules */ | ||
19 | #include <linux/init.h> /* Initdata */ | ||
20 | #include <linux/ioport.h> /* check_region, request_region */ | ||
21 | #include <linux/delay.h> /* udelay */ | ||
22 | #include <asm/io.h> /* outb, outb_p */ | ||
23 | #include <asm/uaccess.h> /* copy to/from user */ | ||
24 | #include <linux/videodev.h> /* kernel radio structs */ | ||
25 | #include <linux/config.h> /* CONFIG_RADIO_GEMTEK_PORT */ | ||
26 | #include <linux/spinlock.h> | ||
27 | |||
28 | #ifndef CONFIG_RADIO_GEMTEK_PORT | ||
29 | #define CONFIG_RADIO_GEMTEK_PORT -1 | ||
30 | #endif | ||
31 | |||
32 | static int io = CONFIG_RADIO_GEMTEK_PORT; | ||
33 | static int radio_nr = -1; | ||
34 | static spinlock_t lock; | ||
35 | |||
36 | struct gemtek_device | ||
37 | { | ||
38 | int port; | ||
39 | unsigned long curfreq; | ||
40 | int muted; | ||
41 | }; | ||
42 | |||
43 | |||
44 | /* local things */ | ||
45 | |||
46 | /* the correct way to mute the gemtek may be to write the last written | ||
47 | * frequency || 0x10, but just writing 0x10 once seems to do it as well | ||
48 | */ | ||
49 | static void gemtek_mute(struct gemtek_device *dev) | ||
50 | { | ||
51 | if(dev->muted) | ||
52 | return; | ||
53 | spin_lock(&lock); | ||
54 | outb(0x10, io); | ||
55 | spin_unlock(&lock); | ||
56 | dev->muted = 1; | ||
57 | } | ||
58 | |||
59 | static void gemtek_unmute(struct gemtek_device *dev) | ||
60 | { | ||
61 | if(dev->muted == 0) | ||
62 | return; | ||
63 | spin_lock(&lock); | ||
64 | outb(0x20, io); | ||
65 | spin_unlock(&lock); | ||
66 | dev->muted = 0; | ||
67 | } | ||
68 | |||
69 | static void zero(void) | ||
70 | { | ||
71 | outb_p(0x04, io); | ||
72 | udelay(5); | ||
73 | outb_p(0x05, io); | ||
74 | udelay(5); | ||
75 | } | ||
76 | |||
77 | static void one(void) | ||
78 | { | ||
79 | outb_p(0x06, io); | ||
80 | udelay(5); | ||
81 | outb_p(0x07, io); | ||
82 | udelay(5); | ||
83 | } | ||
84 | |||
85 | static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq) | ||
86 | { | ||
87 | int i; | ||
88 | |||
89 | /* freq = 78.25*((float)freq/16000.0 + 10.52); */ | ||
90 | |||
91 | freq /= 16; | ||
92 | freq += 10520; | ||
93 | freq *= 7825; | ||
94 | freq /= 100000; | ||
95 | |||
96 | spin_lock(&lock); | ||
97 | |||
98 | /* 2 start bits */ | ||
99 | outb_p(0x03, io); | ||
100 | udelay(5); | ||
101 | outb_p(0x07, io); | ||
102 | udelay(5); | ||
103 | |||
104 | /* 28 frequency bits (lsb first) */ | ||
105 | for (i = 0; i < 14; i++) | ||
106 | if (freq & (1 << i)) | ||
107 | one(); | ||
108 | else | ||
109 | zero(); | ||
110 | /* 36 unknown bits */ | ||
111 | for (i = 0; i < 11; i++) | ||
112 | zero(); | ||
113 | one(); | ||
114 | for (i = 0; i < 4; i++) | ||
115 | zero(); | ||
116 | one(); | ||
117 | zero(); | ||
118 | |||
119 | /* 2 end bits */ | ||
120 | outb_p(0x03, io); | ||
121 | udelay(5); | ||
122 | outb_p(0x07, io); | ||
123 | udelay(5); | ||
124 | |||
125 | spin_unlock(&lock); | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static int gemtek_getsigstr(struct gemtek_device *dev) | ||
131 | { | ||
132 | spin_lock(&lock); | ||
133 | inb(io); | ||
134 | udelay(5); | ||
135 | spin_unlock(&lock); | ||
136 | if (inb(io) & 8) /* bit set = no signal present */ | ||
137 | return 0; | ||
138 | return 1; /* signal present */ | ||
139 | } | ||
140 | |||
141 | static int gemtek_do_ioctl(struct inode *inode, struct file *file, | ||
142 | unsigned int cmd, void *arg) | ||
143 | { | ||
144 | struct video_device *dev = video_devdata(file); | ||
145 | struct gemtek_device *rt=dev->priv; | ||
146 | |||
147 | switch(cmd) | ||
148 | { | ||
149 | case VIDIOCGCAP: | ||
150 | { | ||
151 | struct video_capability *v = arg; | ||
152 | memset(v,0,sizeof(*v)); | ||
153 | v->type=VID_TYPE_TUNER; | ||
154 | v->channels=1; | ||
155 | v->audios=1; | ||
156 | strcpy(v->name, "GemTek"); | ||
157 | return 0; | ||
158 | } | ||
159 | case VIDIOCGTUNER: | ||
160 | { | ||
161 | struct video_tuner *v = arg; | ||
162 | if(v->tuner) /* Only 1 tuner */ | ||
163 | return -EINVAL; | ||
164 | v->rangelow=87*16000; | ||
165 | v->rangehigh=108*16000; | ||
166 | v->flags=VIDEO_TUNER_LOW; | ||
167 | v->mode=VIDEO_MODE_AUTO; | ||
168 | v->signal=0xFFFF*gemtek_getsigstr(rt); | ||
169 | strcpy(v->name, "FM"); | ||
170 | return 0; | ||
171 | } | ||
172 | case VIDIOCSTUNER: | ||
173 | { | ||
174 | struct video_tuner *v = arg; | ||
175 | if(v->tuner!=0) | ||
176 | return -EINVAL; | ||
177 | /* Only 1 tuner so no setting needed ! */ | ||
178 | return 0; | ||
179 | } | ||
180 | case VIDIOCGFREQ: | ||
181 | { | ||
182 | unsigned long *freq = arg; | ||
183 | *freq = rt->curfreq; | ||
184 | return 0; | ||
185 | } | ||
186 | case VIDIOCSFREQ: | ||
187 | { | ||
188 | unsigned long *freq = arg; | ||
189 | rt->curfreq = *freq; | ||
190 | /* needs to be called twice in order for getsigstr to work */ | ||
191 | gemtek_setfreq(rt, rt->curfreq); | ||
192 | gemtek_setfreq(rt, rt->curfreq); | ||
193 | return 0; | ||
194 | } | ||
195 | case VIDIOCGAUDIO: | ||
196 | { | ||
197 | struct video_audio *v = arg; | ||
198 | memset(v,0, sizeof(*v)); | ||
199 | v->flags|=VIDEO_AUDIO_MUTABLE; | ||
200 | v->volume=1; | ||
201 | v->step=65535; | ||
202 | strcpy(v->name, "Radio"); | ||
203 | return 0; | ||
204 | } | ||
205 | case VIDIOCSAUDIO: | ||
206 | { | ||
207 | struct video_audio *v = arg; | ||
208 | if(v->audio) | ||
209 | return -EINVAL; | ||
210 | |||
211 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
212 | gemtek_mute(rt); | ||
213 | else | ||
214 | gemtek_unmute(rt); | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | default: | ||
219 | return -ENOIOCTLCMD; | ||
220 | } | ||
221 | } | ||
222 | |||
223 | static int gemtek_ioctl(struct inode *inode, struct file *file, | ||
224 | unsigned int cmd, unsigned long arg) | ||
225 | { | ||
226 | return video_usercopy(inode, file, cmd, arg, gemtek_do_ioctl); | ||
227 | } | ||
228 | |||
229 | static struct gemtek_device gemtek_unit; | ||
230 | |||
231 | static struct file_operations gemtek_fops = { | ||
232 | .owner = THIS_MODULE, | ||
233 | .open = video_exclusive_open, | ||
234 | .release = video_exclusive_release, | ||
235 | .ioctl = gemtek_ioctl, | ||
236 | .llseek = no_llseek, | ||
237 | }; | ||
238 | |||
239 | static struct video_device gemtek_radio= | ||
240 | { | ||
241 | .owner = THIS_MODULE, | ||
242 | .name = "GemTek radio", | ||
243 | .type = VID_TYPE_TUNER, | ||
244 | .hardware = VID_HARDWARE_GEMTEK, | ||
245 | .fops = &gemtek_fops, | ||
246 | }; | ||
247 | |||
248 | static int __init gemtek_init(void) | ||
249 | { | ||
250 | if(io==-1) | ||
251 | { | ||
252 | printk(KERN_ERR "You must set an I/O address with io=0x20c, io=0x30c, io=0x24c or io=0x34c (io=0x020c or io=0x248 for the combined sound/radiocard)\n"); | ||
253 | return -EINVAL; | ||
254 | } | ||
255 | |||
256 | if (!request_region(io, 4, "gemtek")) | ||
257 | { | ||
258 | printk(KERN_ERR "gemtek: port 0x%x already in use\n", io); | ||
259 | return -EBUSY; | ||
260 | } | ||
261 | |||
262 | gemtek_radio.priv=&gemtek_unit; | ||
263 | |||
264 | if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
265 | { | ||
266 | release_region(io, 4); | ||
267 | return -EINVAL; | ||
268 | } | ||
269 | printk(KERN_INFO "GemTek Radio Card driver.\n"); | ||
270 | |||
271 | spin_lock_init(&lock); | ||
272 | |||
273 | /* this is _maybe_ unnecessary */ | ||
274 | outb(0x01, io); | ||
275 | |||
276 | /* mute card - prevents noisy bootups */ | ||
277 | gemtek_unit.muted = 0; | ||
278 | gemtek_mute(&gemtek_unit); | ||
279 | |||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | MODULE_AUTHOR("Jonas Munsin"); | ||
284 | MODULE_DESCRIPTION("A driver for the GemTek Radio Card"); | ||
285 | MODULE_LICENSE("GPL"); | ||
286 | |||
287 | module_param(io, int, 0); | ||
288 | MODULE_PARM_DESC(io, "I/O address of the GemTek card (0x20c, 0x30c, 0x24c or 0x34c (0x20c or 0x248 have been reported to work for the combined sound/radiocard))."); | ||
289 | module_param(radio_nr, int, 0); | ||
290 | |||
291 | static void __exit gemtek_cleanup(void) | ||
292 | { | ||
293 | video_unregister_device(&gemtek_radio); | ||
294 | release_region(io,4); | ||
295 | } | ||
296 | |||
297 | module_init(gemtek_init); | ||
298 | module_exit(gemtek_cleanup); | ||
299 | |||
300 | /* | ||
301 | Local variables: | ||
302 | compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c" | ||
303 | End: | ||
304 | */ | ||
diff --git a/drivers/media/radio/radio-maestro.c b/drivers/media/radio/radio-maestro.c new file mode 100644 index 000000000000..e62147e4ed1b --- /dev/null +++ b/drivers/media/radio/radio-maestro.c | |||
@@ -0,0 +1,332 @@ | |||
1 | /* Maestro PCI sound card radio driver for Linux support | ||
2 | * (c) 2000 A. Tlalka, atlka@pg.gda.pl | ||
3 | * Notes on the hardware | ||
4 | * | ||
5 | * + Frequency control is done digitally | ||
6 | * + No volume control - only mute/unmute - you have to use Aux line volume | ||
7 | * control on Maestro card to set the volume | ||
8 | * + Radio status (tuned/not_tuned and stereo/mono) is valid some time after | ||
9 | * frequency setting (>100ms) and only when the radio is unmuted. | ||
10 | * version 0.02 | ||
11 | * + io port is automatically detected - only the first radio is used | ||
12 | * version 0.03 | ||
13 | * + thread access locking additions | ||
14 | * version 0.04 | ||
15 | * + code improvements | ||
16 | * + VIDEO_TUNER_LOW is permanent | ||
17 | */ | ||
18 | |||
19 | #include <linux/module.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/ioport.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/sched.h> | ||
24 | #include <asm/io.h> | ||
25 | #include <asm/uaccess.h> | ||
26 | #include <asm/semaphore.h> | ||
27 | #include <linux/pci.h> | ||
28 | #include <linux/videodev.h> | ||
29 | |||
30 | #define DRIVER_VERSION "0.04" | ||
31 | |||
32 | #define PCI_VENDOR_ESS 0x125D | ||
33 | #define PCI_DEVICE_ID_ESS_ESS1968 0x1968 /* Maestro 2 */ | ||
34 | #define PCI_DEVICE_ID_ESS_ESS1978 0x1978 /* Maestro 2E */ | ||
35 | |||
36 | #define GPIO_DATA 0x60 /* port offset from ESS_IO_BASE */ | ||
37 | |||
38 | #define IO_MASK 4 /* mask register offset from GPIO_DATA | ||
39 | bits 1=unmask write to given bit */ | ||
40 | #define IO_DIR 8 /* direction register offset from GPIO_DATA | ||
41 | bits 0/1=read/write direction */ | ||
42 | |||
43 | #define GPIO6 0x0040 /* mask bits for GPIO lines */ | ||
44 | #define GPIO7 0x0080 | ||
45 | #define GPIO8 0x0100 | ||
46 | #define GPIO9 0x0200 | ||
47 | |||
48 | #define STR_DATA GPIO6 /* radio TEA5757 pins and GPIO bits */ | ||
49 | #define STR_CLK GPIO7 | ||
50 | #define STR_WREN GPIO8 | ||
51 | #define STR_MOST GPIO9 | ||
52 | |||
53 | #define FREQ_LO 50*16000 | ||
54 | #define FREQ_HI 150*16000 | ||
55 | |||
56 | #define FREQ_IF 171200 /* 10.7*16000 */ | ||
57 | #define FREQ_STEP 200 /* 12.5*16 */ | ||
58 | |||
59 | #define FREQ2BITS(x) ((((unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\ | ||
60 | /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */ | ||
61 | |||
62 | #define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) | ||
63 | |||
64 | static int radio_nr = -1; | ||
65 | module_param(radio_nr, int, 0); | ||
66 | |||
67 | static int radio_ioctl(struct inode *inode, struct file *file, | ||
68 | unsigned int cmd, unsigned long arg); | ||
69 | |||
70 | static struct file_operations maestro_fops = { | ||
71 | .owner = THIS_MODULE, | ||
72 | .open = video_exclusive_open, | ||
73 | .release = video_exclusive_release, | ||
74 | .ioctl = radio_ioctl, | ||
75 | .llseek = no_llseek, | ||
76 | }; | ||
77 | |||
78 | static struct video_device maestro_radio= | ||
79 | { | ||
80 | .owner = THIS_MODULE, | ||
81 | .name = "Maestro radio", | ||
82 | .type = VID_TYPE_TUNER, | ||
83 | .hardware = VID_HARDWARE_SF16MI, | ||
84 | .fops = &maestro_fops, | ||
85 | }; | ||
86 | |||
87 | static struct radio_device | ||
88 | { | ||
89 | __u16 io, /* base of Maestro card radio io (GPIO_DATA)*/ | ||
90 | muted, /* VIDEO_AUDIO_MUTE */ | ||
91 | stereo, /* VIDEO_TUNER_STEREO_ON */ | ||
92 | tuned; /* signal strength (0 or 0xffff) */ | ||
93 | struct semaphore lock; | ||
94 | } radio_unit = {0, 0, 0, 0, }; | ||
95 | |||
96 | static __u32 radio_bits_get(struct radio_device *dev) | ||
97 | { | ||
98 | register __u16 io=dev->io, l, rdata; | ||
99 | register __u32 data=0; | ||
100 | __u16 omask; | ||
101 | omask = inw(io + IO_MASK); | ||
102 | outw(~(STR_CLK | STR_WREN), io + IO_MASK); | ||
103 | outw(0, io); | ||
104 | udelay(16); | ||
105 | |||
106 | for (l=24;l--;) { | ||
107 | outw(STR_CLK, io); /* HI state */ | ||
108 | udelay(2); | ||
109 | if(!l) | ||
110 | dev->tuned = inw(io) & STR_MOST ? 0 : 0xffff; | ||
111 | outw(0, io); /* LO state */ | ||
112 | udelay(2); | ||
113 | data <<= 1; /* shift data */ | ||
114 | rdata = inw(io); | ||
115 | if(!l) | ||
116 | dev->stereo = rdata & STR_MOST ? | ||
117 | 0 : VIDEO_TUNER_STEREO_ON; | ||
118 | else | ||
119 | if(rdata & STR_DATA) | ||
120 | data++; | ||
121 | udelay(2); | ||
122 | } | ||
123 | if(dev->muted) | ||
124 | outw(STR_WREN, io); | ||
125 | udelay(4); | ||
126 | outw(omask, io + IO_MASK); | ||
127 | return data & 0x3ffe; | ||
128 | } | ||
129 | |||
130 | static void radio_bits_set(struct radio_device *dev, __u32 data) | ||
131 | { | ||
132 | register __u16 io=dev->io, l, bits; | ||
133 | __u16 omask, odir; | ||
134 | omask = inw(io + IO_MASK); | ||
135 | odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN); | ||
136 | outw(odir | STR_DATA, io + IO_DIR); | ||
137 | outw(~(STR_DATA | STR_CLK | STR_WREN), io + IO_MASK); | ||
138 | udelay(16); | ||
139 | for (l=25;l;l--) { | ||
140 | bits = ((data >> 18) & STR_DATA) | STR_WREN ; | ||
141 | data <<= 1; /* shift data */ | ||
142 | outw(bits, io); /* start strobe */ | ||
143 | udelay(2); | ||
144 | outw(bits | STR_CLK, io); /* HI level */ | ||
145 | udelay(2); | ||
146 | outw(bits, io); /* LO level */ | ||
147 | udelay(4); | ||
148 | } | ||
149 | if(!dev->muted) | ||
150 | outw(0, io); | ||
151 | udelay(4); | ||
152 | outw(omask, io + IO_MASK); | ||
153 | outw(odir, io + IO_DIR); | ||
154 | msleep(125); | ||
155 | } | ||
156 | |||
157 | inline static int radio_function(struct inode *inode, struct file *file, | ||
158 | unsigned int cmd, void *arg) | ||
159 | { | ||
160 | struct video_device *dev = video_devdata(file); | ||
161 | struct radio_device *card=dev->priv; | ||
162 | |||
163 | switch(cmd) { | ||
164 | case VIDIOCGCAP: { | ||
165 | struct video_capability *v = arg; | ||
166 | memset(v,0,sizeof(*v)); | ||
167 | strcpy(v->name, "Maestro radio"); | ||
168 | v->type=VID_TYPE_TUNER; | ||
169 | v->channels=v->audios=1; | ||
170 | return 0; | ||
171 | } | ||
172 | case VIDIOCGTUNER: { | ||
173 | struct video_tuner *v = arg; | ||
174 | if(v->tuner) | ||
175 | return -EINVAL; | ||
176 | (void)radio_bits_get(card); | ||
177 | v->flags = VIDEO_TUNER_LOW | card->stereo; | ||
178 | v->signal = card->tuned; | ||
179 | strcpy(v->name, "FM"); | ||
180 | v->rangelow = FREQ_LO; | ||
181 | v->rangehigh = FREQ_HI; | ||
182 | v->mode = VIDEO_MODE_AUTO; | ||
183 | return 0; | ||
184 | } | ||
185 | case VIDIOCSTUNER: { | ||
186 | struct video_tuner *v = arg; | ||
187 | if(v->tuner!=0) | ||
188 | return -EINVAL; | ||
189 | return 0; | ||
190 | } | ||
191 | case VIDIOCGFREQ: { | ||
192 | unsigned long *freq = arg; | ||
193 | *freq = BITS2FREQ(radio_bits_get(card)); | ||
194 | return 0; | ||
195 | } | ||
196 | case VIDIOCSFREQ: { | ||
197 | unsigned long *freq = arg; | ||
198 | if (*freq<FREQ_LO || *freq>FREQ_HI ) | ||
199 | return -EINVAL; | ||
200 | radio_bits_set(card, FREQ2BITS(*freq)); | ||
201 | return 0; | ||
202 | } | ||
203 | case VIDIOCGAUDIO: { | ||
204 | struct video_audio *v = arg; | ||
205 | memset(v,0,sizeof(*v)); | ||
206 | strcpy(v->name, "Radio"); | ||
207 | v->flags=VIDEO_AUDIO_MUTABLE | card->muted; | ||
208 | v->mode=VIDEO_SOUND_STEREO; | ||
209 | return 0; | ||
210 | } | ||
211 | case VIDIOCSAUDIO: { | ||
212 | struct video_audio *v = arg; | ||
213 | if(v->audio) | ||
214 | return -EINVAL; | ||
215 | { | ||
216 | register __u16 io=card->io; | ||
217 | register __u16 omask = inw(io + IO_MASK); | ||
218 | outw(~STR_WREN, io + IO_MASK); | ||
219 | outw((card->muted = v->flags & VIDEO_AUDIO_MUTE) | ||
220 | ? STR_WREN : 0, io); | ||
221 | udelay(4); | ||
222 | outw(omask, io + IO_MASK); | ||
223 | msleep(125); | ||
224 | return 0; | ||
225 | } | ||
226 | } | ||
227 | case VIDIOCGUNIT: { | ||
228 | struct video_unit *v = arg; | ||
229 | v->video=VIDEO_NO_UNIT; | ||
230 | v->vbi=VIDEO_NO_UNIT; | ||
231 | v->radio=dev->minor; | ||
232 | v->audio=0; | ||
233 | v->teletext=VIDEO_NO_UNIT; | ||
234 | return 0; | ||
235 | } | ||
236 | default: return -ENOIOCTLCMD; | ||
237 | } | ||
238 | } | ||
239 | |||
240 | static int radio_ioctl(struct inode *inode, struct file *file, | ||
241 | unsigned int cmd, unsigned long arg) | ||
242 | { | ||
243 | struct video_device *dev = video_devdata(file); | ||
244 | struct radio_device *card=dev->priv; | ||
245 | int ret; | ||
246 | |||
247 | down(&card->lock); | ||
248 | ret = video_usercopy(inode, file, cmd, arg, radio_function); | ||
249 | up(&card->lock); | ||
250 | return ret; | ||
251 | } | ||
252 | |||
253 | static __u16 radio_install(struct pci_dev *pcidev); | ||
254 | |||
255 | MODULE_AUTHOR("Adam Tlalka, atlka@pg.gda.pl"); | ||
256 | MODULE_DESCRIPTION("Radio driver for the Maestro PCI sound card radio."); | ||
257 | MODULE_LICENSE("GPL"); | ||
258 | |||
259 | static void __exit maestro_radio_exit(void) | ||
260 | { | ||
261 | video_unregister_device(&maestro_radio); | ||
262 | } | ||
263 | |||
264 | static int __init maestro_radio_init(void) | ||
265 | { | ||
266 | register __u16 found=0; | ||
267 | struct pci_dev *pcidev = NULL; | ||
268 | while(!found && (pcidev = pci_find_device(PCI_VENDOR_ESS, | ||
269 | PCI_DEVICE_ID_ESS_ESS1968, | ||
270 | pcidev))) | ||
271 | found |= radio_install(pcidev); | ||
272 | while(!found && (pcidev = pci_find_device(PCI_VENDOR_ESS, | ||
273 | PCI_DEVICE_ID_ESS_ESS1978, | ||
274 | pcidev))) | ||
275 | found |= radio_install(pcidev); | ||
276 | if(!found) { | ||
277 | printk(KERN_INFO "radio-maestro: no devices found.\n"); | ||
278 | return -ENODEV; | ||
279 | } | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | module_init(maestro_radio_init); | ||
284 | module_exit(maestro_radio_exit); | ||
285 | |||
286 | inline static __u16 radio_power_on(struct radio_device *dev) | ||
287 | { | ||
288 | register __u16 io=dev->io; | ||
289 | register __u32 ofreq; | ||
290 | __u16 omask, odir; | ||
291 | omask = inw(io + IO_MASK); | ||
292 | odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN); | ||
293 | outw(odir & ~STR_WREN, io + IO_DIR); | ||
294 | dev->muted = inw(io) & STR_WREN ? 0 : VIDEO_AUDIO_MUTE; | ||
295 | outw(odir, io + IO_DIR); | ||
296 | outw(~(STR_WREN | STR_CLK), io + IO_MASK); | ||
297 | outw(dev->muted ? 0 : STR_WREN, io); | ||
298 | udelay(16); | ||
299 | outw(omask, io + IO_MASK); | ||
300 | ofreq = radio_bits_get(dev); | ||
301 | if((ofreq<FREQ2BITS(FREQ_LO)) || (ofreq>FREQ2BITS(FREQ_HI))) | ||
302 | ofreq = FREQ2BITS(FREQ_LO); | ||
303 | radio_bits_set(dev, ofreq); | ||
304 | return (ofreq == radio_bits_get(dev)); | ||
305 | } | ||
306 | |||
307 | static __u16 radio_install(struct pci_dev *pcidev) | ||
308 | { | ||
309 | if(((pcidev->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO) | ||
310 | return 0; | ||
311 | |||
312 | radio_unit.io = pcidev->resource[0].start + GPIO_DATA; | ||
313 | maestro_radio.priv = &radio_unit; | ||
314 | init_MUTEX(&radio_unit.lock); | ||
315 | |||
316 | if(radio_power_on(&radio_unit)) { | ||
317 | if(video_register_device(&maestro_radio, VFL_TYPE_RADIO, radio_nr)==-1) { | ||
318 | printk("radio-maestro: can't register device!"); | ||
319 | return 0; | ||
320 | } | ||
321 | printk(KERN_INFO "radio-maestro: version " | ||
322 | DRIVER_VERSION | ||
323 | " time " | ||
324 | __TIME__ " " | ||
325 | __DATE__ | ||
326 | "\n"); | ||
327 | printk(KERN_INFO "radio-maestro: radio chip initialized\n"); | ||
328 | return 1; | ||
329 | } else | ||
330 | return 0; | ||
331 | } | ||
332 | |||
diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c new file mode 100644 index 000000000000..5b748a48ce72 --- /dev/null +++ b/drivers/media/radio/radio-maxiradio.c | |||
@@ -0,0 +1,349 @@ | |||
1 | /* | ||
2 | * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux | ||
3 | * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net> | ||
4 | * | ||
5 | * Based in the radio Maestro PCI driver. Actually it uses the same chip | ||
6 | * for radio but different pci controller. | ||
7 | * | ||
8 | * I didn't have any specs I reversed engineered the protocol from | ||
9 | * the windows driver (radio.dll). | ||
10 | * | ||
11 | * The card uses the TEA5757 chip that includes a search function but it | ||
12 | * is useless as I haven't found any way to read back the frequency. If | ||
13 | * anybody does please mail me. | ||
14 | * | ||
15 | * For the pdf file see: | ||
16 | * http://www.semiconductors.philips.com/pip/TEA5757H/V1 | ||
17 | * | ||
18 | * | ||
19 | * CHANGES: | ||
20 | * 0.75b | ||
21 | * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr> | ||
22 | * | ||
23 | * 0.75 | ||
24 | * - tiding up | ||
25 | * - removed support for multiple devices as it didn't work anyway | ||
26 | * | ||
27 | * BUGS: | ||
28 | * - card unmutes if you change frequency | ||
29 | * | ||
30 | */ | ||
31 | |||
32 | |||
33 | #include <linux/module.h> | ||
34 | #include <linux/init.h> | ||
35 | #include <linux/ioport.h> | ||
36 | #include <linux/delay.h> | ||
37 | #include <linux/sched.h> | ||
38 | #include <asm/io.h> | ||
39 | #include <asm/uaccess.h> | ||
40 | #include <asm/semaphore.h> | ||
41 | #include <linux/pci.h> | ||
42 | #include <linux/videodev.h> | ||
43 | |||
44 | /* version 0.75 Sun Feb 4 22:51:27 EET 2001 */ | ||
45 | #define DRIVER_VERSION "0.75" | ||
46 | |||
47 | #ifndef PCI_VENDOR_ID_GUILLEMOT | ||
48 | #define PCI_VENDOR_ID_GUILLEMOT 0x5046 | ||
49 | #endif | ||
50 | |||
51 | #ifndef PCI_DEVICE_ID_GUILLEMOT | ||
52 | #define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 | ||
53 | #endif | ||
54 | |||
55 | |||
56 | /* TEA5757 pin mappings */ | ||
57 | static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ; | ||
58 | |||
59 | static int radio_nr = -1; | ||
60 | module_param(radio_nr, int, 0); | ||
61 | |||
62 | |||
63 | #define FREQ_LO 50*16000 | ||
64 | #define FREQ_HI 150*16000 | ||
65 | |||
66 | #define FREQ_IF 171200 /* 10.7*16000 */ | ||
67 | #define FREQ_STEP 200 /* 12.5*16 */ | ||
68 | |||
69 | #define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\ | ||
70 | /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */ | ||
71 | |||
72 | #define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) | ||
73 | |||
74 | |||
75 | static int radio_ioctl(struct inode *inode, struct file *file, | ||
76 | unsigned int cmd, unsigned long arg); | ||
77 | |||
78 | static struct file_operations maxiradio_fops = { | ||
79 | .owner = THIS_MODULE, | ||
80 | .open = video_exclusive_open, | ||
81 | .release = video_exclusive_release, | ||
82 | .ioctl = radio_ioctl, | ||
83 | .llseek = no_llseek, | ||
84 | }; | ||
85 | static struct video_device maxiradio_radio = | ||
86 | { | ||
87 | .owner = THIS_MODULE, | ||
88 | .name = "Maxi Radio FM2000 radio", | ||
89 | .type = VID_TYPE_TUNER, | ||
90 | .hardware = VID_HARDWARE_SF16MI, | ||
91 | .fops = &maxiradio_fops, | ||
92 | }; | ||
93 | |||
94 | static struct radio_device | ||
95 | { | ||
96 | __u16 io, /* base of radio io */ | ||
97 | muted, /* VIDEO_AUDIO_MUTE */ | ||
98 | stereo, /* VIDEO_TUNER_STEREO_ON */ | ||
99 | tuned; /* signal strength (0 or 0xffff) */ | ||
100 | |||
101 | unsigned long freq; | ||
102 | |||
103 | struct semaphore lock; | ||
104 | } radio_unit = {0, 0, 0, 0, }; | ||
105 | |||
106 | |||
107 | static void outbit(unsigned long bit, __u16 io) | ||
108 | { | ||
109 | if(bit != 0) | ||
110 | { | ||
111 | outb( power|wren|data ,io); udelay(4); | ||
112 | outb( power|wren|data|clk ,io); udelay(4); | ||
113 | outb( power|wren|data ,io); udelay(4); | ||
114 | } | ||
115 | else | ||
116 | { | ||
117 | outb( power|wren ,io); udelay(4); | ||
118 | outb( power|wren|clk ,io); udelay(4); | ||
119 | outb( power|wren ,io); udelay(4); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | static void turn_power(__u16 io, int p) | ||
124 | { | ||
125 | if(p != 0) outb(power, io); else outb(0,io); | ||
126 | } | ||
127 | |||
128 | |||
129 | static void set_freq(__u16 io, __u32 data) | ||
130 | { | ||
131 | unsigned long int si; | ||
132 | int bl; | ||
133 | |||
134 | /* TEA5757 shift register bits (see pdf) */ | ||
135 | |||
136 | outbit(0,io); // 24 search | ||
137 | outbit(1,io); // 23 search up/down | ||
138 | |||
139 | outbit(0,io); // 22 stereo/mono | ||
140 | |||
141 | outbit(0,io); // 21 band | ||
142 | outbit(0,io); // 20 band (only 00=FM works I think) | ||
143 | |||
144 | outbit(0,io); // 19 port ? | ||
145 | outbit(0,io); // 18 port ? | ||
146 | |||
147 | outbit(0,io); // 17 search level | ||
148 | outbit(0,io); // 16 search level | ||
149 | |||
150 | si = 0x8000; | ||
151 | for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; } | ||
152 | |||
153 | outb(power,io); | ||
154 | } | ||
155 | |||
156 | static int get_stereo(__u16 io) | ||
157 | { | ||
158 | outb(power,io); udelay(4); | ||
159 | return !(inb(io) & mo_st); | ||
160 | } | ||
161 | |||
162 | static int get_tune(__u16 io) | ||
163 | { | ||
164 | outb(power+clk,io); udelay(4); | ||
165 | return !(inb(io) & mo_st); | ||
166 | } | ||
167 | |||
168 | |||
169 | inline static int radio_function(struct inode *inode, struct file *file, | ||
170 | unsigned int cmd, void *arg) | ||
171 | { | ||
172 | struct video_device *dev = video_devdata(file); | ||
173 | struct radio_device *card=dev->priv; | ||
174 | |||
175 | switch(cmd) { | ||
176 | case VIDIOCGCAP: { | ||
177 | struct video_capability *v = arg; | ||
178 | |||
179 | memset(v,0,sizeof(*v)); | ||
180 | strcpy(v->name, "Maxi Radio FM2000 radio"); | ||
181 | v->type=VID_TYPE_TUNER; | ||
182 | v->channels=v->audios=1; | ||
183 | return 0; | ||
184 | } | ||
185 | case VIDIOCGTUNER: { | ||
186 | struct video_tuner *v = arg; | ||
187 | |||
188 | if(v->tuner) | ||
189 | return -EINVAL; | ||
190 | |||
191 | card->stereo = 0xffff * get_stereo(card->io); | ||
192 | card->tuned = 0xffff * get_tune(card->io); | ||
193 | |||
194 | v->flags = VIDEO_TUNER_LOW | card->stereo; | ||
195 | v->signal = card->tuned; | ||
196 | |||
197 | strcpy(v->name, "FM"); | ||
198 | |||
199 | v->rangelow = FREQ_LO; | ||
200 | v->rangehigh = FREQ_HI; | ||
201 | v->mode = VIDEO_MODE_AUTO; | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | case VIDIOCSTUNER: { | ||
206 | struct video_tuner *v = arg; | ||
207 | if(v->tuner!=0) | ||
208 | return -EINVAL; | ||
209 | return 0; | ||
210 | } | ||
211 | case VIDIOCGFREQ: { | ||
212 | unsigned long *freq = arg; | ||
213 | |||
214 | *freq = card->freq; | ||
215 | return 0; | ||
216 | } | ||
217 | case VIDIOCSFREQ: { | ||
218 | unsigned long *freq = arg; | ||
219 | |||
220 | if (*freq < FREQ_LO || *freq > FREQ_HI) | ||
221 | return -EINVAL; | ||
222 | card->freq = *freq; | ||
223 | set_freq(card->io, FREQ2BITS(card->freq)); | ||
224 | msleep(125); | ||
225 | return 0; | ||
226 | } | ||
227 | case VIDIOCGAUDIO: { | ||
228 | struct video_audio *v = arg; | ||
229 | memset(v,0,sizeof(*v)); | ||
230 | strcpy(v->name, "Radio"); | ||
231 | v->flags=VIDEO_AUDIO_MUTABLE | card->muted; | ||
232 | v->mode=VIDEO_SOUND_STEREO; | ||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | case VIDIOCSAUDIO: { | ||
237 | struct video_audio *v = arg; | ||
238 | |||
239 | if(v->audio) | ||
240 | return -EINVAL; | ||
241 | card->muted = v->flags & VIDEO_AUDIO_MUTE; | ||
242 | if(card->muted) | ||
243 | turn_power(card->io, 0); | ||
244 | else | ||
245 | set_freq(card->io, FREQ2BITS(card->freq)); | ||
246 | return 0; | ||
247 | } | ||
248 | case VIDIOCGUNIT: { | ||
249 | struct video_unit *v = arg; | ||
250 | |||
251 | v->video=VIDEO_NO_UNIT; | ||
252 | v->vbi=VIDEO_NO_UNIT; | ||
253 | v->radio=dev->minor; | ||
254 | v->audio=0; | ||
255 | v->teletext=VIDEO_NO_UNIT; | ||
256 | return 0; | ||
257 | } | ||
258 | default: return -ENOIOCTLCMD; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | static int radio_ioctl(struct inode *inode, struct file *file, | ||
263 | unsigned int cmd, unsigned long arg) | ||
264 | { | ||
265 | struct video_device *dev = video_devdata(file); | ||
266 | struct radio_device *card=dev->priv; | ||
267 | int ret; | ||
268 | |||
269 | down(&card->lock); | ||
270 | ret = video_usercopy(inode, file, cmd, arg, radio_function); | ||
271 | up(&card->lock); | ||
272 | return ret; | ||
273 | } | ||
274 | |||
275 | MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); | ||
276 | MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); | ||
277 | MODULE_LICENSE("GPL"); | ||
278 | |||
279 | |||
280 | static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | ||
281 | { | ||
282 | if(!request_region(pci_resource_start(pdev, 0), | ||
283 | pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { | ||
284 | printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n"); | ||
285 | goto err_out; | ||
286 | } | ||
287 | |||
288 | if (pci_enable_device(pdev)) | ||
289 | goto err_out_free_region; | ||
290 | |||
291 | radio_unit.io = pci_resource_start(pdev, 0); | ||
292 | init_MUTEX(&radio_unit.lock); | ||
293 | maxiradio_radio.priv = &radio_unit; | ||
294 | |||
295 | if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) { | ||
296 | printk("radio-maxiradio: can't register device!"); | ||
297 | goto err_out_free_region; | ||
298 | } | ||
299 | |||
300 | printk(KERN_INFO "radio-maxiradio: version " | ||
301 | DRIVER_VERSION | ||
302 | " time " | ||
303 | __TIME__ " " | ||
304 | __DATE__ | ||
305 | "\n"); | ||
306 | |||
307 | printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n", | ||
308 | radio_unit.io); | ||
309 | return 0; | ||
310 | |||
311 | err_out_free_region: | ||
312 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | ||
313 | err_out: | ||
314 | return -ENODEV; | ||
315 | } | ||
316 | |||
317 | static void __devexit maxiradio_remove_one(struct pci_dev *pdev) | ||
318 | { | ||
319 | video_unregister_device(&maxiradio_radio); | ||
320 | release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); | ||
321 | } | ||
322 | |||
323 | static struct pci_device_id maxiradio_pci_tbl[] = { | ||
324 | { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, | ||
325 | PCI_ANY_ID, PCI_ANY_ID, }, | ||
326 | { 0,} | ||
327 | }; | ||
328 | |||
329 | MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); | ||
330 | |||
331 | static struct pci_driver maxiradio_driver = { | ||
332 | .name = "radio-maxiradio", | ||
333 | .id_table = maxiradio_pci_tbl, | ||
334 | .probe = maxiradio_init_one, | ||
335 | .remove = __devexit_p(maxiradio_remove_one), | ||
336 | }; | ||
337 | |||
338 | static int __init maxiradio_radio_init(void) | ||
339 | { | ||
340 | return pci_module_init(&maxiradio_driver); | ||
341 | } | ||
342 | |||
343 | static void __exit maxiradio_radio_exit(void) | ||
344 | { | ||
345 | pci_unregister_driver(&maxiradio_driver); | ||
346 | } | ||
347 | |||
348 | module_init(maxiradio_radio_init); | ||
349 | module_exit(maxiradio_radio_exit); | ||
diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c new file mode 100644 index 000000000000..c00245d4d249 --- /dev/null +++ b/drivers/media/radio/radio-rtrack2.c | |||
@@ -0,0 +1,266 @@ | |||
1 | /* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff | ||
2 | * | ||
3 | * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood | ||
4 | * Converted to new API by Alan Cox <Alan.Cox@linux.org> | ||
5 | * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> | ||
6 | * | ||
7 | * TODO: Allow for more than one of these foolish entities :-) | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #include <linux/module.h> /* Modules */ | ||
12 | #include <linux/init.h> /* Initdata */ | ||
13 | #include <linux/ioport.h> /* check_region, request_region */ | ||
14 | #include <linux/delay.h> /* udelay */ | ||
15 | #include <asm/io.h> /* outb, outb_p */ | ||
16 | #include <asm/uaccess.h> /* copy to/from user */ | ||
17 | #include <linux/videodev.h> /* kernel radio structs */ | ||
18 | #include <linux/config.h> /* CONFIG_RADIO_RTRACK2_PORT */ | ||
19 | #include <linux/spinlock.h> | ||
20 | |||
21 | #ifndef CONFIG_RADIO_RTRACK2_PORT | ||
22 | #define CONFIG_RADIO_RTRACK2_PORT -1 | ||
23 | #endif | ||
24 | |||
25 | static int io = CONFIG_RADIO_RTRACK2_PORT; | ||
26 | static int radio_nr = -1; | ||
27 | static spinlock_t lock; | ||
28 | |||
29 | struct rt_device | ||
30 | { | ||
31 | int port; | ||
32 | unsigned long curfreq; | ||
33 | int muted; | ||
34 | }; | ||
35 | |||
36 | |||
37 | /* local things */ | ||
38 | |||
39 | static void rt_mute(struct rt_device *dev) | ||
40 | { | ||
41 | if(dev->muted) | ||
42 | return; | ||
43 | spin_lock(&lock); | ||
44 | outb(1, io); | ||
45 | spin_unlock(&lock); | ||
46 | dev->muted = 1; | ||
47 | } | ||
48 | |||
49 | static void rt_unmute(struct rt_device *dev) | ||
50 | { | ||
51 | if(dev->muted == 0) | ||
52 | return; | ||
53 | spin_lock(&lock); | ||
54 | outb(0, io); | ||
55 | spin_unlock(&lock); | ||
56 | dev->muted = 0; | ||
57 | } | ||
58 | |||
59 | static void zero(void) | ||
60 | { | ||
61 | outb_p(1, io); | ||
62 | outb_p(3, io); | ||
63 | outb_p(1, io); | ||
64 | } | ||
65 | |||
66 | static void one(void) | ||
67 | { | ||
68 | outb_p(5, io); | ||
69 | outb_p(7, io); | ||
70 | outb_p(5, io); | ||
71 | } | ||
72 | |||
73 | static int rt_setfreq(struct rt_device *dev, unsigned long freq) | ||
74 | { | ||
75 | int i; | ||
76 | |||
77 | freq = freq / 200 + 856; | ||
78 | |||
79 | spin_lock(&lock); | ||
80 | |||
81 | outb_p(0xc8, io); | ||
82 | outb_p(0xc9, io); | ||
83 | outb_p(0xc9, io); | ||
84 | |||
85 | for (i = 0; i < 10; i++) | ||
86 | zero (); | ||
87 | |||
88 | for (i = 14; i >= 0; i--) | ||
89 | if (freq & (1 << i)) | ||
90 | one (); | ||
91 | else | ||
92 | zero (); | ||
93 | |||
94 | outb_p(0xc8, io); | ||
95 | if (!dev->muted) | ||
96 | outb_p(0, io); | ||
97 | |||
98 | spin_unlock(&lock); | ||
99 | return 0; | ||
100 | } | ||
101 | |||
102 | static int rt_getsigstr(struct rt_device *dev) | ||
103 | { | ||
104 | if (inb(io) & 2) /* bit set = no signal present */ | ||
105 | return 0; | ||
106 | return 1; /* signal present */ | ||
107 | } | ||
108 | |||
109 | static int rt_do_ioctl(struct inode *inode, struct file *file, | ||
110 | unsigned int cmd, void *arg) | ||
111 | { | ||
112 | struct video_device *dev = video_devdata(file); | ||
113 | struct rt_device *rt=dev->priv; | ||
114 | |||
115 | switch(cmd) | ||
116 | { | ||
117 | case VIDIOCGCAP: | ||
118 | { | ||
119 | struct video_capability *v = arg; | ||
120 | memset(v,0,sizeof(*v)); | ||
121 | v->type=VID_TYPE_TUNER; | ||
122 | v->channels=1; | ||
123 | v->audios=1; | ||
124 | strcpy(v->name, "RadioTrack II"); | ||
125 | return 0; | ||
126 | } | ||
127 | case VIDIOCGTUNER: | ||
128 | { | ||
129 | struct video_tuner *v = arg; | ||
130 | if(v->tuner) /* Only 1 tuner */ | ||
131 | return -EINVAL; | ||
132 | v->rangelow=88*16000; | ||
133 | v->rangehigh=108*16000; | ||
134 | v->flags=VIDEO_TUNER_LOW; | ||
135 | v->mode=VIDEO_MODE_AUTO; | ||
136 | v->signal=0xFFFF*rt_getsigstr(rt); | ||
137 | strcpy(v->name, "FM"); | ||
138 | return 0; | ||
139 | } | ||
140 | case VIDIOCSTUNER: | ||
141 | { | ||
142 | struct video_tuner *v = arg; | ||
143 | if(v->tuner!=0) | ||
144 | return -EINVAL; | ||
145 | /* Only 1 tuner so no setting needed ! */ | ||
146 | return 0; | ||
147 | } | ||
148 | case VIDIOCGFREQ: | ||
149 | { | ||
150 | unsigned long *freq = arg; | ||
151 | *freq = rt->curfreq; | ||
152 | return 0; | ||
153 | } | ||
154 | case VIDIOCSFREQ: | ||
155 | { | ||
156 | unsigned long *freq = arg; | ||
157 | rt->curfreq = *freq; | ||
158 | rt_setfreq(rt, rt->curfreq); | ||
159 | return 0; | ||
160 | } | ||
161 | case VIDIOCGAUDIO: | ||
162 | { | ||
163 | struct video_audio *v = arg; | ||
164 | memset(v,0, sizeof(*v)); | ||
165 | v->flags|=VIDEO_AUDIO_MUTABLE; | ||
166 | v->volume=1; | ||
167 | v->step=65535; | ||
168 | strcpy(v->name, "Radio"); | ||
169 | return 0; | ||
170 | } | ||
171 | case VIDIOCSAUDIO: | ||
172 | { | ||
173 | struct video_audio *v = arg; | ||
174 | if(v->audio) | ||
175 | return -EINVAL; | ||
176 | |||
177 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
178 | rt_mute(rt); | ||
179 | else | ||
180 | rt_unmute(rt); | ||
181 | |||
182 | return 0; | ||
183 | } | ||
184 | default: | ||
185 | return -ENOIOCTLCMD; | ||
186 | } | ||
187 | } | ||
188 | |||
189 | static int rt_ioctl(struct inode *inode, struct file *file, | ||
190 | unsigned int cmd, unsigned long arg) | ||
191 | { | ||
192 | return video_usercopy(inode, file, cmd, arg, rt_do_ioctl); | ||
193 | } | ||
194 | |||
195 | static struct rt_device rtrack2_unit; | ||
196 | |||
197 | static struct file_operations rtrack2_fops = { | ||
198 | .owner = THIS_MODULE, | ||
199 | .open = video_exclusive_open, | ||
200 | .release = video_exclusive_release, | ||
201 | .ioctl = rt_ioctl, | ||
202 | .llseek = no_llseek, | ||
203 | }; | ||
204 | |||
205 | static struct video_device rtrack2_radio= | ||
206 | { | ||
207 | .owner = THIS_MODULE, | ||
208 | .name = "RadioTrack II radio", | ||
209 | .type = VID_TYPE_TUNER, | ||
210 | .hardware = VID_HARDWARE_RTRACK2, | ||
211 | .fops = &rtrack2_fops, | ||
212 | }; | ||
213 | |||
214 | static int __init rtrack2_init(void) | ||
215 | { | ||
216 | if(io==-1) | ||
217 | { | ||
218 | printk(KERN_ERR "You must set an I/O address with io=0x20c or io=0x30c\n"); | ||
219 | return -EINVAL; | ||
220 | } | ||
221 | if (!request_region(io, 4, "rtrack2")) | ||
222 | { | ||
223 | printk(KERN_ERR "rtrack2: port 0x%x already in use\n", io); | ||
224 | return -EBUSY; | ||
225 | } | ||
226 | |||
227 | rtrack2_radio.priv=&rtrack2_unit; | ||
228 | |||
229 | spin_lock_init(&lock); | ||
230 | if(video_register_device(&rtrack2_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
231 | { | ||
232 | release_region(io, 4); | ||
233 | return -EINVAL; | ||
234 | } | ||
235 | |||
236 | printk(KERN_INFO "AIMSlab Radiotrack II card driver.\n"); | ||
237 | |||
238 | /* mute card - prevents noisy bootups */ | ||
239 | outb(1, io); | ||
240 | rtrack2_unit.muted = 1; | ||
241 | |||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | MODULE_AUTHOR("Ben Pfaff"); | ||
246 | MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); | ||
247 | MODULE_LICENSE("GPL"); | ||
248 | |||
249 | module_param(io, int, 0); | ||
250 | MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); | ||
251 | module_param(radio_nr, int, 0); | ||
252 | |||
253 | static void __exit rtrack2_cleanup_module(void) | ||
254 | { | ||
255 | video_unregister_device(&rtrack2_radio); | ||
256 | release_region(io,4); | ||
257 | } | ||
258 | |||
259 | module_init(rtrack2_init); | ||
260 | module_exit(rtrack2_cleanup_module); | ||
261 | |||
262 | /* | ||
263 | Local variables: | ||
264 | compile-command: "mmake" | ||
265 | End: | ||
266 | */ | ||
diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c new file mode 100644 index 000000000000..3a464a09221f --- /dev/null +++ b/drivers/media/radio/radio-sf16fmi.c | |||
@@ -0,0 +1,328 @@ | |||
1 | /* SF16FMI radio driver for Linux radio support | ||
2 | * heavily based on rtrack driver... | ||
3 | * (c) 1997 M. Kirkwood | ||
4 | * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz | ||
5 | * | ||
6 | * Fitted to new interface by Alan Cox <alan.cox@linux.org> | ||
7 | * Made working and cleaned up functions <mikael.hedin@irf.se> | ||
8 | * Support for ISAPnP by Ladislav Michl <ladis@psi.cz> | ||
9 | * | ||
10 | * Notes on the hardware | ||
11 | * | ||
12 | * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | ||
13 | * No volume control - only mute/unmute - you have to use line volume | ||
14 | * control on SB-part of SF16FMI | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> /* __setup */ | ||
19 | #include <linux/module.h> /* Modules */ | ||
20 | #include <linux/init.h> /* Initdata */ | ||
21 | #include <linux/ioport.h> /* check_region, request_region */ | ||
22 | #include <linux/delay.h> /* udelay */ | ||
23 | #include <linux/videodev.h> /* kernel radio structs */ | ||
24 | #include <linux/isapnp.h> | ||
25 | #include <asm/io.h> /* outb, outb_p */ | ||
26 | #include <asm/uaccess.h> /* copy to/from user */ | ||
27 | #include <asm/semaphore.h> | ||
28 | |||
29 | struct fmi_device | ||
30 | { | ||
31 | int port; | ||
32 | int curvol; /* 1 or 0 */ | ||
33 | unsigned long curfreq; /* freq in kHz */ | ||
34 | __u32 flags; | ||
35 | }; | ||
36 | |||
37 | static int io = -1; | ||
38 | static int radio_nr = -1; | ||
39 | static struct pnp_dev *dev = NULL; | ||
40 | static struct semaphore lock; | ||
41 | |||
42 | /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ | ||
43 | /* It is only useful to give freq in intervall of 800 (=0.05Mhz), | ||
44 | * other bits will be truncated, e.g 92.7400016 -> 92.7, but | ||
45 | * 92.7400017 -> 92.75 | ||
46 | */ | ||
47 | #define RSF16_ENCODE(x) ((x)/800+214) | ||
48 | #define RSF16_MINFREQ 87*16000 | ||
49 | #define RSF16_MAXFREQ 108*16000 | ||
50 | |||
51 | static void outbits(int bits, unsigned int data, int port) | ||
52 | { | ||
53 | while(bits--) { | ||
54 | if(data & 1) { | ||
55 | outb(5, port); | ||
56 | udelay(6); | ||
57 | outb(7, port); | ||
58 | udelay(6); | ||
59 | } else { | ||
60 | outb(1, port); | ||
61 | udelay(6); | ||
62 | outb(3, port); | ||
63 | udelay(6); | ||
64 | } | ||
65 | data>>=1; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | static inline void fmi_mute(int port) | ||
70 | { | ||
71 | down(&lock); | ||
72 | outb(0x00, port); | ||
73 | up(&lock); | ||
74 | } | ||
75 | |||
76 | static inline void fmi_unmute(int port) | ||
77 | { | ||
78 | down(&lock); | ||
79 | outb(0x08, port); | ||
80 | up(&lock); | ||
81 | } | ||
82 | |||
83 | static inline int fmi_setfreq(struct fmi_device *dev) | ||
84 | { | ||
85 | int myport = dev->port; | ||
86 | unsigned long freq = dev->curfreq; | ||
87 | |||
88 | down(&lock); | ||
89 | |||
90 | outbits(16, RSF16_ENCODE(freq), myport); | ||
91 | outbits(8, 0xC0, myport); | ||
92 | msleep(143); /* was schedule_timeout(HZ/7) */ | ||
93 | up(&lock); | ||
94 | if (dev->curvol) fmi_unmute(myport); | ||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static inline int fmi_getsigstr(struct fmi_device *dev) | ||
99 | { | ||
100 | int val; | ||
101 | int res; | ||
102 | int myport = dev->port; | ||
103 | |||
104 | |||
105 | down(&lock); | ||
106 | val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ | ||
107 | outb(val, myport); | ||
108 | outb(val | 0x10, myport); | ||
109 | msleep(143); /* was schedule_timeout(HZ/7) */ | ||
110 | res = (int)inb(myport+1); | ||
111 | outb(val, myport); | ||
112 | |||
113 | up(&lock); | ||
114 | return (res & 2) ? 0 : 0xFFFF; | ||
115 | } | ||
116 | |||
117 | static int fmi_do_ioctl(struct inode *inode, struct file *file, | ||
118 | unsigned int cmd, void *arg) | ||
119 | { | ||
120 | struct video_device *dev = video_devdata(file); | ||
121 | struct fmi_device *fmi=dev->priv; | ||
122 | |||
123 | switch(cmd) | ||
124 | { | ||
125 | case VIDIOCGCAP: | ||
126 | { | ||
127 | struct video_capability *v = arg; | ||
128 | memset(v,0,sizeof(*v)); | ||
129 | strcpy(v->name, "SF16-FMx radio"); | ||
130 | v->type=VID_TYPE_TUNER; | ||
131 | v->channels=1; | ||
132 | v->audios=1; | ||
133 | return 0; | ||
134 | } | ||
135 | case VIDIOCGTUNER: | ||
136 | { | ||
137 | struct video_tuner *v = arg; | ||
138 | int mult; | ||
139 | |||
140 | if(v->tuner) /* Only 1 tuner */ | ||
141 | return -EINVAL; | ||
142 | strcpy(v->name, "FM"); | ||
143 | mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000; | ||
144 | v->rangelow = RSF16_MINFREQ/mult; | ||
145 | v->rangehigh = RSF16_MAXFREQ/mult; | ||
146 | v->flags=fmi->flags; | ||
147 | v->mode=VIDEO_MODE_AUTO; | ||
148 | v->signal = fmi_getsigstr(fmi); | ||
149 | return 0; | ||
150 | } | ||
151 | case VIDIOCSTUNER: | ||
152 | { | ||
153 | struct video_tuner *v = arg; | ||
154 | if(v->tuner!=0) | ||
155 | return -EINVAL; | ||
156 | fmi->flags = v->flags & VIDEO_TUNER_LOW; | ||
157 | /* Only 1 tuner so no setting needed ! */ | ||
158 | return 0; | ||
159 | } | ||
160 | case VIDIOCGFREQ: | ||
161 | { | ||
162 | unsigned long *freq = arg; | ||
163 | *freq = fmi->curfreq; | ||
164 | if (!(fmi->flags & VIDEO_TUNER_LOW)) | ||
165 | *freq /= 1000; | ||
166 | return 0; | ||
167 | } | ||
168 | case VIDIOCSFREQ: | ||
169 | { | ||
170 | unsigned long *freq = arg; | ||
171 | if (!(fmi->flags & VIDEO_TUNER_LOW)) | ||
172 | *freq *= 1000; | ||
173 | if (*freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) | ||
174 | return -EINVAL; | ||
175 | /*rounding in steps of 800 to match th freq | ||
176 | that will be used */ | ||
177 | fmi->curfreq = (*freq/800)*800; | ||
178 | fmi_setfreq(fmi); | ||
179 | return 0; | ||
180 | } | ||
181 | case VIDIOCGAUDIO: | ||
182 | { | ||
183 | struct video_audio *v = arg; | ||
184 | memset(v,0,sizeof(*v)); | ||
185 | v->flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); | ||
186 | strcpy(v->name, "Radio"); | ||
187 | v->mode=VIDEO_SOUND_STEREO; | ||
188 | return 0; | ||
189 | } | ||
190 | case VIDIOCSAUDIO: | ||
191 | { | ||
192 | struct video_audio *v = arg; | ||
193 | if(v->audio) | ||
194 | return -EINVAL; | ||
195 | fmi->curvol= v->flags&VIDEO_AUDIO_MUTE ? 0 : 1; | ||
196 | fmi->curvol ? | ||
197 | fmi_unmute(fmi->port) : fmi_mute(fmi->port); | ||
198 | return 0; | ||
199 | } | ||
200 | case VIDIOCGUNIT: | ||
201 | { | ||
202 | struct video_unit *v = arg; | ||
203 | v->video=VIDEO_NO_UNIT; | ||
204 | v->vbi=VIDEO_NO_UNIT; | ||
205 | v->radio=dev->minor; | ||
206 | v->audio=0; /* How do we find out this??? */ | ||
207 | v->teletext=VIDEO_NO_UNIT; | ||
208 | return 0; | ||
209 | } | ||
210 | default: | ||
211 | return -ENOIOCTLCMD; | ||
212 | } | ||
213 | } | ||
214 | |||
215 | static int fmi_ioctl(struct inode *inode, struct file *file, | ||
216 | unsigned int cmd, unsigned long arg) | ||
217 | { | ||
218 | return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); | ||
219 | } | ||
220 | |||
221 | static struct fmi_device fmi_unit; | ||
222 | |||
223 | static struct file_operations fmi_fops = { | ||
224 | .owner = THIS_MODULE, | ||
225 | .open = video_exclusive_open, | ||
226 | .release = video_exclusive_release, | ||
227 | .ioctl = fmi_ioctl, | ||
228 | .llseek = no_llseek, | ||
229 | }; | ||
230 | |||
231 | static struct video_device fmi_radio= | ||
232 | { | ||
233 | .owner = THIS_MODULE, | ||
234 | .name = "SF16FMx radio", | ||
235 | .type = VID_TYPE_TUNER, | ||
236 | .hardware = VID_HARDWARE_SF16MI, | ||
237 | .fops = &fmi_fops, | ||
238 | }; | ||
239 | |||
240 | /* ladis: this is my card. does any other types exist? */ | ||
241 | static struct isapnp_device_id id_table[] __devinitdata = { | ||
242 | { ISAPNP_ANY_ID, ISAPNP_ANY_ID, | ||
243 | ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, | ||
244 | { ISAPNP_CARD_END, }, | ||
245 | }; | ||
246 | |||
247 | MODULE_DEVICE_TABLE(isapnp, id_table); | ||
248 | |||
249 | static int isapnp_fmi_probe(void) | ||
250 | { | ||
251 | int i = 0; | ||
252 | |||
253 | while (id_table[i].card_vendor != 0 && dev == NULL) { | ||
254 | dev = pnp_find_dev(NULL, id_table[i].vendor, | ||
255 | id_table[i].function, NULL); | ||
256 | i++; | ||
257 | } | ||
258 | |||
259 | if (!dev) | ||
260 | return -ENODEV; | ||
261 | if (pnp_device_attach(dev) < 0) | ||
262 | return -EAGAIN; | ||
263 | if (pnp_activate_dev(dev) < 0) { | ||
264 | printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); | ||
265 | pnp_device_detach(dev); | ||
266 | return -ENOMEM; | ||
267 | } | ||
268 | if (!pnp_port_valid(dev, 0)) { | ||
269 | pnp_device_detach(dev); | ||
270 | return -ENODEV; | ||
271 | } | ||
272 | |||
273 | i = pnp_port_start(dev, 0); | ||
274 | printk ("radio-sf16fmi: PnP reports card at %#x\n", i); | ||
275 | |||
276 | return i; | ||
277 | } | ||
278 | |||
279 | static int __init fmi_init(void) | ||
280 | { | ||
281 | if (io < 0) | ||
282 | io = isapnp_fmi_probe(); | ||
283 | if (io < 0) { | ||
284 | printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); | ||
285 | return io; | ||
286 | } | ||
287 | if (!request_region(io, 2, "radio-sf16fmi")) { | ||
288 | printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); | ||
289 | return -EBUSY; | ||
290 | } | ||
291 | |||
292 | fmi_unit.port = io; | ||
293 | fmi_unit.curvol = 0; | ||
294 | fmi_unit.curfreq = 0; | ||
295 | fmi_unit.flags = VIDEO_TUNER_LOW; | ||
296 | fmi_radio.priv = &fmi_unit; | ||
297 | |||
298 | init_MUTEX(&lock); | ||
299 | |||
300 | if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { | ||
301 | release_region(io, 2); | ||
302 | return -EINVAL; | ||
303 | } | ||
304 | |||
305 | printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); | ||
306 | /* mute card - prevents noisy bootups */ | ||
307 | fmi_mute(io); | ||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); | ||
312 | MODULE_DESCRIPTION("A driver for the SF16MI radio."); | ||
313 | MODULE_LICENSE("GPL"); | ||
314 | |||
315 | module_param(io, int, 0); | ||
316 | MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); | ||
317 | module_param(radio_nr, int, 0); | ||
318 | |||
319 | static void __exit fmi_cleanup_module(void) | ||
320 | { | ||
321 | video_unregister_device(&fmi_radio); | ||
322 | release_region(io, 2); | ||
323 | if (dev) | ||
324 | pnp_device_detach(dev); | ||
325 | } | ||
326 | |||
327 | module_init(fmi_init); | ||
328 | module_exit(fmi_cleanup_module); | ||
diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c new file mode 100644 index 000000000000..0732efda6a98 --- /dev/null +++ b/drivers/media/radio/radio-sf16fmr2.c | |||
@@ -0,0 +1,434 @@ | |||
1 | /* SF16FMR2 radio driver for Linux radio support | ||
2 | * heavily based on fmi driver... | ||
3 | * (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com | ||
4 | * | ||
5 | * Notes on the hardware | ||
6 | * | ||
7 | * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | ||
8 | * No volume control - only mute/unmute - you have to use line volume | ||
9 | * | ||
10 | * For read stereo/mono you must wait 0.1 sec after set frequency and | ||
11 | * card unmuted so I set frequency on unmute | ||
12 | * Signal handling seem to work only on autoscanning (not implemented) | ||
13 | */ | ||
14 | |||
15 | #include <linux/module.h> /* Modules */ | ||
16 | #include <linux/init.h> /* Initdata */ | ||
17 | #include <linux/ioport.h> /* check_region, request_region */ | ||
18 | #include <linux/delay.h> /* udelay */ | ||
19 | #include <asm/io.h> /* outb, outb_p */ | ||
20 | #include <asm/uaccess.h> /* copy to/from user */ | ||
21 | #include <linux/videodev.h> /* kernel radio structs */ | ||
22 | #include <asm/semaphore.h> | ||
23 | |||
24 | static struct semaphore lock; | ||
25 | |||
26 | #undef DEBUG | ||
27 | //#define DEBUG 1 | ||
28 | |||
29 | #ifdef DEBUG | ||
30 | # define debug_print(s) printk s | ||
31 | #else | ||
32 | # define debug_print(s) | ||
33 | #endif | ||
34 | |||
35 | /* this should be static vars for module size */ | ||
36 | struct fmr2_device | ||
37 | { | ||
38 | int port; | ||
39 | int curvol; /* 0-65535, if not volume 0 or 65535 */ | ||
40 | int mute; | ||
41 | int stereo; /* card is producing stereo audio */ | ||
42 | unsigned long curfreq; /* freq in kHz */ | ||
43 | int card_type; | ||
44 | __u32 flags; | ||
45 | }; | ||
46 | |||
47 | static int io = 0x384; | ||
48 | static int radio_nr = -1; | ||
49 | |||
50 | /* hw precision is 12.5 kHz | ||
51 | * It is only usefull to give freq in intervall of 200 (=0.0125Mhz), | ||
52 | * other bits will be truncated | ||
53 | */ | ||
54 | #define RSF16_ENCODE(x) ((x)/200+856) | ||
55 | #define RSF16_MINFREQ 87*16000 | ||
56 | #define RSF16_MAXFREQ 108*16000 | ||
57 | |||
58 | static inline void wait(int n,int port) | ||
59 | { | ||
60 | for (;n;--n) inb(port); | ||
61 | } | ||
62 | |||
63 | static void outbits(int bits, unsigned int data, int nWait, int port) | ||
64 | { | ||
65 | int bit; | ||
66 | for(;--bits>=0;) { | ||
67 | bit = (data>>bits) & 1; | ||
68 | outb(bit,port); | ||
69 | wait(nWait,port); | ||
70 | outb(bit|2,port); | ||
71 | wait(nWait,port); | ||
72 | outb(bit,port); | ||
73 | wait(nWait,port); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | static inline void fmr2_mute(int port) | ||
78 | { | ||
79 | outb(0x00, port); | ||
80 | wait(4,port); | ||
81 | } | ||
82 | |||
83 | static inline void fmr2_unmute(int port) | ||
84 | { | ||
85 | outb(0x04, port); | ||
86 | wait(4,port); | ||
87 | } | ||
88 | |||
89 | static inline int fmr2_stereo_mode(int port) | ||
90 | { | ||
91 | int n = inb(port); | ||
92 | outb(6,port); | ||
93 | inb(port); | ||
94 | n = ((n>>3)&1)^1; | ||
95 | debug_print((KERN_DEBUG "stereo: %d\n", n)); | ||
96 | return n; | ||
97 | } | ||
98 | |||
99 | static int fmr2_product_info(struct fmr2_device *dev) | ||
100 | { | ||
101 | int n = inb(dev->port); | ||
102 | n &= 0xC1; | ||
103 | if (n == 0) | ||
104 | { | ||
105 | /* this should support volume set */ | ||
106 | dev->card_type = 12; | ||
107 | return 0; | ||
108 | } | ||
109 | /* not volume (mine is 11) */ | ||
110 | dev->card_type = (n==128)?11:0; | ||
111 | return n; | ||
112 | } | ||
113 | |||
114 | static inline int fmr2_getsigstr(struct fmr2_device *dev) | ||
115 | { | ||
116 | /* !!! work only if scanning freq */ | ||
117 | int port = dev->port, res = 0xffff; | ||
118 | outb(5,port); | ||
119 | wait(4,port); | ||
120 | if (!(inb(port)&1)) res = 0; | ||
121 | debug_print((KERN_DEBUG "signal: %d\n", res)); | ||
122 | return res; | ||
123 | } | ||
124 | |||
125 | /* set frequency and unmute card */ | ||
126 | static int fmr2_setfreq(struct fmr2_device *dev) | ||
127 | { | ||
128 | int port = dev->port; | ||
129 | unsigned long freq = dev->curfreq; | ||
130 | |||
131 | fmr2_mute(port); | ||
132 | |||
133 | /* 0x42 for mono output | ||
134 | * 0x102 forward scanning | ||
135 | * 0x182 scansione avanti | ||
136 | */ | ||
137 | outbits(9,0x2,3,port); | ||
138 | outbits(16,RSF16_ENCODE(freq),2,port); | ||
139 | |||
140 | fmr2_unmute(port); | ||
141 | |||
142 | /* wait 0.11 sec */ | ||
143 | msleep(110); | ||
144 | |||
145 | /* NOTE if mute this stop radio | ||
146 | you must set freq on unmute */ | ||
147 | dev->stereo = fmr2_stereo_mode(port); | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | /* !!! not tested, in my card this does't work !!! */ | ||
152 | static int fmr2_setvolume(struct fmr2_device *dev) | ||
153 | { | ||
154 | int i,a,n, port = dev->port; | ||
155 | |||
156 | if (dev->card_type != 11) return 1; | ||
157 | |||
158 | switch( (dev->curvol+(1<<11)) >> 12 ) | ||
159 | { | ||
160 | case 0: case 1: n = 0x21; break; | ||
161 | case 2: n = 0x84; break; | ||
162 | case 3: n = 0x90; break; | ||
163 | case 4: n = 0x104; break; | ||
164 | case 5: n = 0x110; break; | ||
165 | case 6: n = 0x204; break; | ||
166 | case 7: n = 0x210; break; | ||
167 | case 8: n = 0x402; break; | ||
168 | case 9: n = 0x404; break; | ||
169 | default: | ||
170 | case 10: n = 0x408; break; | ||
171 | case 11: n = 0x410; break; | ||
172 | case 12: n = 0x801; break; | ||
173 | case 13: n = 0x802; break; | ||
174 | case 14: n = 0x804; break; | ||
175 | case 15: n = 0x808; break; | ||
176 | case 16: n = 0x810; break; | ||
177 | } | ||
178 | for(i=12;--i>=0;) | ||
179 | { | ||
180 | a = ((n >> i) & 1) << 6; /* if (a=0) a= 0; else a= 0x40; */ | ||
181 | outb(a|4, port); | ||
182 | wait(4,port); | ||
183 | outb(a|0x24, port); | ||
184 | wait(4,port); | ||
185 | outb(a|4, port); | ||
186 | wait(4,port); | ||
187 | } | ||
188 | for(i=6;--i>=0;) | ||
189 | { | ||
190 | a = ((0x18 >> i) & 1) << 6; | ||
191 | outb(a|4, port); | ||
192 | wait(4,port); | ||
193 | outb(a|0x24, port); | ||
194 | wait(4,port); | ||
195 | outb(a|4, port); | ||
196 | wait(4,port); | ||
197 | } | ||
198 | wait(4,port); | ||
199 | outb(0x14, port); | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int fmr2_do_ioctl(struct inode *inode, struct file *file, | ||
205 | unsigned int cmd, void *arg) | ||
206 | { | ||
207 | struct video_device *dev = video_devdata(file); | ||
208 | struct fmr2_device *fmr2 = dev->priv; | ||
209 | debug_print((KERN_DEBUG "freq %ld flags %d vol %d mute %d " | ||
210 | "stereo %d type %d\n", | ||
211 | fmr2->curfreq, fmr2->flags, fmr2->curvol, fmr2->mute, | ||
212 | fmr2->stereo, fmr2->card_type)); | ||
213 | |||
214 | switch(cmd) | ||
215 | { | ||
216 | case VIDIOCGCAP: | ||
217 | { | ||
218 | struct video_capability *v = arg; | ||
219 | memset(v,0,sizeof(*v)); | ||
220 | strcpy(v->name, "SF16-FMR2 radio"); | ||
221 | v->type=VID_TYPE_TUNER; | ||
222 | v->channels=1; | ||
223 | v->audios=1; | ||
224 | return 0; | ||
225 | } | ||
226 | case VIDIOCGTUNER: | ||
227 | { | ||
228 | struct video_tuner *v = arg; | ||
229 | int mult; | ||
230 | |||
231 | if(v->tuner) /* Only 1 tuner */ | ||
232 | return -EINVAL; | ||
233 | strcpy(v->name, "FM"); | ||
234 | mult = (fmr2->flags & VIDEO_TUNER_LOW) ? 1 : 1000; | ||
235 | v->rangelow = RSF16_MINFREQ/mult; | ||
236 | v->rangehigh = RSF16_MAXFREQ/mult; | ||
237 | v->flags = fmr2->flags | VIDEO_AUDIO_MUTABLE; | ||
238 | if (fmr2->mute) | ||
239 | v->flags |= VIDEO_AUDIO_MUTE; | ||
240 | v->mode=VIDEO_MODE_AUTO; | ||
241 | down(&lock); | ||
242 | v->signal = fmr2_getsigstr(fmr2); | ||
243 | up(&lock); | ||
244 | return 0; | ||
245 | } | ||
246 | case VIDIOCSTUNER: | ||
247 | { | ||
248 | struct video_tuner *v = arg; | ||
249 | if (v->tuner!=0) | ||
250 | return -EINVAL; | ||
251 | fmr2->flags = v->flags & VIDEO_TUNER_LOW; | ||
252 | return 0; | ||
253 | } | ||
254 | case VIDIOCGFREQ: | ||
255 | { | ||
256 | unsigned long *freq = arg; | ||
257 | *freq = fmr2->curfreq; | ||
258 | if (!(fmr2->flags & VIDEO_TUNER_LOW)) | ||
259 | *freq /= 1000; | ||
260 | return 0; | ||
261 | } | ||
262 | case VIDIOCSFREQ: | ||
263 | { | ||
264 | unsigned long *freq = arg; | ||
265 | if (!(fmr2->flags & VIDEO_TUNER_LOW)) | ||
266 | *freq *= 1000; | ||
267 | if ( *freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) | ||
268 | return -EINVAL; | ||
269 | /* rounding in steps of 200 to match th freq | ||
270 | * that will be used | ||
271 | */ | ||
272 | fmr2->curfreq = (*freq/200)*200; | ||
273 | |||
274 | /* set card freq (if not muted) */ | ||
275 | if (fmr2->curvol && !fmr2->mute) | ||
276 | { | ||
277 | down(&lock); | ||
278 | fmr2_setfreq(fmr2); | ||
279 | up(&lock); | ||
280 | } | ||
281 | return 0; | ||
282 | } | ||
283 | case VIDIOCGAUDIO: | ||
284 | { | ||
285 | struct video_audio *v = arg; | ||
286 | memset(v,0,sizeof(*v)); | ||
287 | /* !!! do not return VIDEO_AUDIO_MUTE */ | ||
288 | v->flags = VIDEO_AUDIO_MUTABLE; | ||
289 | strcpy(v->name, "Radio"); | ||
290 | /* get current stereo mode */ | ||
291 | v->mode = fmr2->stereo ? VIDEO_SOUND_STEREO: VIDEO_SOUND_MONO; | ||
292 | /* volume supported ? */ | ||
293 | if (fmr2->card_type == 11) | ||
294 | { | ||
295 | v->flags |= VIDEO_AUDIO_VOLUME; | ||
296 | v->step = 1 << 12; | ||
297 | v->volume = fmr2->curvol; | ||
298 | } | ||
299 | debug_print((KERN_DEBUG "Get flags %d vol %d\n", v->flags, v->volume)); | ||
300 | return 0; | ||
301 | } | ||
302 | case VIDIOCSAUDIO: | ||
303 | { | ||
304 | struct video_audio *v = arg; | ||
305 | if(v->audio) | ||
306 | return -EINVAL; | ||
307 | debug_print((KERN_DEBUG "Set flags %d vol %d\n", v->flags, v->volume)); | ||
308 | /* set volume */ | ||
309 | if (v->flags & VIDEO_AUDIO_VOLUME) | ||
310 | fmr2->curvol = v->volume; /* !!! set with precision */ | ||
311 | if (fmr2->card_type != 11) fmr2->curvol = 65535; | ||
312 | fmr2->mute = 0; | ||
313 | if (v->flags & VIDEO_AUDIO_MUTE) | ||
314 | fmr2->mute = 1; | ||
315 | #ifdef DEBUG | ||
316 | if (fmr2->curvol && !fmr2->mute) | ||
317 | printk(KERN_DEBUG "unmute\n"); | ||
318 | else | ||
319 | printk(KERN_DEBUG "mute\n"); | ||
320 | #endif | ||
321 | down(&lock); | ||
322 | if (fmr2->curvol && !fmr2->mute) | ||
323 | { | ||
324 | fmr2_setvolume(fmr2); | ||
325 | fmr2_setfreq(fmr2); | ||
326 | } | ||
327 | else fmr2_mute(fmr2->port); | ||
328 | up(&lock); | ||
329 | return 0; | ||
330 | } | ||
331 | case VIDIOCGUNIT: | ||
332 | { | ||
333 | struct video_unit *v = arg; | ||
334 | v->video=VIDEO_NO_UNIT; | ||
335 | v->vbi=VIDEO_NO_UNIT; | ||
336 | v->radio=dev->minor; | ||
337 | v->audio=0; /* How do we find out this??? */ | ||
338 | v->teletext=VIDEO_NO_UNIT; | ||
339 | return 0; | ||
340 | } | ||
341 | default: | ||
342 | return -ENOIOCTLCMD; | ||
343 | } | ||
344 | } | ||
345 | |||
346 | static int fmr2_ioctl(struct inode *inode, struct file *file, | ||
347 | unsigned int cmd, unsigned long arg) | ||
348 | { | ||
349 | return video_usercopy(inode, file, cmd, arg, fmr2_do_ioctl); | ||
350 | } | ||
351 | |||
352 | static struct fmr2_device fmr2_unit; | ||
353 | |||
354 | static struct file_operations fmr2_fops = { | ||
355 | .owner = THIS_MODULE, | ||
356 | .open = video_exclusive_open, | ||
357 | .release = video_exclusive_release, | ||
358 | .ioctl = fmr2_ioctl, | ||
359 | .llseek = no_llseek, | ||
360 | }; | ||
361 | |||
362 | static struct video_device fmr2_radio= | ||
363 | { | ||
364 | .owner = THIS_MODULE, | ||
365 | .name = "SF16FMR2 radio", | ||
366 | . type = VID_TYPE_TUNER, | ||
367 | .hardware = VID_HARDWARE_SF16FMR2, | ||
368 | .fops = &fmr2_fops, | ||
369 | }; | ||
370 | |||
371 | static int __init fmr2_init(void) | ||
372 | { | ||
373 | fmr2_unit.port = io; | ||
374 | fmr2_unit.curvol = 0; | ||
375 | fmr2_unit.mute = 0; | ||
376 | fmr2_unit.curfreq = 0; | ||
377 | fmr2_unit.stereo = 1; | ||
378 | fmr2_unit.flags = VIDEO_TUNER_LOW; | ||
379 | fmr2_unit.card_type = 0; | ||
380 | fmr2_radio.priv = &fmr2_unit; | ||
381 | |||
382 | init_MUTEX(&lock); | ||
383 | |||
384 | if (request_region(io, 2, "sf16fmr2")) | ||
385 | { | ||
386 | printk(KERN_ERR "fmr2: port 0x%x already in use\n", io); | ||
387 | return -EBUSY; | ||
388 | } | ||
389 | |||
390 | if(video_register_device(&fmr2_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
391 | { | ||
392 | release_region(io, 2); | ||
393 | return -EINVAL; | ||
394 | } | ||
395 | |||
396 | printk(KERN_INFO "SF16FMR2 radio card driver at 0x%x.\n", io); | ||
397 | debug_print((KERN_DEBUG "Mute %d Low %d\n",VIDEO_AUDIO_MUTE,VIDEO_TUNER_LOW)); | ||
398 | /* mute card - prevents noisy bootups */ | ||
399 | down(&lock); | ||
400 | fmr2_mute(io); | ||
401 | fmr2_product_info(&fmr2_unit); | ||
402 | up(&lock); | ||
403 | debug_print((KERN_DEBUG "card_type %d\n", fmr2_unit.card_type)); | ||
404 | return 0; | ||
405 | } | ||
406 | |||
407 | MODULE_AUTHOR("Ziglio Frediano, freddy77@angelfire.com"); | ||
408 | MODULE_DESCRIPTION("A driver for the SF16FMR2 radio."); | ||
409 | MODULE_LICENSE("GPL"); | ||
410 | |||
411 | module_param(io, int, 0); | ||
412 | MODULE_PARM_DESC(io, "I/O address of the SF16FMR2 card (should be 0x384, if do not work try 0x284)"); | ||
413 | module_param(radio_nr, int, 0); | ||
414 | |||
415 | static void __exit fmr2_cleanup_module(void) | ||
416 | { | ||
417 | video_unregister_device(&fmr2_radio); | ||
418 | release_region(io,2); | ||
419 | } | ||
420 | |||
421 | module_init(fmr2_init); | ||
422 | module_exit(fmr2_cleanup_module); | ||
423 | |||
424 | #ifndef MODULE | ||
425 | |||
426 | static int __init fmr2_setup_io(char *str) | ||
427 | { | ||
428 | get_option(&str, &io); | ||
429 | return 1; | ||
430 | } | ||
431 | |||
432 | __setup("sf16fmr2=", fmr2_setup_io); | ||
433 | |||
434 | #endif | ||
diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c new file mode 100644 index 000000000000..248d67fde037 --- /dev/null +++ b/drivers/media/radio/radio-terratec.c | |||
@@ -0,0 +1,341 @@ | |||
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) | ||
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 | */ | ||
25 | |||
26 | #include <linux/module.h> /* Modules */ | ||
27 | #include <linux/init.h> /* Initdata */ | ||
28 | #include <linux/ioport.h> /* check_region, request_region */ | ||
29 | #include <linux/delay.h> /* udelay */ | ||
30 | #include <asm/io.h> /* outb, outb_p */ | ||
31 | #include <asm/uaccess.h> /* copy to/from user */ | ||
32 | #include <linux/videodev.h> /* kernel radio structs */ | ||
33 | #include <linux/config.h> /* CONFIG_RADIO_TERRATEC_PORT */ | ||
34 | #include <linux/spinlock.h> | ||
35 | |||
36 | #ifndef CONFIG_RADIO_TERRATEC_PORT | ||
37 | #define CONFIG_RADIO_TERRATEC_PORT 0x590 | ||
38 | #endif | ||
39 | |||
40 | /**************** this ones are for the terratec *******************/ | ||
41 | #define BASEPORT 0x590 | ||
42 | #define VOLPORT 0x591 | ||
43 | #define WRT_DIS 0x00 | ||
44 | #define CLK_OFF 0x00 | ||
45 | #define IIC_DATA 0x01 | ||
46 | #define IIC_CLK 0x02 | ||
47 | #define DATA 0x04 | ||
48 | #define CLK_ON 0x08 | ||
49 | #define WRT_EN 0x10 | ||
50 | /*******************************************************************/ | ||
51 | |||
52 | static int io = CONFIG_RADIO_TERRATEC_PORT; | ||
53 | static int radio_nr = -1; | ||
54 | static spinlock_t lock; | ||
55 | |||
56 | struct tt_device | ||
57 | { | ||
58 | int port; | ||
59 | int curvol; | ||
60 | unsigned long curfreq; | ||
61 | int muted; | ||
62 | }; | ||
63 | |||
64 | |||
65 | /* local things */ | ||
66 | |||
67 | static void cardWriteVol(int volume) | ||
68 | { | ||
69 | int i; | ||
70 | volume = volume+(volume * 32); // change both channels | ||
71 | spin_lock(&lock); | ||
72 | for (i=0;i<8;i++) | ||
73 | { | ||
74 | if (volume & (0x80>>i)) | ||
75 | outb(0x80, VOLPORT); | ||
76 | else outb(0x00, VOLPORT); | ||
77 | } | ||
78 | spin_unlock(&lock); | ||
79 | } | ||
80 | |||
81 | |||
82 | |||
83 | static void tt_mute(struct tt_device *dev) | ||
84 | { | ||
85 | dev->muted = 1; | ||
86 | cardWriteVol(0); | ||
87 | } | ||
88 | |||
89 | static int tt_setvol(struct tt_device *dev, int vol) | ||
90 | { | ||
91 | |||
92 | // printk(KERN_ERR "setvol called, vol = %d\n", vol); | ||
93 | |||
94 | if(vol == dev->curvol) { /* requested volume = current */ | ||
95 | if (dev->muted) { /* user is unmuting the card */ | ||
96 | dev->muted = 0; | ||
97 | cardWriteVol(vol); /* enable card */ | ||
98 | } | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | |||
103 | if(vol == 0) { /* volume = 0 means mute the card */ | ||
104 | cardWriteVol(0); /* "turn off card" by setting vol to 0 */ | ||
105 | dev->curvol = vol; /* track the volume state! */ | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | dev->muted = 0; | ||
110 | |||
111 | cardWriteVol(vol); | ||
112 | |||
113 | dev->curvol = vol; | ||
114 | |||
115 | return 0; | ||
116 | |||
117 | } | ||
118 | |||
119 | |||
120 | /* this is the worst part in this driver */ | ||
121 | /* many more or less strange things are going on here, but hey, it works :) */ | ||
122 | |||
123 | static int tt_setfreq(struct tt_device *dev, unsigned long freq1) | ||
124 | { | ||
125 | int freq; | ||
126 | int i; | ||
127 | int p; | ||
128 | int temp; | ||
129 | long rest; | ||
130 | |||
131 | unsigned char buffer[25]; /* we have to bit shift 25 registers */ | ||
132 | freq = freq1/160; /* convert the freq. to a nice to handle value */ | ||
133 | for(i=24;i>-1;i--) | ||
134 | buffer[i]=0; | ||
135 | |||
136 | rest = freq*10+10700; /* i once had understood what is going on here */ | ||
137 | /* maybe some wise guy (friedhelm?) can comment this stuff */ | ||
138 | i=13; | ||
139 | p=10; | ||
140 | temp=102400; | ||
141 | while (rest!=0) | ||
142 | { | ||
143 | if (rest%temp == rest) | ||
144 | buffer[i] = 0; | ||
145 | else | ||
146 | { | ||
147 | buffer[i] = 1; | ||
148 | rest = rest-temp; | ||
149 | } | ||
150 | i--; | ||
151 | p--; | ||
152 | temp = temp/2; | ||
153 | } | ||
154 | |||
155 | spin_lock(&lock); | ||
156 | |||
157 | for (i=24;i>-1;i--) /* bit shift the values to the radiocard */ | ||
158 | { | ||
159 | if (buffer[i]==1) | ||
160 | { | ||
161 | outb(WRT_EN|DATA, BASEPORT); | ||
162 | outb(WRT_EN|DATA|CLK_ON , BASEPORT); | ||
163 | outb(WRT_EN|DATA, BASEPORT); | ||
164 | } | ||
165 | else | ||
166 | { | ||
167 | outb(WRT_EN|0x00, BASEPORT); | ||
168 | outb(WRT_EN|0x00|CLK_ON , BASEPORT); | ||
169 | } | ||
170 | } | ||
171 | outb(0x00, BASEPORT); | ||
172 | |||
173 | spin_unlock(&lock); | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int tt_getsigstr(struct tt_device *dev) /* TODO */ | ||
179 | { | ||
180 | if (inb(io) & 2) /* bit set = no signal present */ | ||
181 | return 0; | ||
182 | return 1; /* signal present */ | ||
183 | } | ||
184 | |||
185 | |||
186 | /* implement the video4linux api */ | ||
187 | |||
188 | static int tt_do_ioctl(struct inode *inode, struct file *file, | ||
189 | unsigned int cmd, void *arg) | ||
190 | { | ||
191 | struct video_device *dev = video_devdata(file); | ||
192 | struct tt_device *tt=dev->priv; | ||
193 | |||
194 | switch(cmd) | ||
195 | { | ||
196 | case VIDIOCGCAP: | ||
197 | { | ||
198 | struct video_capability *v = arg; | ||
199 | memset(v,0,sizeof(*v)); | ||
200 | v->type=VID_TYPE_TUNER; | ||
201 | v->channels=1; | ||
202 | v->audios=1; | ||
203 | strcpy(v->name, "ActiveRadio"); | ||
204 | return 0; | ||
205 | } | ||
206 | case VIDIOCGTUNER: | ||
207 | { | ||
208 | struct video_tuner *v = arg; | ||
209 | if(v->tuner) /* Only 1 tuner */ | ||
210 | return -EINVAL; | ||
211 | v->rangelow=(87*16000); | ||
212 | v->rangehigh=(108*16000); | ||
213 | v->flags=VIDEO_TUNER_LOW; | ||
214 | v->mode=VIDEO_MODE_AUTO; | ||
215 | strcpy(v->name, "FM"); | ||
216 | v->signal=0xFFFF*tt_getsigstr(tt); | ||
217 | return 0; | ||
218 | } | ||
219 | case VIDIOCSTUNER: | ||
220 | { | ||
221 | struct video_tuner *v = arg; | ||
222 | if(v->tuner!=0) | ||
223 | return -EINVAL; | ||
224 | /* Only 1 tuner so no setting needed ! */ | ||
225 | return 0; | ||
226 | } | ||
227 | case VIDIOCGFREQ: | ||
228 | { | ||
229 | unsigned long *freq = arg; | ||
230 | *freq = tt->curfreq; | ||
231 | return 0; | ||
232 | } | ||
233 | case VIDIOCSFREQ: | ||
234 | { | ||
235 | unsigned long *freq = arg; | ||
236 | tt->curfreq = *freq; | ||
237 | tt_setfreq(tt, tt->curfreq); | ||
238 | return 0; | ||
239 | } | ||
240 | case VIDIOCGAUDIO: | ||
241 | { | ||
242 | struct video_audio *v = arg; | ||
243 | memset(v,0, sizeof(*v)); | ||
244 | v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; | ||
245 | v->volume=tt->curvol * 6554; | ||
246 | v->step=6554; | ||
247 | strcpy(v->name, "Radio"); | ||
248 | return 0; | ||
249 | } | ||
250 | case VIDIOCSAUDIO: | ||
251 | { | ||
252 | struct video_audio *v = arg; | ||
253 | if(v->audio) | ||
254 | return -EINVAL; | ||
255 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
256 | tt_mute(tt); | ||
257 | else | ||
258 | tt_setvol(tt,v->volume/6554); | ||
259 | return 0; | ||
260 | } | ||
261 | default: | ||
262 | return -ENOIOCTLCMD; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | static int tt_ioctl(struct inode *inode, struct file *file, | ||
267 | unsigned int cmd, unsigned long arg) | ||
268 | { | ||
269 | return video_usercopy(inode, file, cmd, arg, tt_do_ioctl); | ||
270 | } | ||
271 | |||
272 | static struct tt_device terratec_unit; | ||
273 | |||
274 | static struct file_operations terratec_fops = { | ||
275 | .owner = THIS_MODULE, | ||
276 | .open = video_exclusive_open, | ||
277 | .release = video_exclusive_release, | ||
278 | .ioctl = tt_ioctl, | ||
279 | .llseek = no_llseek, | ||
280 | }; | ||
281 | |||
282 | static struct video_device terratec_radio= | ||
283 | { | ||
284 | .owner = THIS_MODULE, | ||
285 | .name = "TerraTec ActiveRadio", | ||
286 | .type = VID_TYPE_TUNER, | ||
287 | .hardware = VID_HARDWARE_TERRATEC, | ||
288 | .fops = &terratec_fops, | ||
289 | }; | ||
290 | |||
291 | static int __init terratec_init(void) | ||
292 | { | ||
293 | if(io==-1) | ||
294 | { | ||
295 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
296 | return -EINVAL; | ||
297 | } | ||
298 | if (!request_region(io, 2, "terratec")) | ||
299 | { | ||
300 | printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io); | ||
301 | return -EBUSY; | ||
302 | } | ||
303 | |||
304 | terratec_radio.priv=&terratec_unit; | ||
305 | |||
306 | spin_lock_init(&lock); | ||
307 | |||
308 | if(video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
309 | { | ||
310 | release_region(io,2); | ||
311 | return -EINVAL; | ||
312 | } | ||
313 | |||
314 | printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n"); | ||
315 | |||
316 | /* mute card - prevents noisy bootups */ | ||
317 | |||
318 | /* this ensures that the volume is all the way down */ | ||
319 | cardWriteVol(0); | ||
320 | terratec_unit.curvol = 0; | ||
321 | |||
322 | return 0; | ||
323 | } | ||
324 | |||
325 | MODULE_AUTHOR("R.OFFERMANNS & others"); | ||
326 | MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); | ||
327 | MODULE_LICENSE("GPL"); | ||
328 | module_param(io, int, 0); | ||
329 | MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); | ||
330 | module_param(radio_nr, int, 0); | ||
331 | |||
332 | static void __exit terratec_cleanup_module(void) | ||
333 | { | ||
334 | video_unregister_device(&terratec_radio); | ||
335 | release_region(io,2); | ||
336 | printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n"); | ||
337 | } | ||
338 | |||
339 | module_init(terratec_init); | ||
340 | module_exit(terratec_cleanup_module); | ||
341 | |||
diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c new file mode 100644 index 000000000000..b300bedf7c74 --- /dev/null +++ b/drivers/media/radio/radio-trust.c | |||
@@ -0,0 +1,320 @@ | |||
1 | /* radio-trust.c - Trust FM Radio card driver for Linux 2.2 | ||
2 | * by Eric Lammerts <eric@scintilla.utwente.nl> | ||
3 | * | ||
4 | * Based on radio-aztech.c. Original notes: | ||
5 | * | ||
6 | * Adapted to support the Video for Linux API by | ||
7 | * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: | ||
8 | * | ||
9 | * Quay Ly | ||
10 | * Donald Song | ||
11 | * Jason Lewis (jlewis@twilight.vtc.vsc.edu) | ||
12 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) | ||
13 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) | ||
14 | * | ||
15 | * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ | ||
16 | */ | ||
17 | |||
18 | #include <stdarg.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/ioport.h> | ||
22 | #include <asm/io.h> | ||
23 | #include <asm/uaccess.h> | ||
24 | #include <linux/videodev.h> | ||
25 | #include <linux/config.h> /* CONFIG_RADIO_TRUST_PORT */ | ||
26 | |||
27 | /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ | ||
28 | |||
29 | #ifndef CONFIG_RADIO_TRUST_PORT | ||
30 | #define CONFIG_RADIO_TRUST_PORT -1 | ||
31 | #endif | ||
32 | |||
33 | static int io = CONFIG_RADIO_TRUST_PORT; | ||
34 | static int radio_nr = -1; | ||
35 | static int ioval = 0xf; | ||
36 | static __u16 curvol; | ||
37 | static __u16 curbass; | ||
38 | static __u16 curtreble; | ||
39 | static unsigned long curfreq; | ||
40 | static int curstereo; | ||
41 | static int curmute; | ||
42 | |||
43 | /* i2c addresses */ | ||
44 | #define TDA7318_ADDR 0x88 | ||
45 | #define TSA6060T_ADDR 0xc4 | ||
46 | |||
47 | #define TR_DELAY do { inb(io); inb(io); inb(io); } while(0) | ||
48 | #define TR_SET_SCL outb(ioval |= 2, io) | ||
49 | #define TR_CLR_SCL outb(ioval &= 0xfd, io) | ||
50 | #define TR_SET_SDA outb(ioval |= 1, io) | ||
51 | #define TR_CLR_SDA outb(ioval &= 0xfe, io) | ||
52 | |||
53 | static void write_i2c(int n, ...) | ||
54 | { | ||
55 | unsigned char val, mask; | ||
56 | va_list args; | ||
57 | |||
58 | va_start(args, n); | ||
59 | |||
60 | /* start condition */ | ||
61 | TR_SET_SDA; | ||
62 | TR_SET_SCL; | ||
63 | TR_DELAY; | ||
64 | TR_CLR_SDA; | ||
65 | TR_CLR_SCL; | ||
66 | TR_DELAY; | ||
67 | |||
68 | for(; n; n--) { | ||
69 | val = va_arg(args, unsigned); | ||
70 | for(mask = 0x80; mask; mask >>= 1) { | ||
71 | if(val & mask) | ||
72 | TR_SET_SDA; | ||
73 | else | ||
74 | TR_CLR_SDA; | ||
75 | TR_SET_SCL; | ||
76 | TR_DELAY; | ||
77 | TR_CLR_SCL; | ||
78 | TR_DELAY; | ||
79 | } | ||
80 | /* acknowledge bit */ | ||
81 | TR_SET_SDA; | ||
82 | TR_SET_SCL; | ||
83 | TR_DELAY; | ||
84 | TR_CLR_SCL; | ||
85 | TR_DELAY; | ||
86 | } | ||
87 | |||
88 | /* stop condition */ | ||
89 | TR_CLR_SDA; | ||
90 | TR_DELAY; | ||
91 | TR_SET_SCL; | ||
92 | TR_DELAY; | ||
93 | TR_SET_SDA; | ||
94 | TR_DELAY; | ||
95 | |||
96 | va_end(args); | ||
97 | } | ||
98 | |||
99 | static void tr_setvol(__u16 vol) | ||
100 | { | ||
101 | curvol = vol / 2048; | ||
102 | write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f); | ||
103 | } | ||
104 | |||
105 | static int basstreble2chip[15] = { | ||
106 | 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 | ||
107 | }; | ||
108 | |||
109 | static void tr_setbass(__u16 bass) | ||
110 | { | ||
111 | curbass = bass / 4370; | ||
112 | write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]); | ||
113 | } | ||
114 | |||
115 | static void tr_settreble(__u16 treble) | ||
116 | { | ||
117 | curtreble = treble / 4370; | ||
118 | write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]); | ||
119 | } | ||
120 | |||
121 | static void tr_setstereo(int stereo) | ||
122 | { | ||
123 | curstereo = !!stereo; | ||
124 | ioval = (ioval & 0xfb) | (!curstereo << 2); | ||
125 | outb(ioval, io); | ||
126 | } | ||
127 | |||
128 | static void tr_setmute(int mute) | ||
129 | { | ||
130 | curmute = !!mute; | ||
131 | ioval = (ioval & 0xf7) | (curmute << 3); | ||
132 | outb(ioval, io); | ||
133 | } | ||
134 | |||
135 | static int tr_getsigstr(void) | ||
136 | { | ||
137 | int i, v; | ||
138 | |||
139 | for(i = 0, v = 0; i < 100; i++) v |= inb(io); | ||
140 | return (v & 1)? 0 : 0xffff; | ||
141 | } | ||
142 | |||
143 | static int tr_getstereo(void) | ||
144 | { | ||
145 | /* don't know how to determine it, just return the setting */ | ||
146 | return curstereo; | ||
147 | } | ||
148 | |||
149 | static void tr_setfreq(unsigned long f) | ||
150 | { | ||
151 | f /= 160; /* Convert to 10 kHz units */ | ||
152 | f += 1070; /* Add 10.7 MHz IF */ | ||
153 | |||
154 | write_i2c(5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0); | ||
155 | } | ||
156 | |||
157 | static int tr_do_ioctl(struct inode *inode, struct file *file, | ||
158 | unsigned int cmd, void *arg) | ||
159 | { | ||
160 | switch(cmd) | ||
161 | { | ||
162 | case VIDIOCGCAP: | ||
163 | { | ||
164 | struct video_capability *v = arg; | ||
165 | |||
166 | memset(v,0,sizeof(*v)); | ||
167 | v->type=VID_TYPE_TUNER; | ||
168 | v->channels=1; | ||
169 | v->audios=1; | ||
170 | strcpy(v->name, "Trust FM Radio"); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | case VIDIOCGTUNER: | ||
175 | { | ||
176 | struct video_tuner *v = arg; | ||
177 | |||
178 | if(v->tuner) /* Only 1 tuner */ | ||
179 | return -EINVAL; | ||
180 | |||
181 | v->rangelow = 87500 * 16; | ||
182 | v->rangehigh = 108000 * 16; | ||
183 | v->flags = VIDEO_TUNER_LOW; | ||
184 | v->mode = VIDEO_MODE_AUTO; | ||
185 | |||
186 | v->signal = tr_getsigstr(); | ||
187 | if(tr_getstereo()) | ||
188 | v->flags |= VIDEO_TUNER_STEREO_ON; | ||
189 | |||
190 | strcpy(v->name, "FM"); | ||
191 | |||
192 | return 0; | ||
193 | } | ||
194 | case VIDIOCSTUNER: | ||
195 | { | ||
196 | struct video_tuner *v = arg; | ||
197 | if(v->tuner != 0) | ||
198 | return -EINVAL; | ||
199 | return 0; | ||
200 | } | ||
201 | case VIDIOCGFREQ: | ||
202 | { | ||
203 | unsigned long *freq = arg; | ||
204 | *freq = curfreq; | ||
205 | return 0; | ||
206 | } | ||
207 | case VIDIOCSFREQ: | ||
208 | { | ||
209 | unsigned long *freq = arg; | ||
210 | tr_setfreq(*freq); | ||
211 | return 0; | ||
212 | } | ||
213 | case VIDIOCGAUDIO: | ||
214 | { | ||
215 | struct video_audio *v = arg; | ||
216 | |||
217 | memset(v,0, sizeof(*v)); | ||
218 | v->flags = VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME | | ||
219 | VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE; | ||
220 | v->mode = curstereo? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO; | ||
221 | v->volume = curvol * 2048; | ||
222 | v->step = 2048; | ||
223 | v->bass = curbass * 4370; | ||
224 | v->treble = curtreble * 4370; | ||
225 | |||
226 | strcpy(v->name, "Trust FM Radio"); | ||
227 | return 0; | ||
228 | } | ||
229 | case VIDIOCSAUDIO: | ||
230 | { | ||
231 | struct video_audio *v = arg; | ||
232 | |||
233 | if(v->audio) | ||
234 | return -EINVAL; | ||
235 | tr_setvol(v->volume); | ||
236 | tr_setbass(v->bass); | ||
237 | tr_settreble(v->treble); | ||
238 | tr_setstereo(v->mode & VIDEO_SOUND_STEREO); | ||
239 | tr_setmute(v->flags & VIDEO_AUDIO_MUTE); | ||
240 | return 0; | ||
241 | } | ||
242 | default: | ||
243 | return -ENOIOCTLCMD; | ||
244 | } | ||
245 | } | ||
246 | |||
247 | static int tr_ioctl(struct inode *inode, struct file *file, | ||
248 | unsigned int cmd, unsigned long arg) | ||
249 | { | ||
250 | return video_usercopy(inode, file, cmd, arg, tr_do_ioctl); | ||
251 | } | ||
252 | |||
253 | static struct file_operations trust_fops = { | ||
254 | .owner = THIS_MODULE, | ||
255 | .open = video_exclusive_open, | ||
256 | .release = video_exclusive_release, | ||
257 | .ioctl = tr_ioctl, | ||
258 | .llseek = no_llseek, | ||
259 | }; | ||
260 | |||
261 | static struct video_device trust_radio= | ||
262 | { | ||
263 | .owner = THIS_MODULE, | ||
264 | .name = "Trust FM Radio", | ||
265 | .type = VID_TYPE_TUNER, | ||
266 | .hardware = VID_HARDWARE_TRUST, | ||
267 | .fops = &trust_fops, | ||
268 | }; | ||
269 | |||
270 | static int __init trust_init(void) | ||
271 | { | ||
272 | if(io == -1) { | ||
273 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
274 | return -EINVAL; | ||
275 | } | ||
276 | if(!request_region(io, 2, "Trust FM Radio")) { | ||
277 | printk(KERN_ERR "trust: port 0x%x already in use\n", io); | ||
278 | return -EBUSY; | ||
279 | } | ||
280 | if(video_register_device(&trust_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
281 | { | ||
282 | release_region(io, 2); | ||
283 | return -EINVAL; | ||
284 | } | ||
285 | |||
286 | printk(KERN_INFO "Trust FM Radio card driver v1.0.\n"); | ||
287 | |||
288 | write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ | ||
289 | write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ | ||
290 | write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ | ||
291 | write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ | ||
292 | write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ | ||
293 | |||
294 | tr_setvol(0x8000); | ||
295 | tr_setbass(0x8000); | ||
296 | tr_settreble(0x8000); | ||
297 | tr_setstereo(1); | ||
298 | |||
299 | /* mute card - prevents noisy bootups */ | ||
300 | tr_setmute(1); | ||
301 | |||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); | ||
306 | MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); | ||
307 | MODULE_LICENSE("GPL"); | ||
308 | |||
309 | module_param(io, int, 0); | ||
310 | MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); | ||
311 | module_param(radio_nr, int, 0); | ||
312 | |||
313 | static void __exit cleanup_trust_module(void) | ||
314 | { | ||
315 | video_unregister_device(&trust_radio); | ||
316 | release_region(io, 2); | ||
317 | } | ||
318 | |||
319 | module_init(trust_init); | ||
320 | module_exit(cleanup_trust_module); | ||
diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c new file mode 100644 index 000000000000..d7da901ebe90 --- /dev/null +++ b/drivers/media/radio/radio-typhoon.c | |||
@@ -0,0 +1,383 @@ | |||
1 | /* Typhoon Radio Card driver for radio support | ||
2 | * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de> | ||
3 | * | ||
4 | * Card manufacturer: | ||
5 | * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e | ||
6 | * | ||
7 | * Notes on the hardware | ||
8 | * | ||
9 | * This card has two output sockets, one for speakers and one for line. | ||
10 | * The speaker output has volume control, but only in four discrete | ||
11 | * steps. The line output has neither volume control nor mute. | ||
12 | * | ||
13 | * The card has auto-stereo according to its manual, although it all | ||
14 | * sounds mono to me (even with the Win/DOS drivers). Maybe it's my | ||
15 | * antenna - I really don't know for sure. | ||
16 | * | ||
17 | * Frequency control is done digitally. | ||
18 | * | ||
19 | * Volume control is done digitally, but there are only four different | ||
20 | * possible values. So you should better always turn the volume up and | ||
21 | * use line control. I got the best results by connecting line output | ||
22 | * to the sound card microphone input. For such a configuration the | ||
23 | * volume control has no effect, since volume control only influences | ||
24 | * the speaker output. | ||
25 | * | ||
26 | * There is no explicit mute/unmute. So I set the radio frequency to a | ||
27 | * value where I do expect just noise and turn the speaker volume down. | ||
28 | * The frequency change is necessary since the card never seems to be | ||
29 | * completely silent. | ||
30 | */ | ||
31 | |||
32 | #include <linux/module.h> /* Modules */ | ||
33 | #include <linux/init.h> /* Initdata */ | ||
34 | #include <linux/ioport.h> /* check_region, request_region */ | ||
35 | #include <linux/proc_fs.h> /* radio card status report */ | ||
36 | #include <asm/io.h> /* outb, outb_p */ | ||
37 | #include <asm/uaccess.h> /* copy to/from user */ | ||
38 | #include <linux/videodev.h> /* kernel radio structs */ | ||
39 | #include <linux/config.h> /* CONFIG_RADIO_TYPHOON_* */ | ||
40 | |||
41 | #define BANNER "Typhoon Radio Card driver v0.1\n" | ||
42 | |||
43 | #ifndef CONFIG_RADIO_TYPHOON_PORT | ||
44 | #define CONFIG_RADIO_TYPHOON_PORT -1 | ||
45 | #endif | ||
46 | |||
47 | #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ | ||
48 | #define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 | ||
49 | #endif | ||
50 | |||
51 | #ifndef CONFIG_PROC_FS | ||
52 | #undef CONFIG_RADIO_TYPHOON_PROC_FS | ||
53 | #endif | ||
54 | |||
55 | struct typhoon_device { | ||
56 | int users; | ||
57 | int iobase; | ||
58 | int curvol; | ||
59 | int muted; | ||
60 | unsigned long curfreq; | ||
61 | unsigned long mutefreq; | ||
62 | struct semaphore lock; | ||
63 | }; | ||
64 | |||
65 | static void typhoon_setvol_generic(struct typhoon_device *dev, int vol); | ||
66 | static int typhoon_setfreq_generic(struct typhoon_device *dev, | ||
67 | unsigned long frequency); | ||
68 | static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency); | ||
69 | static void typhoon_mute(struct typhoon_device *dev); | ||
70 | static void typhoon_unmute(struct typhoon_device *dev); | ||
71 | static int typhoon_setvol(struct typhoon_device *dev, int vol); | ||
72 | static int typhoon_ioctl(struct inode *inode, struct file *file, | ||
73 | unsigned int cmd, unsigned long arg); | ||
74 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
75 | static int typhoon_get_info(char *buf, char **start, off_t offset, int len); | ||
76 | #endif | ||
77 | |||
78 | static void typhoon_setvol_generic(struct typhoon_device *dev, int vol) | ||
79 | { | ||
80 | down(&dev->lock); | ||
81 | vol >>= 14; /* Map 16 bit to 2 bit */ | ||
82 | vol &= 3; | ||
83 | outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */ | ||
84 | outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */ | ||
85 | up(&dev->lock); | ||
86 | } | ||
87 | |||
88 | static int typhoon_setfreq_generic(struct typhoon_device *dev, | ||
89 | unsigned long frequency) | ||
90 | { | ||
91 | unsigned long outval; | ||
92 | unsigned long x; | ||
93 | |||
94 | /* | ||
95 | * The frequency transfer curve is not linear. The best fit I could | ||
96 | * get is | ||
97 | * | ||
98 | * outval = -155 + exp((f + 15.55) * 0.057)) | ||
99 | * | ||
100 | * where frequency f is in MHz. Since we don't have exp in the kernel, | ||
101 | * I approximate this function by a third order polynomial. | ||
102 | * | ||
103 | */ | ||
104 | |||
105 | down(&dev->lock); | ||
106 | x = frequency / 160; | ||
107 | outval = (x * x + 2500) / 5000; | ||
108 | outval = (outval * x + 5000) / 10000; | ||
109 | outval -= (10 * x * x + 10433) / 20866; | ||
110 | outval += 4 * x - 11505; | ||
111 | |||
112 | outb_p((outval >> 8) & 0x01, dev->iobase + 4); | ||
113 | outb_p(outval >> 9, dev->iobase + 6); | ||
114 | outb_p(outval & 0xff, dev->iobase + 8); | ||
115 | up(&dev->lock); | ||
116 | |||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency) | ||
121 | { | ||
122 | typhoon_setfreq_generic(dev, frequency); | ||
123 | dev->curfreq = frequency; | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static void typhoon_mute(struct typhoon_device *dev) | ||
128 | { | ||
129 | if (dev->muted == 1) | ||
130 | return; | ||
131 | typhoon_setvol_generic(dev, 0); | ||
132 | typhoon_setfreq_generic(dev, dev->mutefreq); | ||
133 | dev->muted = 1; | ||
134 | } | ||
135 | |||
136 | static void typhoon_unmute(struct typhoon_device *dev) | ||
137 | { | ||
138 | if (dev->muted == 0) | ||
139 | return; | ||
140 | typhoon_setfreq_generic(dev, dev->curfreq); | ||
141 | typhoon_setvol_generic(dev, dev->curvol); | ||
142 | dev->muted = 0; | ||
143 | } | ||
144 | |||
145 | static int typhoon_setvol(struct typhoon_device *dev, int vol) | ||
146 | { | ||
147 | if (dev->muted && vol != 0) { /* user is unmuting the card */ | ||
148 | dev->curvol = vol; | ||
149 | typhoon_unmute(dev); | ||
150 | return 0; | ||
151 | } | ||
152 | if (vol == dev->curvol) /* requested volume == current */ | ||
153 | return 0; | ||
154 | |||
155 | if (vol == 0) { /* volume == 0 means mute the card */ | ||
156 | typhoon_mute(dev); | ||
157 | dev->curvol = vol; | ||
158 | return 0; | ||
159 | } | ||
160 | typhoon_setvol_generic(dev, vol); | ||
161 | dev->curvol = vol; | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | |||
166 | static int typhoon_do_ioctl(struct inode *inode, struct file *file, | ||
167 | unsigned int cmd, void *arg) | ||
168 | { | ||
169 | struct video_device *dev = video_devdata(file); | ||
170 | struct typhoon_device *typhoon = dev->priv; | ||
171 | |||
172 | switch (cmd) { | ||
173 | case VIDIOCGCAP: | ||
174 | { | ||
175 | struct video_capability *v = arg; | ||
176 | memset(v,0,sizeof(*v)); | ||
177 | v->type = VID_TYPE_TUNER; | ||
178 | v->channels = 1; | ||
179 | v->audios = 1; | ||
180 | strcpy(v->name, "Typhoon Radio"); | ||
181 | return 0; | ||
182 | } | ||
183 | case VIDIOCGTUNER: | ||
184 | { | ||
185 | struct video_tuner *v = arg; | ||
186 | if (v->tuner) /* Only 1 tuner */ | ||
187 | return -EINVAL; | ||
188 | v->rangelow = 875 * 1600; | ||
189 | v->rangehigh = 1080 * 1600; | ||
190 | v->flags = VIDEO_TUNER_LOW; | ||
191 | v->mode = VIDEO_MODE_AUTO; | ||
192 | v->signal = 0xFFFF; /* We can't get the signal strength */ | ||
193 | strcpy(v->name, "FM"); | ||
194 | return 0; | ||
195 | } | ||
196 | case VIDIOCSTUNER: | ||
197 | { | ||
198 | struct video_tuner *v = arg; | ||
199 | if (v->tuner != 0) | ||
200 | return -EINVAL; | ||
201 | /* Only 1 tuner so no setting needed ! */ | ||
202 | return 0; | ||
203 | } | ||
204 | case VIDIOCGFREQ: | ||
205 | { | ||
206 | unsigned long *freq = arg; | ||
207 | *freq = typhoon->curfreq; | ||
208 | return 0; | ||
209 | } | ||
210 | case VIDIOCSFREQ: | ||
211 | { | ||
212 | unsigned long *freq = arg; | ||
213 | typhoon->curfreq = *freq; | ||
214 | typhoon_setfreq(typhoon, typhoon->curfreq); | ||
215 | return 0; | ||
216 | } | ||
217 | case VIDIOCGAUDIO: | ||
218 | { | ||
219 | struct video_audio *v = arg; | ||
220 | memset(v, 0, sizeof(*v)); | ||
221 | v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; | ||
222 | v->mode |= VIDEO_SOUND_MONO; | ||
223 | v->volume = typhoon->curvol; | ||
224 | v->step = 1 << 14; | ||
225 | strcpy(v->name, "Typhoon Radio"); | ||
226 | return 0; | ||
227 | } | ||
228 | case VIDIOCSAUDIO: | ||
229 | { | ||
230 | struct video_audio *v = arg; | ||
231 | if (v->audio) | ||
232 | return -EINVAL; | ||
233 | if (v->flags & VIDEO_AUDIO_MUTE) | ||
234 | typhoon_mute(typhoon); | ||
235 | else | ||
236 | typhoon_unmute(typhoon); | ||
237 | if (v->flags & VIDEO_AUDIO_VOLUME) | ||
238 | typhoon_setvol(typhoon, v->volume); | ||
239 | return 0; | ||
240 | } | ||
241 | default: | ||
242 | return -ENOIOCTLCMD; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | static int typhoon_ioctl(struct inode *inode, struct file *file, | ||
247 | unsigned int cmd, unsigned long arg) | ||
248 | { | ||
249 | return video_usercopy(inode, file, cmd, arg, typhoon_do_ioctl); | ||
250 | } | ||
251 | |||
252 | static struct typhoon_device typhoon_unit = | ||
253 | { | ||
254 | .iobase = CONFIG_RADIO_TYPHOON_PORT, | ||
255 | .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, | ||
256 | .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, | ||
257 | }; | ||
258 | |||
259 | static struct file_operations typhoon_fops = { | ||
260 | .owner = THIS_MODULE, | ||
261 | .open = video_exclusive_open, | ||
262 | .release = video_exclusive_release, | ||
263 | .ioctl = typhoon_ioctl, | ||
264 | .llseek = no_llseek, | ||
265 | }; | ||
266 | |||
267 | static struct video_device typhoon_radio = | ||
268 | { | ||
269 | .owner = THIS_MODULE, | ||
270 | .name = "Typhoon Radio", | ||
271 | .type = VID_TYPE_TUNER, | ||
272 | .hardware = VID_HARDWARE_TYPHOON, | ||
273 | .fops = &typhoon_fops, | ||
274 | }; | ||
275 | |||
276 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
277 | |||
278 | static int typhoon_get_info(char *buf, char **start, off_t offset, int len) | ||
279 | { | ||
280 | char *out = buf; | ||
281 | |||
282 | #ifdef MODULE | ||
283 | #define MODULEPROCSTRING "Driver loaded as a module" | ||
284 | #else | ||
285 | #define MODULEPROCSTRING "Driver compiled into kernel" | ||
286 | #endif | ||
287 | |||
288 | /* output must be kept under PAGE_SIZE */ | ||
289 | out += sprintf(out, BANNER); | ||
290 | out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n"); | ||
291 | out += sprintf(out, "frequency = %lu kHz\n", | ||
292 | typhoon_unit.curfreq >> 4); | ||
293 | out += sprintf(out, "volume = %d\n", typhoon_unit.curvol); | ||
294 | out += sprintf(out, "mute = %s\n", typhoon_unit.muted ? | ||
295 | "on" : "off"); | ||
296 | out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase); | ||
297 | out += sprintf(out, "mute frequency = %lu kHz\n", | ||
298 | typhoon_unit.mutefreq >> 4); | ||
299 | return out - buf; | ||
300 | } | ||
301 | |||
302 | #endif /* CONFIG_RADIO_TYPHOON_PROC_FS */ | ||
303 | |||
304 | MODULE_AUTHOR("Dr. Henrik Seidel"); | ||
305 | MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); | ||
306 | MODULE_LICENSE("GPL"); | ||
307 | |||
308 | static int io = -1; | ||
309 | static int radio_nr = -1; | ||
310 | |||
311 | module_param(io, int, 0); | ||
312 | MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); | ||
313 | module_param(radio_nr, int, 0); | ||
314 | |||
315 | #ifdef MODULE | ||
316 | static unsigned long mutefreq = 0; | ||
317 | module_param(mutefreq, ulong, 0); | ||
318 | MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); | ||
319 | #endif | ||
320 | |||
321 | static int __init typhoon_init(void) | ||
322 | { | ||
323 | #ifdef MODULE | ||
324 | if (io == -1) { | ||
325 | printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n"); | ||
326 | return -EINVAL; | ||
327 | } | ||
328 | typhoon_unit.iobase = io; | ||
329 | |||
330 | if (mutefreq < 87000 || mutefreq > 108500) { | ||
331 | printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n"); | ||
332 | printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); | ||
333 | return -EINVAL; | ||
334 | } | ||
335 | typhoon_unit.mutefreq = mutefreq; | ||
336 | #endif /* MODULE */ | ||
337 | |||
338 | printk(KERN_INFO BANNER); | ||
339 | init_MUTEX(&typhoon_unit.lock); | ||
340 | io = typhoon_unit.iobase; | ||
341 | if (!request_region(io, 8, "typhoon")) { | ||
342 | printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n", | ||
343 | typhoon_unit.iobase); | ||
344 | return -EBUSY; | ||
345 | } | ||
346 | |||
347 | typhoon_radio.priv = &typhoon_unit; | ||
348 | if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1) | ||
349 | { | ||
350 | release_region(io, 8); | ||
351 | return -EINVAL; | ||
352 | } | ||
353 | printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase); | ||
354 | printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n", | ||
355 | typhoon_unit.mutefreq); | ||
356 | typhoon_unit.mutefreq <<= 4; | ||
357 | |||
358 | /* mute card - prevents noisy bootups */ | ||
359 | typhoon_mute(&typhoon_unit); | ||
360 | |||
361 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
362 | if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL, | ||
363 | typhoon_get_info)) | ||
364 | printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n"); | ||
365 | #endif | ||
366 | |||
367 | return 0; | ||
368 | } | ||
369 | |||
370 | static void __exit typhoon_cleanup_module(void) | ||
371 | { | ||
372 | |||
373 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
374 | remove_proc_entry("driver/radio-typhoon", NULL); | ||
375 | #endif | ||
376 | |||
377 | video_unregister_device(&typhoon_radio); | ||
378 | release_region(io, 8); | ||
379 | } | ||
380 | |||
381 | module_init(typhoon_init); | ||
382 | module_exit(typhoon_cleanup_module); | ||
383 | |||
diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c new file mode 100644 index 000000000000..342f92df4aba --- /dev/null +++ b/drivers/media/radio/radio-zoltrix.c | |||
@@ -0,0 +1,385 @@ | |||
1 | /* zoltrix radio plus driver for Linux radio support | ||
2 | * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> | ||
3 | * | ||
4 | * BUGS | ||
5 | * Due to the inconsistency in reading from the signal flags | ||
6 | * it is difficult to get an accurate tuned signal. | ||
7 | * | ||
8 | * It seems that the card is not linear to 0 volume. It cuts off | ||
9 | * at a low volume, and it is not possible (at least I have not found) | ||
10 | * to get fine volume control over the low volume range. | ||
11 | * | ||
12 | * Some code derived from code by Romolo Manfredini | ||
13 | * romolo@bicnet.it | ||
14 | * | ||
15 | * 1999-05-06 - (C. van Schaik) | ||
16 | * - Make signal strength and stereo scans | ||
17 | * kinder to cpu while in delay | ||
18 | * 1999-01-05 - (C. van Schaik) | ||
19 | * - Changed tuning to 1/160Mhz accuracy | ||
20 | * - Added stereo support | ||
21 | * (card defaults to stereo) | ||
22 | * (can explicitly force mono on the card) | ||
23 | * (can detect if station is in stereo) | ||
24 | * - Added unmute function | ||
25 | * - Reworked ioctl functions | ||
26 | * 2002-07-15 - Fix Stereo typo | ||
27 | */ | ||
28 | |||
29 | #include <linux/module.h> /* Modules */ | ||
30 | #include <linux/init.h> /* Initdata */ | ||
31 | #include <linux/ioport.h> /* check_region, request_region */ | ||
32 | #include <linux/delay.h> /* udelay, msleep */ | ||
33 | #include <asm/io.h> /* outb, outb_p */ | ||
34 | #include <asm/uaccess.h> /* copy to/from user */ | ||
35 | #include <linux/videodev.h> /* kernel radio structs */ | ||
36 | #include <linux/config.h> /* CONFIG_RADIO_ZOLTRIX_PORT */ | ||
37 | |||
38 | #ifndef CONFIG_RADIO_ZOLTRIX_PORT | ||
39 | #define CONFIG_RADIO_ZOLTRIX_PORT -1 | ||
40 | #endif | ||
41 | |||
42 | static int io = CONFIG_RADIO_ZOLTRIX_PORT; | ||
43 | static int radio_nr = -1; | ||
44 | |||
45 | struct zol_device { | ||
46 | int port; | ||
47 | int curvol; | ||
48 | unsigned long curfreq; | ||
49 | int muted; | ||
50 | unsigned int stereo; | ||
51 | struct semaphore lock; | ||
52 | }; | ||
53 | |||
54 | static int zol_setvol(struct zol_device *dev, int vol) | ||
55 | { | ||
56 | dev->curvol = vol; | ||
57 | if (dev->muted) | ||
58 | return 0; | ||
59 | |||
60 | down(&dev->lock); | ||
61 | if (vol == 0) { | ||
62 | outb(0, io); | ||
63 | outb(0, io); | ||
64 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | ||
65 | up(&dev->lock); | ||
66 | return 0; | ||
67 | } | ||
68 | |||
69 | outb(dev->curvol-1, io); | ||
70 | msleep(10); | ||
71 | inb(io + 2); | ||
72 | up(&dev->lock); | ||
73 | return 0; | ||
74 | } | ||
75 | |||
76 | static void zol_mute(struct zol_device *dev) | ||
77 | { | ||
78 | dev->muted = 1; | ||
79 | down(&dev->lock); | ||
80 | outb(0, io); | ||
81 | outb(0, io); | ||
82 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | ||
83 | up(&dev->lock); | ||
84 | } | ||
85 | |||
86 | static void zol_unmute(struct zol_device *dev) | ||
87 | { | ||
88 | dev->muted = 0; | ||
89 | zol_setvol(dev, dev->curvol); | ||
90 | } | ||
91 | |||
92 | static int zol_setfreq(struct zol_device *dev, unsigned long freq) | ||
93 | { | ||
94 | /* tunes the radio to the desired frequency */ | ||
95 | unsigned long long bitmask, f, m; | ||
96 | unsigned int stereo = dev->stereo; | ||
97 | int i; | ||
98 | |||
99 | if (freq == 0) | ||
100 | return 1; | ||
101 | m = (freq / 160 - 8800) * 2; | ||
102 | f = (unsigned long long) m + 0x4d1c; | ||
103 | |||
104 | bitmask = 0xc480402c10080000ull; | ||
105 | i = 45; | ||
106 | |||
107 | down(&dev->lock); | ||
108 | |||
109 | outb(0, io); | ||
110 | outb(0, io); | ||
111 | inb(io + 3); /* Zoltrix needs to be read to confirm */ | ||
112 | |||
113 | outb(0x40, io); | ||
114 | outb(0xc0, io); | ||
115 | |||
116 | bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31)); | ||
117 | while (i--) { | ||
118 | if ((bitmask & 0x8000000000000000ull) != 0) { | ||
119 | outb(0x80, io); | ||
120 | udelay(50); | ||
121 | outb(0x00, io); | ||
122 | udelay(50); | ||
123 | outb(0x80, io); | ||
124 | udelay(50); | ||
125 | } else { | ||
126 | outb(0xc0, io); | ||
127 | udelay(50); | ||
128 | outb(0x40, io); | ||
129 | udelay(50); | ||
130 | outb(0xc0, io); | ||
131 | udelay(50); | ||
132 | } | ||
133 | bitmask *= 2; | ||
134 | } | ||
135 | /* termination sequence */ | ||
136 | outb(0x80, io); | ||
137 | outb(0xc0, io); | ||
138 | outb(0x40, io); | ||
139 | udelay(1000); | ||
140 | inb(io+2); | ||
141 | |||
142 | udelay(1000); | ||
143 | |||
144 | if (dev->muted) | ||
145 | { | ||
146 | outb(0, io); | ||
147 | outb(0, io); | ||
148 | inb(io + 3); | ||
149 | udelay(1000); | ||
150 | } | ||
151 | |||
152 | up(&dev->lock); | ||
153 | |||
154 | if(!dev->muted) | ||
155 | { | ||
156 | zol_setvol(dev, dev->curvol); | ||
157 | } | ||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | /* Get signal strength */ | ||
162 | |||
163 | static int zol_getsigstr(struct zol_device *dev) | ||
164 | { | ||
165 | int a, b; | ||
166 | |||
167 | down(&dev->lock); | ||
168 | outb(0x00, io); /* This stuff I found to do nothing */ | ||
169 | outb(dev->curvol, io); | ||
170 | msleep(20); | ||
171 | |||
172 | a = inb(io); | ||
173 | msleep(10); | ||
174 | b = inb(io); | ||
175 | |||
176 | up(&dev->lock); | ||
177 | |||
178 | if (a != b) | ||
179 | return (0); | ||
180 | |||
181 | if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */ | ||
182 | || (a == 0xef)) /* with a binary scanner on the card io */ | ||
183 | return (1); | ||
184 | return (0); | ||
185 | } | ||
186 | |||
187 | static int zol_is_stereo (struct zol_device *dev) | ||
188 | { | ||
189 | int x1, x2; | ||
190 | |||
191 | down(&dev->lock); | ||
192 | |||
193 | outb(0x00, io); | ||
194 | outb(dev->curvol, io); | ||
195 | msleep(20); | ||
196 | |||
197 | x1 = inb(io); | ||
198 | msleep(10); | ||
199 | x2 = inb(io); | ||
200 | |||
201 | up(&dev->lock); | ||
202 | |||
203 | if ((x1 == x2) && (x1 == 0xcf)) | ||
204 | return 1; | ||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | static int zol_do_ioctl(struct inode *inode, struct file *file, | ||
209 | unsigned int cmd, void *arg) | ||
210 | { | ||
211 | struct video_device *dev = video_devdata(file); | ||
212 | struct zol_device *zol = dev->priv; | ||
213 | |||
214 | switch (cmd) { | ||
215 | case VIDIOCGCAP: | ||
216 | { | ||
217 | struct video_capability *v = arg; | ||
218 | |||
219 | memset(v,0,sizeof(*v)); | ||
220 | v->type = VID_TYPE_TUNER; | ||
221 | v->channels = 1 + zol->stereo; | ||
222 | v->audios = 1; | ||
223 | strcpy(v->name, "Zoltrix Radio"); | ||
224 | return 0; | ||
225 | } | ||
226 | case VIDIOCGTUNER: | ||
227 | { | ||
228 | struct video_tuner *v = arg; | ||
229 | if (v->tuner) | ||
230 | return -EINVAL; | ||
231 | strcpy(v->name, "FM"); | ||
232 | v->rangelow = (int) (88.0 * 16000); | ||
233 | v->rangehigh = (int) (108.0 * 16000); | ||
234 | v->flags = zol_is_stereo(zol) | ||
235 | ? VIDEO_TUNER_STEREO_ON : 0; | ||
236 | v->flags |= VIDEO_TUNER_LOW; | ||
237 | v->mode = VIDEO_MODE_AUTO; | ||
238 | v->signal = 0xFFFF * zol_getsigstr(zol); | ||
239 | return 0; | ||
240 | } | ||
241 | case VIDIOCSTUNER: | ||
242 | { | ||
243 | struct video_tuner *v = arg; | ||
244 | if (v->tuner != 0) | ||
245 | return -EINVAL; | ||
246 | /* Only 1 tuner so no setting needed ! */ | ||
247 | return 0; | ||
248 | } | ||
249 | case VIDIOCGFREQ: | ||
250 | { | ||
251 | unsigned long *freq = arg; | ||
252 | *freq = zol->curfreq; | ||
253 | return 0; | ||
254 | } | ||
255 | case VIDIOCSFREQ: | ||
256 | { | ||
257 | unsigned long *freq = arg; | ||
258 | zol->curfreq = *freq; | ||
259 | zol_setfreq(zol, zol->curfreq); | ||
260 | return 0; | ||
261 | } | ||
262 | case VIDIOCGAUDIO: | ||
263 | { | ||
264 | struct video_audio *v = arg; | ||
265 | memset(v, 0, sizeof(*v)); | ||
266 | v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; | ||
267 | v->mode |= zol_is_stereo(zol) | ||
268 | ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO; | ||
269 | v->volume = zol->curvol * 4096; | ||
270 | v->step = 4096; | ||
271 | strcpy(v->name, "Zoltrix Radio"); | ||
272 | return 0; | ||
273 | } | ||
274 | case VIDIOCSAUDIO: | ||
275 | { | ||
276 | struct video_audio *v = arg; | ||
277 | if (v->audio) | ||
278 | return -EINVAL; | ||
279 | |||
280 | if (v->flags & VIDEO_AUDIO_MUTE) | ||
281 | zol_mute(zol); | ||
282 | else { | ||
283 | zol_unmute(zol); | ||
284 | zol_setvol(zol, v->volume / 4096); | ||
285 | } | ||
286 | |||
287 | if (v->mode & VIDEO_SOUND_STEREO) { | ||
288 | zol->stereo = 1; | ||
289 | zol_setfreq(zol, zol->curfreq); | ||
290 | } | ||
291 | if (v->mode & VIDEO_SOUND_MONO) { | ||
292 | zol->stereo = 0; | ||
293 | zol_setfreq(zol, zol->curfreq); | ||
294 | } | ||
295 | return 0; | ||
296 | } | ||
297 | default: | ||
298 | return -ENOIOCTLCMD; | ||
299 | } | ||
300 | } | ||
301 | |||
302 | static int zol_ioctl(struct inode *inode, struct file *file, | ||
303 | unsigned int cmd, unsigned long arg) | ||
304 | { | ||
305 | return video_usercopy(inode, file, cmd, arg, zol_do_ioctl); | ||
306 | } | ||
307 | |||
308 | static struct zol_device zoltrix_unit; | ||
309 | |||
310 | static struct file_operations zoltrix_fops = | ||
311 | { | ||
312 | .owner = THIS_MODULE, | ||
313 | .open = video_exclusive_open, | ||
314 | .release = video_exclusive_release, | ||
315 | .ioctl = zol_ioctl, | ||
316 | .llseek = no_llseek, | ||
317 | }; | ||
318 | |||
319 | static struct video_device zoltrix_radio = | ||
320 | { | ||
321 | .owner = THIS_MODULE, | ||
322 | .name = "Zoltrix Radio Plus", | ||
323 | .type = VID_TYPE_TUNER, | ||
324 | .hardware = VID_HARDWARE_ZOLTRIX, | ||
325 | .fops = &zoltrix_fops, | ||
326 | }; | ||
327 | |||
328 | static int __init zoltrix_init(void) | ||
329 | { | ||
330 | if (io == -1) { | ||
331 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
332 | return -EINVAL; | ||
333 | } | ||
334 | if ((io != 0x20c) && (io != 0x30c)) { | ||
335 | printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n"); | ||
336 | return -ENXIO; | ||
337 | } | ||
338 | |||
339 | zoltrix_radio.priv = &zoltrix_unit; | ||
340 | if (!request_region(io, 2, "zoltrix")) { | ||
341 | printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io); | ||
342 | return -EBUSY; | ||
343 | } | ||
344 | |||
345 | if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1) | ||
346 | { | ||
347 | release_region(io, 2); | ||
348 | return -EINVAL; | ||
349 | } | ||
350 | printk(KERN_INFO "Zoltrix Radio Plus card driver.\n"); | ||
351 | |||
352 | init_MUTEX(&zoltrix_unit.lock); | ||
353 | |||
354 | /* mute card - prevents noisy bootups */ | ||
355 | |||
356 | /* this ensures that the volume is all the way down */ | ||
357 | |||
358 | outb(0, io); | ||
359 | outb(0, io); | ||
360 | msleep(20); | ||
361 | inb(io + 3); | ||
362 | |||
363 | zoltrix_unit.curvol = 0; | ||
364 | zoltrix_unit.stereo = 1; | ||
365 | |||
366 | return 0; | ||
367 | } | ||
368 | |||
369 | MODULE_AUTHOR("C.van Schaik"); | ||
370 | MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); | ||
371 | MODULE_LICENSE("GPL"); | ||
372 | |||
373 | module_param(io, int, 0); | ||
374 | MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); | ||
375 | module_param(radio_nr, int, 0); | ||
376 | |||
377 | static void __exit zoltrix_cleanup_module(void) | ||
378 | { | ||
379 | video_unregister_device(&zoltrix_radio); | ||
380 | release_region(io, 2); | ||
381 | } | ||
382 | |||
383 | module_init(zoltrix_init); | ||
384 | module_exit(zoltrix_cleanup_module); | ||
385 | |||