diff options
Diffstat (limited to 'drivers/media/radio/radio-isa.c')
-rw-r--r-- | drivers/media/radio/radio-isa.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c new file mode 100644 index 000000000000..06f906351fad --- /dev/null +++ b/drivers/media/radio/radio-isa.c | |||
@@ -0,0 +1,340 @@ | |||
1 | /* | ||
2 | * Framework for ISA radio drivers. | ||
3 | * This takes care of all the V4L2 scaffolding, allowing the ISA drivers | ||
4 | * to concentrate on the actual hardware operation. | ||
5 | * | ||
6 | * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
20 | * 02110-1301 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/ioport.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/videodev2.h> | ||
28 | #include <linux/io.h> | ||
29 | #include <linux/slab.h> | ||
30 | #include <media/v4l2-device.h> | ||
31 | #include <media/v4l2-ioctl.h> | ||
32 | #include <media/v4l2-fh.h> | ||
33 | #include <media/v4l2-ctrls.h> | ||
34 | #include <media/v4l2-event.h> | ||
35 | |||
36 | #include "radio-isa.h" | ||
37 | |||
38 | MODULE_AUTHOR("Hans Verkuil"); | ||
39 | MODULE_DESCRIPTION("A framework for ISA radio drivers."); | ||
40 | MODULE_LICENSE("GPL"); | ||
41 | |||
42 | #define FREQ_LOW (87U * 16000U) | ||
43 | #define FREQ_HIGH (108U * 16000U) | ||
44 | |||
45 | static int radio_isa_querycap(struct file *file, void *priv, | ||
46 | struct v4l2_capability *v) | ||
47 | { | ||
48 | struct radio_isa_card *isa = video_drvdata(file); | ||
49 | |||
50 | strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver)); | ||
51 | strlcpy(v->card, isa->drv->card, sizeof(v->card)); | ||
52 | snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name); | ||
53 | |||
54 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; | ||
55 | v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS; | ||
56 | return 0; | ||
57 | } | ||
58 | |||
59 | static int radio_isa_g_tuner(struct file *file, void *priv, | ||
60 | struct v4l2_tuner *v) | ||
61 | { | ||
62 | struct radio_isa_card *isa = video_drvdata(file); | ||
63 | const struct radio_isa_ops *ops = isa->drv->ops; | ||
64 | |||
65 | if (v->index > 0) | ||
66 | return -EINVAL; | ||
67 | |||
68 | strlcpy(v->name, "FM", sizeof(v->name)); | ||
69 | v->type = V4L2_TUNER_RADIO; | ||
70 | v->rangelow = FREQ_LOW; | ||
71 | v->rangehigh = FREQ_HIGH; | ||
72 | v->capability = V4L2_TUNER_CAP_LOW; | ||
73 | if (isa->drv->has_stereo) | ||
74 | v->capability |= V4L2_TUNER_CAP_STEREO; | ||
75 | |||
76 | if (ops->g_rxsubchans) | ||
77 | v->rxsubchans = ops->g_rxsubchans(isa); | ||
78 | else | ||
79 | v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; | ||
80 | v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; | ||
81 | if (ops->g_signal) | ||
82 | v->signal = ops->g_signal(isa); | ||
83 | else | ||
84 | v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ? | ||
85 | 0xffff : 0; | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | static int radio_isa_s_tuner(struct file *file, void *priv, | ||
90 | struct v4l2_tuner *v) | ||
91 | { | ||
92 | struct radio_isa_card *isa = video_drvdata(file); | ||
93 | const struct radio_isa_ops *ops = isa->drv->ops; | ||
94 | |||
95 | if (v->index) | ||
96 | return -EINVAL; | ||
97 | if (ops->s_stereo) { | ||
98 | isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO); | ||
99 | return ops->s_stereo(isa, isa->stereo); | ||
100 | } | ||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | static int radio_isa_s_frequency(struct file *file, void *priv, | ||
105 | struct v4l2_frequency *f) | ||
106 | { | ||
107 | struct radio_isa_card *isa = video_drvdata(file); | ||
108 | int res; | ||
109 | |||
110 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) | ||
111 | return -EINVAL; | ||
112 | f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH); | ||
113 | res = isa->drv->ops->s_frequency(isa, f->frequency); | ||
114 | if (res == 0) | ||
115 | isa->freq = f->frequency; | ||
116 | return res; | ||
117 | } | ||
118 | |||
119 | static int radio_isa_g_frequency(struct file *file, void *priv, | ||
120 | struct v4l2_frequency *f) | ||
121 | { | ||
122 | struct radio_isa_card *isa = video_drvdata(file); | ||
123 | |||
124 | if (f->tuner != 0) | ||
125 | return -EINVAL; | ||
126 | f->type = V4L2_TUNER_RADIO; | ||
127 | f->frequency = isa->freq; | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl) | ||
132 | { | ||
133 | struct radio_isa_card *isa = | ||
134 | container_of(ctrl->handler, struct radio_isa_card, hdl); | ||
135 | |||
136 | switch (ctrl->id) { | ||
137 | case V4L2_CID_AUDIO_MUTE: | ||
138 | return isa->drv->ops->s_mute_volume(isa, ctrl->val, | ||
139 | isa->volume ? isa->volume->val : 0); | ||
140 | } | ||
141 | return -EINVAL; | ||
142 | } | ||
143 | |||
144 | static int radio_isa_log_status(struct file *file, void *priv) | ||
145 | { | ||
146 | struct radio_isa_card *isa = video_drvdata(file); | ||
147 | |||
148 | v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io); | ||
149 | v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name); | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int radio_isa_subscribe_event(struct v4l2_fh *fh, | ||
154 | struct v4l2_event_subscription *sub) | ||
155 | { | ||
156 | if (sub->type == V4L2_EVENT_CTRL) | ||
157 | return v4l2_event_subscribe(fh, sub, 0); | ||
158 | return -EINVAL; | ||
159 | } | ||
160 | |||
161 | static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = { | ||
162 | .s_ctrl = radio_isa_s_ctrl, | ||
163 | }; | ||
164 | |||
165 | static const struct v4l2_file_operations radio_isa_fops = { | ||
166 | .owner = THIS_MODULE, | ||
167 | .open = v4l2_fh_open, | ||
168 | .release = v4l2_fh_release, | ||
169 | .poll = v4l2_ctrl_poll, | ||
170 | .unlocked_ioctl = video_ioctl2, | ||
171 | }; | ||
172 | |||
173 | static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = { | ||
174 | .vidioc_querycap = radio_isa_querycap, | ||
175 | .vidioc_g_tuner = radio_isa_g_tuner, | ||
176 | .vidioc_s_tuner = radio_isa_s_tuner, | ||
177 | .vidioc_g_frequency = radio_isa_g_frequency, | ||
178 | .vidioc_s_frequency = radio_isa_s_frequency, | ||
179 | .vidioc_log_status = radio_isa_log_status, | ||
180 | .vidioc_subscribe_event = radio_isa_subscribe_event, | ||
181 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, | ||
182 | }; | ||
183 | |||
184 | int radio_isa_match(struct device *pdev, unsigned int dev) | ||
185 | { | ||
186 | struct radio_isa_driver *drv = pdev->platform_data; | ||
187 | |||
188 | return drv->probe || drv->io_params[dev] >= 0; | ||
189 | } | ||
190 | EXPORT_SYMBOL_GPL(radio_isa_match); | ||
191 | |||
192 | static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io) | ||
193 | { | ||
194 | int i; | ||
195 | |||
196 | for (i = 0; i < drv->num_of_io_ports; i++) | ||
197 | if (drv->io_ports[i] == io) | ||
198 | return true; | ||
199 | return false; | ||
200 | } | ||
201 | |||
202 | int radio_isa_probe(struct device *pdev, unsigned int dev) | ||
203 | { | ||
204 | struct radio_isa_driver *drv = pdev->platform_data; | ||
205 | const struct radio_isa_ops *ops = drv->ops; | ||
206 | struct v4l2_device *v4l2_dev; | ||
207 | struct radio_isa_card *isa; | ||
208 | int res; | ||
209 | |||
210 | isa = drv->ops->alloc(); | ||
211 | if (isa == NULL) | ||
212 | return -ENOMEM; | ||
213 | dev_set_drvdata(pdev, isa); | ||
214 | isa->drv = drv; | ||
215 | isa->io = drv->io_params[dev]; | ||
216 | v4l2_dev = &isa->v4l2_dev; | ||
217 | strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name)); | ||
218 | |||
219 | if (drv->probe && ops->probe) { | ||
220 | int i; | ||
221 | |||
222 | for (i = 0; i < drv->num_of_io_ports; ++i) { | ||
223 | int io = drv->io_ports[i]; | ||
224 | |||
225 | if (request_region(io, drv->region_size, v4l2_dev->name)) { | ||
226 | bool found = ops->probe(isa, io); | ||
227 | |||
228 | release_region(io, drv->region_size); | ||
229 | if (found) { | ||
230 | isa->io = io; | ||
231 | break; | ||
232 | } | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | if (!radio_isa_valid_io(drv, isa->io)) { | ||
238 | int i; | ||
239 | |||
240 | if (isa->io < 0) | ||
241 | return -ENODEV; | ||
242 | v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x", | ||
243 | drv->io_ports[0]); | ||
244 | for (i = 1; i < drv->num_of_io_ports; i++) | ||
245 | printk(KERN_CONT "/0x%03x", drv->io_ports[i]); | ||
246 | printk(KERN_CONT ".\n"); | ||
247 | kfree(isa); | ||
248 | return -EINVAL; | ||
249 | } | ||
250 | |||
251 | if (!request_region(isa->io, drv->region_size, v4l2_dev->name)) { | ||
252 | v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io); | ||
253 | kfree(isa); | ||
254 | return -EBUSY; | ||
255 | } | ||
256 | |||
257 | res = v4l2_device_register(pdev, v4l2_dev); | ||
258 | if (res < 0) { | ||
259 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); | ||
260 | goto err_dev_reg; | ||
261 | } | ||
262 | |||
263 | v4l2_ctrl_handler_init(&isa->hdl, 1); | ||
264 | isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, | ||
265 | V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); | ||
266 | if (drv->max_volume) | ||
267 | isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, | ||
268 | V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1, | ||
269 | drv->max_volume); | ||
270 | v4l2_dev->ctrl_handler = &isa->hdl; | ||
271 | if (isa->hdl.error) { | ||
272 | res = isa->hdl.error; | ||
273 | v4l2_err(v4l2_dev, "Could not register controls\n"); | ||
274 | goto err_hdl; | ||
275 | } | ||
276 | if (drv->max_volume) | ||
277 | v4l2_ctrl_cluster(2, &isa->mute); | ||
278 | v4l2_dev->ctrl_handler = &isa->hdl; | ||
279 | |||
280 | mutex_init(&isa->lock); | ||
281 | isa->vdev.lock = &isa->lock; | ||
282 | strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name)); | ||
283 | isa->vdev.v4l2_dev = v4l2_dev; | ||
284 | isa->vdev.fops = &radio_isa_fops; | ||
285 | isa->vdev.ioctl_ops = &radio_isa_ioctl_ops; | ||
286 | isa->vdev.release = video_device_release_empty; | ||
287 | set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags); | ||
288 | video_set_drvdata(&isa->vdev, isa); | ||
289 | isa->freq = FREQ_LOW; | ||
290 | isa->stereo = drv->has_stereo; | ||
291 | |||
292 | if (ops->init) | ||
293 | res = ops->init(isa); | ||
294 | if (!res) | ||
295 | res = v4l2_ctrl_handler_setup(&isa->hdl); | ||
296 | if (!res) | ||
297 | res = ops->s_frequency(isa, isa->freq); | ||
298 | if (!res && ops->s_stereo) | ||
299 | res = ops->s_stereo(isa, isa->stereo); | ||
300 | if (res < 0) { | ||
301 | v4l2_err(v4l2_dev, "Could not setup card\n"); | ||
302 | goto err_node_reg; | ||
303 | } | ||
304 | res = video_register_device(&isa->vdev, VFL_TYPE_RADIO, | ||
305 | drv->radio_nr_params[dev]); | ||
306 | if (res < 0) { | ||
307 | v4l2_err(v4l2_dev, "Could not register device node\n"); | ||
308 | goto err_node_reg; | ||
309 | } | ||
310 | |||
311 | v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n", | ||
312 | drv->card, isa->io); | ||
313 | return 0; | ||
314 | |||
315 | err_node_reg: | ||
316 | v4l2_ctrl_handler_free(&isa->hdl); | ||
317 | err_hdl: | ||
318 | v4l2_device_unregister(&isa->v4l2_dev); | ||
319 | err_dev_reg: | ||
320 | release_region(isa->io, drv->region_size); | ||
321 | kfree(isa); | ||
322 | return res; | ||
323 | } | ||
324 | EXPORT_SYMBOL_GPL(radio_isa_probe); | ||
325 | |||
326 | int radio_isa_remove(struct device *pdev, unsigned int dev) | ||
327 | { | ||
328 | struct radio_isa_card *isa = dev_get_drvdata(pdev); | ||
329 | const struct radio_isa_ops *ops = isa->drv->ops; | ||
330 | |||
331 | ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0); | ||
332 | video_unregister_device(&isa->vdev); | ||
333 | v4l2_ctrl_handler_free(&isa->hdl); | ||
334 | v4l2_device_unregister(&isa->v4l2_dev); | ||
335 | release_region(isa->io, isa->drv->region_size); | ||
336 | v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card); | ||
337 | kfree(isa); | ||
338 | return 0; | ||
339 | } | ||
340 | EXPORT_SYMBOL_GPL(radio_isa_remove); | ||