diff options
Diffstat (limited to 'drivers/media/radio/radio-aztech.c')
-rw-r--r-- | drivers/media/radio/radio-aztech.c | 372 |
1 files changed, 80 insertions, 292 deletions
diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c index eed7b0840734..177bcbd7a7c1 100644 --- a/drivers/media/radio/radio-aztech.c +++ b/drivers/media/radio/radio-aztech.c | |||
@@ -1,5 +1,7 @@ | |||
1 | /* radio-aztech.c - Aztech radio card driver for Linux 2.2 | 1 | /* |
2 | * radio-aztech.c - Aztech radio card driver | ||
2 | * | 3 | * |
4 | * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl> | ||
3 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> | 5 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> |
4 | * Adapted to support the Video for Linux API by | 6 | * Adapted to support the Video for Linux API by |
5 | * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: | 7 | * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: |
@@ -10,19 +12,7 @@ | |||
10 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) | 12 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) |
11 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) | 13 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) |
12 | * | 14 | * |
13 | * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ | 15 | * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. |
14 | * along with more information on the card itself. | ||
15 | * | ||
16 | * History: | ||
17 | * 1999-02-24 Russell Kroll <rkroll@exploits.org> | ||
18 | * Fine tuning/VIDEO_TUNER_LOW | ||
19 | * Range expanded to 87-108 MHz (from 87.9-107.8) | ||
20 | * | ||
21 | * Notable changes from the original source: | ||
22 | * - includes stripped down to the essentials | ||
23 | * - for loops used as delays replaced with udelay() | ||
24 | * - #defines removed, changed to static values | ||
25 | * - tuning structure changed - no more character arrays, other changes | ||
26 | */ | 16 | */ |
27 | 17 | ||
28 | #include <linux/module.h> /* Modules */ | 18 | #include <linux/module.h> /* Modules */ |
@@ -31,126 +21,72 @@ | |||
31 | #include <linux/delay.h> /* udelay */ | 21 | #include <linux/delay.h> /* udelay */ |
32 | #include <linux/videodev2.h> /* kernel radio structs */ | 22 | #include <linux/videodev2.h> /* kernel radio structs */ |
33 | #include <linux/io.h> /* outb, outb_p */ | 23 | #include <linux/io.h> /* outb, outb_p */ |
24 | #include <linux/slab.h> | ||
34 | #include <media/v4l2-device.h> | 25 | #include <media/v4l2-device.h> |
35 | #include <media/v4l2-ioctl.h> | 26 | #include <media/v4l2-ioctl.h> |
27 | #include <media/v4l2-ctrls.h> | ||
28 | #include "radio-isa.h" | ||
36 | 29 | ||
37 | MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); | 30 | MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); |
38 | MODULE_DESCRIPTION("A driver for the Aztech radio card."); | 31 | MODULE_DESCRIPTION("A driver for the Aztech radio card."); |
39 | MODULE_LICENSE("GPL"); | 32 | MODULE_LICENSE("GPL"); |
40 | MODULE_VERSION("0.0.3"); | 33 | MODULE_VERSION("1.0.0"); |
41 | 34 | ||
42 | /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ | 35 | /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ |
43 | |||
44 | #ifndef CONFIG_RADIO_AZTECH_PORT | 36 | #ifndef CONFIG_RADIO_AZTECH_PORT |
45 | #define CONFIG_RADIO_AZTECH_PORT -1 | 37 | #define CONFIG_RADIO_AZTECH_PORT -1 |
46 | #endif | 38 | #endif |
47 | 39 | ||
48 | static int io = CONFIG_RADIO_AZTECH_PORT; | 40 | #define AZTECH_MAX 2 |
49 | static int radio_nr = -1; | ||
50 | static int radio_wait_time = 1000; | ||
51 | 41 | ||
52 | module_param(io, int, 0); | 42 | static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT, |
53 | module_param(radio_nr, int, 0); | 43 | [1 ... (AZTECH_MAX - 1)] = -1 }; |
54 | MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); | 44 | static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 }; |
45 | static const int radio_wait_time = 1000; | ||
55 | 46 | ||
56 | struct aztech | 47 | module_param_array(io, int, NULL, 0444); |
57 | { | 48 | MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)"); |
58 | struct v4l2_device v4l2_dev; | 49 | module_param_array(radio_nr, int, NULL, 0444); |
59 | struct video_device vdev; | 50 | MODULE_PARM_DESC(radio_nr, "Radio device numbers"); |
60 | int io; | 51 | |
52 | struct aztech { | ||
53 | struct radio_isa_card isa; | ||
61 | int curvol; | 54 | int curvol; |
62 | unsigned long curfreq; | ||
63 | int stereo; | ||
64 | struct mutex lock; | ||
65 | }; | 55 | }; |
66 | 56 | ||
67 | static struct aztech aztech_card; | ||
68 | |||
69 | static int volconvert(int level) | ||
70 | { | ||
71 | level >>= 14; /* Map 16bits down to 2 bit */ | ||
72 | level &= 3; | ||
73 | |||
74 | /* convert to card-friendly values */ | ||
75 | switch (level) { | ||
76 | case 0: | ||
77 | return 0; | ||
78 | case 1: | ||
79 | return 1; | ||
80 | case 2: | ||
81 | return 4; | ||
82 | case 3: | ||
83 | return 5; | ||
84 | } | ||
85 | return 0; /* Quieten gcc */ | ||
86 | } | ||
87 | |||
88 | static void send_0_byte(struct aztech *az) | 57 | static void send_0_byte(struct aztech *az) |
89 | { | 58 | { |
90 | udelay(radio_wait_time); | 59 | udelay(radio_wait_time); |
91 | outb_p(2 + volconvert(az->curvol), az->io); | 60 | outb_p(2 + az->curvol, az->isa.io); |
92 | outb_p(64 + 2 + volconvert(az->curvol), az->io); | 61 | outb_p(64 + 2 + az->curvol, az->isa.io); |
93 | } | 62 | } |
94 | 63 | ||
95 | static void send_1_byte(struct aztech *az) | 64 | static void send_1_byte(struct aztech *az) |
96 | { | 65 | { |
97 | udelay (radio_wait_time); | 66 | udelay(radio_wait_time); |
98 | outb_p(128 + 2 + volconvert(az->curvol), az->io); | 67 | outb_p(128 + 2 + az->curvol, az->isa.io); |
99 | outb_p(128 + 64 + 2 + volconvert(az->curvol), az->io); | 68 | outb_p(128 + 64 + 2 + az->curvol, az->isa.io); |
100 | } | ||
101 | |||
102 | static int az_setvol(struct aztech *az, int vol) | ||
103 | { | ||
104 | mutex_lock(&az->lock); | ||
105 | outb(volconvert(vol), az->io); | ||
106 | mutex_unlock(&az->lock); | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* thanks to Michael Dwyer for giving me a dose of clues in | ||
111 | * the signal strength department.. | ||
112 | * | ||
113 | * This card has a stereo bit - bit 0 set = mono, not set = stereo | ||
114 | * It also has a "signal" bit - bit 1 set = bad signal, not set = good | ||
115 | * | ||
116 | */ | ||
117 | |||
118 | static int az_getsigstr(struct aztech *az) | ||
119 | { | ||
120 | int sig = 1; | ||
121 | |||
122 | mutex_lock(&az->lock); | ||
123 | if (inb(az->io) & 2) /* bit set = no signal present */ | ||
124 | sig = 0; | ||
125 | mutex_unlock(&az->lock); | ||
126 | return sig; | ||
127 | } | 69 | } |
128 | 70 | ||
129 | static int az_getstereo(struct aztech *az) | 71 | static struct radio_isa_card *aztech_alloc(void) |
130 | { | 72 | { |
131 | int stereo = 1; | 73 | struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL); |
132 | 74 | ||
133 | mutex_lock(&az->lock); | 75 | return az ? &az->isa : NULL; |
134 | if (inb(az->io) & 1) /* bit set = mono */ | ||
135 | stereo = 0; | ||
136 | mutex_unlock(&az->lock); | ||
137 | return stereo; | ||
138 | } | 76 | } |
139 | 77 | ||
140 | static int az_setfreq(struct aztech *az, unsigned long frequency) | 78 | static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq) |
141 | { | 79 | { |
80 | struct aztech *az = container_of(isa, struct aztech, isa); | ||
142 | int i; | 81 | int i; |
143 | 82 | ||
144 | mutex_lock(&az->lock); | 83 | freq += 171200; /* Add 10.7 MHz IF */ |
145 | 84 | freq /= 800; /* Convert to 50 kHz units */ | |
146 | az->curfreq = frequency; | ||
147 | frequency += 171200; /* Add 10.7 MHz IF */ | ||
148 | frequency /= 800; /* Convert to 50 kHz units */ | ||
149 | 85 | ||
150 | send_0_byte(az); /* 0: LSB of frequency */ | 86 | send_0_byte(az); /* 0: LSB of frequency */ |
151 | 87 | ||
152 | for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ | 88 | for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ |
153 | if (frequency & (1 << i)) | 89 | if (freq & (1 << i)) |
154 | send_1_byte(az); | 90 | send_1_byte(az); |
155 | else | 91 | else |
156 | send_0_byte(az); | 92 | send_0_byte(az); |
@@ -158,7 +94,7 @@ static int az_setfreq(struct aztech *az, unsigned long frequency) | |||
158 | send_0_byte(az); /* 14: test bit - always 0 */ | 94 | send_0_byte(az); /* 14: test bit - always 0 */ |
159 | send_0_byte(az); /* 15: test bit - always 0 */ | 95 | send_0_byte(az); /* 15: test bit - always 0 */ |
160 | send_0_byte(az); /* 16: band data 0 - always 0 */ | 96 | send_0_byte(az); /* 16: band data 0 - always 0 */ |
161 | if (az->stereo) /* 17: stereo (1 to enable) */ | 97 | if (isa->stereo) /* 17: stereo (1 to enable) */ |
162 | send_1_byte(az); | 98 | send_1_byte(az); |
163 | else | 99 | else |
164 | send_0_byte(az); | 100 | send_0_byte(az); |
@@ -173,225 +109,77 @@ static int az_setfreq(struct aztech *az, unsigned long frequency) | |||
173 | /* latch frequency */ | 109 | /* latch frequency */ |
174 | 110 | ||
175 | udelay(radio_wait_time); | 111 | udelay(radio_wait_time); |
176 | outb_p(128 + 64 + volconvert(az->curvol), az->io); | 112 | outb_p(128 + 64 + az->curvol, az->isa.io); |
177 | |||
178 | mutex_unlock(&az->lock); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static int vidioc_querycap(struct file *file, void *priv, | ||
184 | struct v4l2_capability *v) | ||
185 | { | ||
186 | strlcpy(v->driver, "radio-aztech", sizeof(v->driver)); | ||
187 | strlcpy(v->card, "Aztech Radio", sizeof(v->card)); | ||
188 | strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); | ||
189 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; | ||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static int vidioc_g_tuner(struct file *file, void *priv, | ||
194 | struct v4l2_tuner *v) | ||
195 | { | ||
196 | struct aztech *az = video_drvdata(file); | ||
197 | |||
198 | if (v->index > 0) | ||
199 | return -EINVAL; | ||
200 | |||
201 | strlcpy(v->name, "FM", sizeof(v->name)); | ||
202 | v->type = V4L2_TUNER_RADIO; | ||
203 | |||
204 | v->rangelow = 87 * 16000; | ||
205 | v->rangehigh = 108 * 16000; | ||
206 | v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; | ||
207 | v->capability = V4L2_TUNER_CAP_LOW; | ||
208 | if (az_getstereo(az)) | ||
209 | v->audmode = V4L2_TUNER_MODE_STEREO; | ||
210 | else | ||
211 | v->audmode = V4L2_TUNER_MODE_MONO; | ||
212 | v->signal = 0xFFFF * az_getsigstr(az); | ||
213 | |||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | static int vidioc_s_tuner(struct file *file, void *priv, | ||
218 | struct v4l2_tuner *v) | ||
219 | { | ||
220 | return v->index ? -EINVAL : 0; | ||
221 | } | ||
222 | 113 | ||
223 | static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) | ||
224 | { | ||
225 | *i = 0; | ||
226 | return 0; | 114 | return 0; |
227 | } | 115 | } |
228 | 116 | ||
229 | static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) | 117 | /* thanks to Michael Dwyer for giving me a dose of clues in |
230 | { | 118 | * the signal strength department.. |
231 | return i ? -EINVAL : 0; | 119 | * |
232 | } | 120 | * This card has a stereo bit - bit 0 set = mono, not set = stereo |
233 | 121 | */ | |
234 | static int vidioc_g_audio(struct file *file, void *priv, | 122 | static u32 aztech_g_rxsubchans(struct radio_isa_card *isa) |
235 | struct v4l2_audio *a) | ||
236 | { | ||
237 | a->index = 0; | ||
238 | strlcpy(a->name, "Radio", sizeof(a->name)); | ||
239 | a->capability = V4L2_AUDCAP_STEREO; | ||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static int vidioc_s_audio(struct file *file, void *priv, | ||
244 | struct v4l2_audio *a) | ||
245 | { | 123 | { |
246 | return a->index ? -EINVAL : 0; | 124 | if (inb(isa->io) & 1) |
125 | return V4L2_TUNER_SUB_MONO; | ||
126 | return V4L2_TUNER_SUB_STEREO; | ||
247 | } | 127 | } |
248 | 128 | ||
249 | static int vidioc_s_frequency(struct file *file, void *priv, | 129 | static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo) |
250 | struct v4l2_frequency *f) | ||
251 | { | 130 | { |
252 | struct aztech *az = video_drvdata(file); | 131 | return aztech_s_frequency(isa, isa->freq); |
253 | |||
254 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) | ||
255 | return -EINVAL; | ||
256 | az_setfreq(az, f->frequency); | ||
257 | return 0; | ||
258 | } | 132 | } |
259 | 133 | ||
260 | static int vidioc_g_frequency(struct file *file, void *priv, | 134 | static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) |
261 | struct v4l2_frequency *f) | ||
262 | { | 135 | { |
263 | struct aztech *az = video_drvdata(file); | 136 | struct aztech *az = container_of(isa, struct aztech, isa); |
264 | 137 | ||
265 | if (f->tuner != 0) | 138 | if (mute) |
266 | return -EINVAL; | 139 | vol = 0; |
267 | f->type = V4L2_TUNER_RADIO; | 140 | az->curvol = (vol & 1) + ((vol & 2) << 1); |
268 | f->frequency = az->curfreq; | 141 | outb(az->curvol, isa->io); |
269 | return 0; | 142 | return 0; |
270 | } | 143 | } |
271 | 144 | ||
272 | static int vidioc_queryctrl(struct file *file, void *priv, | 145 | static const struct radio_isa_ops aztech_ops = { |
273 | struct v4l2_queryctrl *qc) | 146 | .alloc = aztech_alloc, |
274 | { | 147 | .s_mute_volume = aztech_s_mute_volume, |
275 | switch (qc->id) { | 148 | .s_frequency = aztech_s_frequency, |
276 | case V4L2_CID_AUDIO_MUTE: | 149 | .s_stereo = aztech_s_stereo, |
277 | return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); | 150 | .g_rxsubchans = aztech_g_rxsubchans, |
278 | case V4L2_CID_AUDIO_VOLUME: | ||
279 | return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff); | ||
280 | } | ||
281 | return -EINVAL; | ||
282 | } | ||
283 | |||
284 | static int vidioc_g_ctrl(struct file *file, void *priv, | ||
285 | struct v4l2_control *ctrl) | ||
286 | { | ||
287 | struct aztech *az = video_drvdata(file); | ||
288 | |||
289 | switch (ctrl->id) { | ||
290 | case V4L2_CID_AUDIO_MUTE: | ||
291 | if (az->curvol == 0) | ||
292 | ctrl->value = 1; | ||
293 | else | ||
294 | ctrl->value = 0; | ||
295 | return 0; | ||
296 | case V4L2_CID_AUDIO_VOLUME: | ||
297 | ctrl->value = az->curvol * 6554; | ||
298 | return 0; | ||
299 | } | ||
300 | return -EINVAL; | ||
301 | } | ||
302 | |||
303 | static int vidioc_s_ctrl(struct file *file, void *priv, | ||
304 | struct v4l2_control *ctrl) | ||
305 | { | ||
306 | struct aztech *az = video_drvdata(file); | ||
307 | |||
308 | switch (ctrl->id) { | ||
309 | case V4L2_CID_AUDIO_MUTE: | ||
310 | if (ctrl->value) | ||
311 | az_setvol(az, 0); | ||
312 | else | ||
313 | az_setvol(az, az->curvol); | ||
314 | return 0; | ||
315 | case V4L2_CID_AUDIO_VOLUME: | ||
316 | az_setvol(az, ctrl->value); | ||
317 | return 0; | ||
318 | } | ||
319 | return -EINVAL; | ||
320 | } | ||
321 | |||
322 | static const struct v4l2_file_operations aztech_fops = { | ||
323 | .owner = THIS_MODULE, | ||
324 | .unlocked_ioctl = video_ioctl2, | ||
325 | }; | 151 | }; |
326 | 152 | ||
327 | static const struct v4l2_ioctl_ops aztech_ioctl_ops = { | 153 | static const int aztech_ioports[] = { 0x350, 0x358 }; |
328 | .vidioc_querycap = vidioc_querycap, | 154 | |
329 | .vidioc_g_tuner = vidioc_g_tuner, | 155 | static struct radio_isa_driver aztech_driver = { |
330 | .vidioc_s_tuner = vidioc_s_tuner, | 156 | .driver = { |
331 | .vidioc_g_audio = vidioc_g_audio, | 157 | .match = radio_isa_match, |
332 | .vidioc_s_audio = vidioc_s_audio, | 158 | .probe = radio_isa_probe, |
333 | .vidioc_g_input = vidioc_g_input, | 159 | .remove = radio_isa_remove, |
334 | .vidioc_s_input = vidioc_s_input, | 160 | .driver = { |
335 | .vidioc_g_frequency = vidioc_g_frequency, | 161 | .name = "radio-aztech", |
336 | .vidioc_s_frequency = vidioc_s_frequency, | 162 | }, |
337 | .vidioc_queryctrl = vidioc_queryctrl, | 163 | }, |
338 | .vidioc_g_ctrl = vidioc_g_ctrl, | 164 | .io_params = io, |
339 | .vidioc_s_ctrl = vidioc_s_ctrl, | 165 | .radio_nr_params = radio_nr, |
166 | .io_ports = aztech_ioports, | ||
167 | .num_of_io_ports = ARRAY_SIZE(aztech_ioports), | ||
168 | .region_size = 2, | ||
169 | .card = "Aztech Radio", | ||
170 | .ops = &aztech_ops, | ||
171 | .has_stereo = true, | ||
172 | .max_volume = 3, | ||
340 | }; | 173 | }; |
341 | 174 | ||
342 | static int __init aztech_init(void) | 175 | static int __init aztech_init(void) |
343 | { | 176 | { |
344 | struct aztech *az = &aztech_card; | 177 | return isa_register_driver(&aztech_driver.driver, AZTECH_MAX); |
345 | struct v4l2_device *v4l2_dev = &az->v4l2_dev; | ||
346 | int res; | ||
347 | |||
348 | strlcpy(v4l2_dev->name, "aztech", sizeof(v4l2_dev->name)); | ||
349 | az->io = io; | ||
350 | |||
351 | if (az->io == -1) { | ||
352 | v4l2_err(v4l2_dev, "you must set an I/O address with io=0x350 or 0x358\n"); | ||
353 | return -EINVAL; | ||
354 | } | ||
355 | |||
356 | if (!request_region(az->io, 2, "aztech")) { | ||
357 | v4l2_err(v4l2_dev, "port 0x%x already in use\n", az->io); | ||
358 | return -EBUSY; | ||
359 | } | ||
360 | |||
361 | res = v4l2_device_register(NULL, v4l2_dev); | ||
362 | if (res < 0) { | ||
363 | release_region(az->io, 2); | ||
364 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | ||
365 | return res; | ||
366 | } | ||
367 | |||
368 | mutex_init(&az->lock); | ||
369 | strlcpy(az->vdev.name, v4l2_dev->name, sizeof(az->vdev.name)); | ||
370 | az->vdev.v4l2_dev = v4l2_dev; | ||
371 | az->vdev.fops = &aztech_fops; | ||
372 | az->vdev.ioctl_ops = &aztech_ioctl_ops; | ||
373 | az->vdev.release = video_device_release_empty; | ||
374 | video_set_drvdata(&az->vdev, az); | ||
375 | /* mute card - prevents noisy bootups */ | ||
376 | outb(0, az->io); | ||
377 | |||
378 | if (video_register_device(&az->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { | ||
379 | v4l2_device_unregister(v4l2_dev); | ||
380 | release_region(az->io, 2); | ||
381 | return -EINVAL; | ||
382 | } | ||
383 | |||
384 | v4l2_info(v4l2_dev, "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); | ||
385 | return 0; | ||
386 | } | 178 | } |
387 | 179 | ||
388 | static void __exit aztech_exit(void) | 180 | static void __exit aztech_exit(void) |
389 | { | 181 | { |
390 | struct aztech *az = &aztech_card; | 182 | isa_unregister_driver(&aztech_driver.driver); |
391 | |||
392 | video_unregister_device(&az->vdev); | ||
393 | v4l2_device_unregister(&az->v4l2_dev); | ||
394 | release_region(az->io, 2); | ||
395 | } | 183 | } |
396 | 184 | ||
397 | module_init(aztech_init); | 185 | module_init(aztech_init); |