Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff |
4286c6f6 | 2 | * |
1da177e4 | 3 | * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood |
d9b01449 | 4 | * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> |
1da177e4 LT |
5 | * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> |
6 | * | |
7 | * TODO: Allow for more than one of these foolish entities :-) | |
8 | * | |
f8c559f8 | 9 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> |
1da177e4 LT |
10 | */ |
11 | ||
12 | #include <linux/module.h> /* Modules */ | |
13 | #include <linux/init.h> /* Initdata */ | |
fb911ee8 | 14 | #include <linux/ioport.h> /* request_region */ |
1da177e4 | 15 | #include <linux/delay.h> /* udelay */ |
f8c559f8 | 16 | #include <linux/videodev2.h> /* kernel radio structs */ |
922c78e9 HV |
17 | #include <linux/mutex.h> |
18 | #include <linux/version.h> /* for KERNEL_VERSION MACRO */ | |
19 | #include <linux/io.h> /* outb, outb_p */ | |
922c78e9 | 20 | #include <media/v4l2-device.h> |
35ea11ff | 21 | #include <media/v4l2-ioctl.h> |
1da177e4 | 22 | |
922c78e9 HV |
23 | MODULE_AUTHOR("Ben Pfaff"); |
24 | MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); | |
25 | MODULE_LICENSE("GPL"); | |
f8c559f8 | 26 | |
1da177e4 LT |
27 | #ifndef CONFIG_RADIO_RTRACK2_PORT |
28 | #define CONFIG_RADIO_RTRACK2_PORT -1 | |
29 | #endif | |
30 | ||
4286c6f6 | 31 | static int io = CONFIG_RADIO_RTRACK2_PORT; |
1da177e4 | 32 | static int radio_nr = -1; |
1da177e4 | 33 | |
922c78e9 HV |
34 | module_param(io, int, 0); |
35 | MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); | |
36 | module_param(radio_nr, int, 0); | |
37 | ||
38 | #define RADIO_VERSION KERNEL_VERSION(0, 0, 2) | |
39 | ||
40 | struct rtrack2 | |
1da177e4 | 41 | { |
922c78e9 HV |
42 | struct v4l2_device v4l2_dev; |
43 | struct video_device vdev; | |
44 | int io; | |
1da177e4 LT |
45 | unsigned long curfreq; |
46 | int muted; | |
922c78e9 | 47 | struct mutex lock; |
1da177e4 LT |
48 | }; |
49 | ||
922c78e9 HV |
50 | static struct rtrack2 rtrack2_card; |
51 | ||
1da177e4 LT |
52 | |
53 | /* local things */ | |
54 | ||
922c78e9 | 55 | static void rt_mute(struct rtrack2 *dev) |
1da177e4 | 56 | { |
922c78e9 | 57 | if (dev->muted) |
1da177e4 | 58 | return; |
922c78e9 HV |
59 | mutex_lock(&dev->lock); |
60 | outb(1, dev->io); | |
61 | mutex_unlock(&dev->lock); | |
1da177e4 LT |
62 | dev->muted = 1; |
63 | } | |
64 | ||
922c78e9 | 65 | static void rt_unmute(struct rtrack2 *dev) |
1da177e4 LT |
66 | { |
67 | if(dev->muted == 0) | |
68 | return; | |
922c78e9 HV |
69 | mutex_lock(&dev->lock); |
70 | outb(0, dev->io); | |
71 | mutex_unlock(&dev->lock); | |
1da177e4 LT |
72 | dev->muted = 0; |
73 | } | |
74 | ||
922c78e9 | 75 | static void zero(struct rtrack2 *dev) |
1da177e4 | 76 | { |
922c78e9 HV |
77 | outb_p(1, dev->io); |
78 | outb_p(3, dev->io); | |
79 | outb_p(1, dev->io); | |
1da177e4 LT |
80 | } |
81 | ||
922c78e9 | 82 | static void one(struct rtrack2 *dev) |
1da177e4 | 83 | { |
922c78e9 HV |
84 | outb_p(5, dev->io); |
85 | outb_p(7, dev->io); | |
86 | outb_p(5, dev->io); | |
1da177e4 LT |
87 | } |
88 | ||
922c78e9 | 89 | static int rt_setfreq(struct rtrack2 *dev, unsigned long freq) |
1da177e4 LT |
90 | { |
91 | int i; | |
92 | ||
922c78e9 HV |
93 | mutex_lock(&dev->lock); |
94 | dev->curfreq = freq; | |
1da177e4 | 95 | freq = freq / 200 + 856; |
4286c6f6 | 96 | |
922c78e9 HV |
97 | outb_p(0xc8, dev->io); |
98 | outb_p(0xc9, dev->io); | |
99 | outb_p(0xc9, dev->io); | |
1da177e4 LT |
100 | |
101 | for (i = 0; i < 10; i++) | |
922c78e9 | 102 | zero(dev); |
1da177e4 LT |
103 | |
104 | for (i = 14; i >= 0; i--) | |
105 | if (freq & (1 << i)) | |
922c78e9 | 106 | one(dev); |
1da177e4 | 107 | else |
922c78e9 | 108 | zero(dev); |
1da177e4 | 109 | |
922c78e9 | 110 | outb_p(0xc8, dev->io); |
1da177e4 | 111 | if (!dev->muted) |
922c78e9 | 112 | outb_p(0, dev->io); |
4286c6f6 | 113 | |
922c78e9 | 114 | mutex_unlock(&dev->lock); |
1da177e4 LT |
115 | return 0; |
116 | } | |
117 | ||
25f30389 DL |
118 | static int vidioc_querycap(struct file *file, void *priv, |
119 | struct v4l2_capability *v) | |
120 | { | |
121 | strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver)); | |
122 | strlcpy(v->card, "RadioTrack II", sizeof(v->card)); | |
922c78e9 | 123 | strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); |
25f30389 | 124 | v->version = RADIO_VERSION; |
922c78e9 | 125 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; |
25f30389 DL |
126 | return 0; |
127 | } | |
128 | ||
129 | static int vidioc_s_tuner(struct file *file, void *priv, | |
130 | struct v4l2_tuner *v) | |
131 | { | |
922c78e9 | 132 | return v->index ? -EINVAL : 0; |
25f30389 DL |
133 | } |
134 | ||
922c78e9 | 135 | static int rt_getsigstr(struct rtrack2 *dev) |
1da177e4 | 136 | { |
922c78e9 HV |
137 | int sig = 1; |
138 | ||
139 | mutex_lock(&dev->lock); | |
140 | if (inb(dev->io) & 2) /* bit set = no signal present */ | |
141 | sig = 0; | |
142 | mutex_unlock(&dev->lock); | |
143 | return sig; | |
1da177e4 LT |
144 | } |
145 | ||
25f30389 DL |
146 | static int vidioc_g_tuner(struct file *file, void *priv, |
147 | struct v4l2_tuner *v) | |
1da177e4 | 148 | { |
922c78e9 | 149 | struct rtrack2 *rt = video_drvdata(file); |
25f30389 DL |
150 | |
151 | if (v->index > 0) | |
152 | return -EINVAL; | |
153 | ||
922c78e9 | 154 | strlcpy(v->name, "FM", sizeof(v->name)); |
25f30389 | 155 | v->type = V4L2_TUNER_RADIO; |
922c78e9 HV |
156 | v->rangelow = 88 * 16000; |
157 | v->rangehigh = 108 * 16000; | |
25f30389 DL |
158 | v->rxsubchans = V4L2_TUNER_SUB_MONO; |
159 | v->capability = V4L2_TUNER_CAP_LOW; | |
160 | v->audmode = V4L2_TUNER_MODE_MONO; | |
922c78e9 | 161 | v->signal = 0xFFFF * rt_getsigstr(rt); |
25f30389 DL |
162 | return 0; |
163 | } | |
f8c559f8 | 164 | |
25f30389 DL |
165 | static int vidioc_s_frequency(struct file *file, void *priv, |
166 | struct v4l2_frequency *f) | |
167 | { | |
922c78e9 | 168 | struct rtrack2 *rt = video_drvdata(file); |
f8c559f8 | 169 | |
922c78e9 | 170 | rt_setfreq(rt, f->frequency); |
25f30389 DL |
171 | return 0; |
172 | } | |
f8c559f8 | 173 | |
25f30389 DL |
174 | static int vidioc_g_frequency(struct file *file, void *priv, |
175 | struct v4l2_frequency *f) | |
176 | { | |
922c78e9 | 177 | struct rtrack2 *rt = video_drvdata(file); |
f8c559f8 | 178 | |
25f30389 DL |
179 | f->type = V4L2_TUNER_RADIO; |
180 | f->frequency = rt->curfreq; | |
181 | return 0; | |
182 | } | |
f8c559f8 | 183 | |
25f30389 DL |
184 | static int vidioc_queryctrl(struct file *file, void *priv, |
185 | struct v4l2_queryctrl *qc) | |
186 | { | |
922c78e9 HV |
187 | switch (qc->id) { |
188 | case V4L2_CID_AUDIO_MUTE: | |
189 | return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); | |
190 | case V4L2_CID_AUDIO_VOLUME: | |
191 | return v4l2_ctrl_query_fill(qc, 0, 65535, 65535, 65535); | |
25f30389 DL |
192 | } |
193 | return -EINVAL; | |
194 | } | |
f8c559f8 | 195 | |
25f30389 DL |
196 | static int vidioc_g_ctrl(struct file *file, void *priv, |
197 | struct v4l2_control *ctrl) | |
198 | { | |
922c78e9 | 199 | struct rtrack2 *rt = video_drvdata(file); |
f8c559f8 | 200 | |
25f30389 DL |
201 | switch (ctrl->id) { |
202 | case V4L2_CID_AUDIO_MUTE: | |
203 | ctrl->value = rt->muted; | |
204 | return 0; | |
205 | case V4L2_CID_AUDIO_VOLUME: | |
206 | if (rt->muted) | |
207 | ctrl->value = 0; | |
208 | else | |
209 | ctrl->value = 65535; | |
210 | return 0; | |
1da177e4 | 211 | } |
25f30389 | 212 | return -EINVAL; |
1da177e4 LT |
213 | } |
214 | ||
25f30389 DL |
215 | static int vidioc_s_ctrl(struct file *file, void *priv, |
216 | struct v4l2_control *ctrl) | |
1da177e4 | 217 | { |
922c78e9 | 218 | struct rtrack2 *rt = video_drvdata(file); |
25f30389 DL |
219 | |
220 | switch (ctrl->id) { | |
221 | case V4L2_CID_AUDIO_MUTE: | |
222 | if (ctrl->value) | |
223 | rt_mute(rt); | |
224 | else | |
225 | rt_unmute(rt); | |
226 | return 0; | |
227 | case V4L2_CID_AUDIO_VOLUME: | |
228 | if (ctrl->value) | |
229 | rt_unmute(rt); | |
230 | else | |
231 | rt_mute(rt); | |
232 | return 0; | |
233 | } | |
234 | return -EINVAL; | |
1da177e4 LT |
235 | } |
236 | ||
8b811cf0 DL |
237 | static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) |
238 | { | |
239 | *i = 0; | |
240 | return 0; | |
241 | } | |
242 | ||
243 | static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) | |
244 | { | |
922c78e9 | 245 | return i ? -EINVAL : 0; |
8b811cf0 DL |
246 | } |
247 | ||
922c78e9 | 248 | static int vidioc_g_audio(struct file *file, void *priv, |
8b811cf0 DL |
249 | struct v4l2_audio *a) |
250 | { | |
922c78e9 HV |
251 | a->index = 0; |
252 | strlcpy(a->name, "Radio", sizeof(a->name)); | |
253 | a->capability = V4L2_AUDCAP_STEREO; | |
8b811cf0 DL |
254 | return 0; |
255 | } | |
256 | ||
922c78e9 HV |
257 | static int vidioc_s_audio(struct file *file, void *priv, |
258 | struct v4l2_audio *a) | |
259 | { | |
260 | return a->index ? -EINVAL : 0; | |
261 | } | |
1da177e4 | 262 | |
bec43661 | 263 | static const struct v4l2_file_operations rtrack2_fops = { |
1da177e4 | 264 | .owner = THIS_MODULE, |
25f30389 | 265 | .ioctl = video_ioctl2, |
1da177e4 LT |
266 | }; |
267 | ||
a399810c | 268 | static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = { |
25f30389 DL |
269 | .vidioc_querycap = vidioc_querycap, |
270 | .vidioc_g_tuner = vidioc_g_tuner, | |
271 | .vidioc_s_tuner = vidioc_s_tuner, | |
272 | .vidioc_g_frequency = vidioc_g_frequency, | |
273 | .vidioc_s_frequency = vidioc_s_frequency, | |
274 | .vidioc_queryctrl = vidioc_queryctrl, | |
275 | .vidioc_g_ctrl = vidioc_g_ctrl, | |
276 | .vidioc_s_ctrl = vidioc_s_ctrl, | |
8b811cf0 DL |
277 | .vidioc_g_audio = vidioc_g_audio, |
278 | .vidioc_s_audio = vidioc_s_audio, | |
279 | .vidioc_g_input = vidioc_g_input, | |
280 | .vidioc_s_input = vidioc_s_input, | |
1da177e4 LT |
281 | }; |
282 | ||
283 | static int __init rtrack2_init(void) | |
284 | { | |
922c78e9 HV |
285 | struct rtrack2 *dev = &rtrack2_card; |
286 | struct v4l2_device *v4l2_dev = &dev->v4l2_dev; | |
287 | int res; | |
288 | ||
289 | strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name)); | |
290 | dev->io = io; | |
291 | if (dev->io == -1) { | |
292 | v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n"); | |
1da177e4 LT |
293 | return -EINVAL; |
294 | } | |
922c78e9 HV |
295 | if (!request_region(dev->io, 4, "rtrack2")) { |
296 | v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io); | |
1da177e4 LT |
297 | return -EBUSY; |
298 | } | |
299 | ||
922c78e9 HV |
300 | res = v4l2_device_register(NULL, v4l2_dev); |
301 | if (res < 0) { | |
302 | release_region(dev->io, 4); | |
303 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | |
304 | return res; | |
305 | } | |
1da177e4 | 306 | |
922c78e9 HV |
307 | strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); |
308 | dev->vdev.v4l2_dev = v4l2_dev; | |
309 | dev->vdev.fops = &rtrack2_fops; | |
310 | dev->vdev.ioctl_ops = &rtrack2_ioctl_ops; | |
311 | dev->vdev.release = video_device_release_empty; | |
312 | video_set_drvdata(&dev->vdev, dev); | |
313 | ||
314 | mutex_init(&dev->lock); | |
315 | if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { | |
316 | v4l2_device_unregister(v4l2_dev); | |
317 | release_region(dev->io, 4); | |
1da177e4 LT |
318 | return -EINVAL; |
319 | } | |
4286c6f6 | 320 | |
922c78e9 | 321 | v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n"); |
1da177e4 | 322 | |
4286c6f6 | 323 | /* mute card - prevents noisy bootups */ |
922c78e9 HV |
324 | outb(1, dev->io); |
325 | dev->muted = 1; | |
1da177e4 LT |
326 | |
327 | return 0; | |
328 | } | |
329 | ||
922c78e9 | 330 | static void __exit rtrack2_exit(void) |
1da177e4 | 331 | { |
922c78e9 HV |
332 | struct rtrack2 *dev = &rtrack2_card; |
333 | ||
334 | video_unregister_device(&dev->vdev); | |
335 | v4l2_device_unregister(&dev->v4l2_dev); | |
336 | release_region(dev->io, 4); | |
1da177e4 LT |
337 | } |
338 | ||
339 | module_init(rtrack2_init); | |
922c78e9 | 340 | module_exit(rtrack2_exit); |