aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/radio/radio-isa.c
diff options
context:
space:
mode:
authorHans Verkuil <hans.verkuil@cisco.com>2012-02-03 06:28:56 -0500
committerMauro Carvalho Chehab <mchehab@redhat.com>2012-02-14 14:01:13 -0500
commit137c579c12bbb47ac1822e1a959aa15d0fcb76c1 (patch)
tree9b2ab60f813734ebf31126eda3bf60466573310a /drivers/media/radio/radio-isa.c
parent83ec8225b6aecfddafd3b1d40cf79c0d4615a84c (diff)
[media] radio-isa: add framework for ISA radio drivers
We have quite a few ISA radio drivers, which are all very similar. This framework makes it possible to reduce the code size of those drivers and makes it much easier to keep them up to date with the latest V4L2 API developments. Drivers rewritten to use this framework fully pass the v4l2-compliance tests and are properly using the ISA bus (so they can be found under /sys/bus/isa). It is now also possible to support multiple cards using the same driver (tested with two radio-gemtek cards). Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/radio/radio-isa.c')
-rw-r--r--drivers/media/radio/radio-isa.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c
new file mode 100644
index 00000000000..02bcead8b08
--- /dev/null
+++ b/drivers/media/radio/radio-isa.c
@@ -0,0 +1,339 @@
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 <media/v4l2-device.h>
30#include <media/v4l2-ioctl.h>
31#include <media/v4l2-fh.h>
32#include <media/v4l2-ctrls.h>
33#include <media/v4l2-event.h>
34
35#include "radio-isa.h"
36
37MODULE_AUTHOR("Hans Verkuil");
38MODULE_DESCRIPTION("A framework for ISA radio drivers.");
39MODULE_LICENSE("GPL");
40
41#define FREQ_LOW (87U * 16000U)
42#define FREQ_HIGH (108U * 16000U)
43
44static int radio_isa_querycap(struct file *file, void *priv,
45 struct v4l2_capability *v)
46{
47 struct radio_isa_card *isa = video_drvdata(file);
48
49 strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver));
50 strlcpy(v->card, isa->drv->card, sizeof(v->card));
51 snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name);
52
53 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
54 v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS;
55 return 0;
56}
57
58static int radio_isa_g_tuner(struct file *file, void *priv,
59 struct v4l2_tuner *v)
60{
61 struct radio_isa_card *isa = video_drvdata(file);
62 const struct radio_isa_ops *ops = isa->drv->ops;
63
64 if (v->index > 0)
65 return -EINVAL;
66
67 strlcpy(v->name, "FM", sizeof(v->name));
68 v->type = V4L2_TUNER_RADIO;
69 v->rangelow = FREQ_LOW;
70 v->rangehigh = FREQ_HIGH;
71 v->capability = V4L2_TUNER_CAP_LOW;
72 if (isa->drv->has_stereo)
73 v->capability |= V4L2_TUNER_CAP_STEREO;
74
75 if (ops->g_rxsubchans)
76 v->rxsubchans = ops->g_rxsubchans(isa);
77 else
78 v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
79 v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
80 if (ops->g_signal)
81 v->signal = ops->g_signal(isa);
82 else
83 v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ?
84 0xffff : 0;
85 return 0;
86}
87
88static int radio_isa_s_tuner(struct file *file, void *priv,
89 struct v4l2_tuner *v)
90{
91 struct radio_isa_card *isa = video_drvdata(file);
92 const struct radio_isa_ops *ops = isa->drv->ops;
93
94 if (v->index)
95 return -EINVAL;
96 if (ops->s_stereo) {
97 isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO);
98 return ops->s_stereo(isa, isa->stereo);
99 }
100 return 0;
101}
102
103static int radio_isa_s_frequency(struct file *file, void *priv,
104 struct v4l2_frequency *f)
105{
106 struct radio_isa_card *isa = video_drvdata(file);
107 int res;
108
109 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
110 return -EINVAL;
111 f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH);
112 res = isa->drv->ops->s_frequency(isa, f->frequency);
113 if (res == 0)
114 isa->freq = f->frequency;
115 return res;
116}
117
118static int radio_isa_g_frequency(struct file *file, void *priv,
119 struct v4l2_frequency *f)
120{
121 struct radio_isa_card *isa = video_drvdata(file);
122
123 if (f->tuner != 0)
124 return -EINVAL;
125 f->type = V4L2_TUNER_RADIO;
126 f->frequency = isa->freq;
127 return 0;
128}
129
130static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl)
131{
132 struct radio_isa_card *isa =
133 container_of(ctrl->handler, struct radio_isa_card, hdl);
134
135 switch (ctrl->id) {
136 case V4L2_CID_AUDIO_MUTE:
137 return isa->drv->ops->s_mute_volume(isa, ctrl->val,
138 isa->volume ? isa->volume->val : 0);
139 }
140 return -EINVAL;
141}
142
143static int radio_isa_log_status(struct file *file, void *priv)
144{
145 struct radio_isa_card *isa = video_drvdata(file);
146
147 v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io);
148 v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name);
149 return 0;
150}
151
152static int radio_isa_subscribe_event(struct v4l2_fh *fh,
153 struct v4l2_event_subscription *sub)
154{
155 if (sub->type == V4L2_EVENT_CTRL)
156 return v4l2_event_subscribe(fh, sub, 0);
157 return -EINVAL;
158}
159
160static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = {
161 .s_ctrl = radio_isa_s_ctrl,
162};
163
164static const struct v4l2_file_operations radio_isa_fops = {
165 .owner = THIS_MODULE,
166 .open = v4l2_fh_open,
167 .release = v4l2_fh_release,
168 .poll = v4l2_ctrl_poll,
169 .unlocked_ioctl = video_ioctl2,
170};
171
172static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = {
173 .vidioc_querycap = radio_isa_querycap,
174 .vidioc_g_tuner = radio_isa_g_tuner,
175 .vidioc_s_tuner = radio_isa_s_tuner,
176 .vidioc_g_frequency = radio_isa_g_frequency,
177 .vidioc_s_frequency = radio_isa_s_frequency,
178 .vidioc_log_status = radio_isa_log_status,
179 .vidioc_subscribe_event = radio_isa_subscribe_event,
180 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
181};
182
183int radio_isa_match(struct device *pdev, unsigned int dev)
184{
185 struct radio_isa_driver *drv = pdev->platform_data;
186
187 return drv->probe || drv->io_params[dev] >= 0;
188}
189EXPORT_SYMBOL_GPL(radio_isa_match);
190
191static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io)
192{
193 int i;
194
195 for (i = 0; i < drv->num_of_io_ports; i++)
196 if (drv->io_ports[i] == io)
197 return true;
198 return false;
199}
200
201int radio_isa_probe(struct device *pdev, unsigned int dev)
202{
203 struct radio_isa_driver *drv = pdev->platform_data;
204 const struct radio_isa_ops *ops = drv->ops;
205 struct v4l2_device *v4l2_dev;
206 struct radio_isa_card *isa;
207 int res;
208
209 isa = drv->ops->alloc();
210 if (isa == NULL)
211 return -ENOMEM;
212 dev_set_drvdata(pdev, isa);
213 isa->drv = drv;
214 isa->io = drv->io_params[dev];
215 v4l2_dev = &isa->v4l2_dev;
216 strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name));
217
218 if (drv->probe && ops->probe) {
219 int i;
220
221 for (i = 0; i < drv->num_of_io_ports; ++i) {
222 int io = drv->io_ports[i];
223
224 if (request_region(io, drv->region_size, v4l2_dev->name)) {
225 bool found = ops->probe(isa, io);
226
227 release_region(io, drv->region_size);
228 if (found) {
229 isa->io = io;
230 break;
231 }
232 }
233 }
234 }
235
236 if (!radio_isa_valid_io(drv, isa->io)) {
237 int i;
238
239 if (isa->io < 0)
240 return -ENODEV;
241 v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x",
242 drv->io_ports[0]);
243 for (i = 1; i < drv->num_of_io_ports; i++)
244 printk(KERN_CONT "/0x%03x", drv->io_ports[i]);
245 printk(KERN_CONT ".\n");
246 kfree(isa);
247 return -EINVAL;
248 }
249
250 if (!request_region(isa->io, drv->region_size, v4l2_dev->name)) {
251 v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io);
252 kfree(isa);
253 return -EBUSY;
254 }
255
256 res = v4l2_device_register(pdev, v4l2_dev);
257 if (res < 0) {
258 v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
259 goto err_dev_reg;
260 }
261
262 v4l2_ctrl_handler_init(&isa->hdl, 1);
263 isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
264 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
265 if (drv->max_volume)
266 isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
267 V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1,
268 drv->max_volume);
269 v4l2_dev->ctrl_handler = &isa->hdl;
270 if (isa->hdl.error) {
271 res = isa->hdl.error;
272 v4l2_err(v4l2_dev, "Could not register controls\n");
273 goto err_hdl;
274 }
275 if (drv->max_volume)
276 v4l2_ctrl_cluster(2, &isa->mute);
277 v4l2_dev->ctrl_handler = &isa->hdl;
278
279 mutex_init(&isa->lock);
280 isa->vdev.lock = &isa->lock;
281 strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name));
282 isa->vdev.v4l2_dev = v4l2_dev;
283 isa->vdev.fops = &radio_isa_fops;
284 isa->vdev.ioctl_ops = &radio_isa_ioctl_ops;
285 isa->vdev.release = video_device_release_empty;
286 set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags);
287 video_set_drvdata(&isa->vdev, isa);
288 isa->freq = FREQ_LOW;
289 isa->stereo = drv->has_stereo;
290
291 if (ops->init)
292 res = ops->init(isa);
293 if (!res)
294 res = v4l2_ctrl_handler_setup(&isa->hdl);
295 if (!res)
296 res = ops->s_frequency(isa, isa->freq);
297 if (!res && ops->s_stereo)
298 res = ops->s_stereo(isa, isa->stereo);
299 if (res < 0) {
300 v4l2_err(v4l2_dev, "Could not setup card\n");
301 goto err_node_reg;
302 }
303 res = video_register_device(&isa->vdev, VFL_TYPE_RADIO,
304 drv->radio_nr_params[dev]);
305 if (res < 0) {
306 v4l2_err(v4l2_dev, "Could not register device node\n");
307 goto err_node_reg;
308 }
309
310 v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n",
311 drv->card, isa->io);
312 return 0;
313
314err_node_reg:
315 v4l2_ctrl_handler_free(&isa->hdl);
316err_hdl:
317 v4l2_device_unregister(&isa->v4l2_dev);
318err_dev_reg:
319 release_region(isa->io, drv->region_size);
320 kfree(isa);
321 return res;
322}
323EXPORT_SYMBOL_GPL(radio_isa_probe);
324
325int radio_isa_remove(struct device *pdev, unsigned int dev)
326{
327 struct radio_isa_card *isa = dev_get_drvdata(pdev);
328 const struct radio_isa_ops *ops = isa->drv->ops;
329
330 ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0);
331 video_unregister_device(&isa->vdev);
332 v4l2_ctrl_handler_free(&isa->hdl);
333 v4l2_device_unregister(&isa->v4l2_dev);
334 release_region(isa->io, isa->drv->region_size);
335 v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card);
336 kfree(isa);
337 return 0;
338}
339EXPORT_SYMBOL_GPL(radio_isa_remove);