diff options
Diffstat (limited to 'drivers/media/radio/radio-maxiradio.c')
-rw-r--r-- | drivers/media/radio/radio-maxiradio.c | 349 |
1 files changed, 349 insertions, 0 deletions
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); | ||