1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Framework for ISA radio drivers. |
4 | * This takes care of all the V4L2 scaffolding, allowing the ISA drivers |
5 | * to concentrate on the actual hardware operation. |
6 | * |
7 | * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/ioport.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/videodev2.h> |
15 | #include <linux/io.h> |
16 | #include <linux/slab.h> |
17 | #include <media/v4l2-device.h> |
18 | #include <media/v4l2-ioctl.h> |
19 | #include <media/v4l2-fh.h> |
20 | #include <media/v4l2-ctrls.h> |
21 | #include <media/v4l2-event.h> |
22 | |
23 | #include "radio-isa.h" |
24 | |
25 | MODULE_AUTHOR("Hans Verkuil" ); |
26 | MODULE_DESCRIPTION("A framework for ISA radio drivers." ); |
27 | MODULE_LICENSE("GPL" ); |
28 | |
29 | #define FREQ_LOW (87U * 16000U) |
30 | #define FREQ_HIGH (108U * 16000U) |
31 | |
32 | static int radio_isa_querycap(struct file *file, void *priv, |
33 | struct v4l2_capability *v) |
34 | { |
35 | struct radio_isa_card *isa = video_drvdata(file); |
36 | |
37 | strscpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver)); |
38 | strscpy(v->card, isa->drv->card, sizeof(v->card)); |
39 | snprintf(buf: v->bus_info, size: sizeof(v->bus_info), fmt: "ISA:%s" , dev_name(dev: isa->v4l2_dev.dev)); |
40 | return 0; |
41 | } |
42 | |
43 | static int radio_isa_g_tuner(struct file *file, void *priv, |
44 | struct v4l2_tuner *v) |
45 | { |
46 | struct radio_isa_card *isa = video_drvdata(file); |
47 | const struct radio_isa_ops *ops = isa->drv->ops; |
48 | |
49 | if (v->index > 0) |
50 | return -EINVAL; |
51 | |
52 | strscpy(v->name, "FM" , sizeof(v->name)); |
53 | v->type = V4L2_TUNER_RADIO; |
54 | v->rangelow = FREQ_LOW; |
55 | v->rangehigh = FREQ_HIGH; |
56 | v->capability = V4L2_TUNER_CAP_LOW; |
57 | if (isa->drv->has_stereo) |
58 | v->capability |= V4L2_TUNER_CAP_STEREO; |
59 | |
60 | if (ops->g_rxsubchans) |
61 | v->rxsubchans = ops->g_rxsubchans(isa); |
62 | else |
63 | v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; |
64 | v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; |
65 | if (ops->g_signal) |
66 | v->signal = ops->g_signal(isa); |
67 | else |
68 | v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ? |
69 | 0xffff : 0; |
70 | return 0; |
71 | } |
72 | |
73 | static int radio_isa_s_tuner(struct file *file, void *priv, |
74 | const struct v4l2_tuner *v) |
75 | { |
76 | struct radio_isa_card *isa = video_drvdata(file); |
77 | const struct radio_isa_ops *ops = isa->drv->ops; |
78 | |
79 | if (v->index) |
80 | return -EINVAL; |
81 | if (ops->s_stereo) { |
82 | isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO); |
83 | return ops->s_stereo(isa, isa->stereo); |
84 | } |
85 | return 0; |
86 | } |
87 | |
88 | static int radio_isa_s_frequency(struct file *file, void *priv, |
89 | const struct v4l2_frequency *f) |
90 | { |
91 | struct radio_isa_card *isa = video_drvdata(file); |
92 | u32 freq = f->frequency; |
93 | int res; |
94 | |
95 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) |
96 | return -EINVAL; |
97 | freq = clamp(freq, FREQ_LOW, FREQ_HIGH); |
98 | res = isa->drv->ops->s_frequency(isa, freq); |
99 | if (res == 0) |
100 | isa->freq = freq; |
101 | return res; |
102 | } |
103 | |
104 | static int radio_isa_g_frequency(struct file *file, void *priv, |
105 | struct v4l2_frequency *f) |
106 | { |
107 | struct radio_isa_card *isa = video_drvdata(file); |
108 | |
109 | if (f->tuner != 0) |
110 | return -EINVAL; |
111 | f->type = V4L2_TUNER_RADIO; |
112 | f->frequency = isa->freq; |
113 | return 0; |
114 | } |
115 | |
116 | static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl) |
117 | { |
118 | struct radio_isa_card *isa = |
119 | container_of(ctrl->handler, struct radio_isa_card, hdl); |
120 | |
121 | switch (ctrl->id) { |
122 | case V4L2_CID_AUDIO_MUTE: |
123 | return isa->drv->ops->s_mute_volume(isa, ctrl->val, |
124 | isa->volume ? isa->volume->val : 0); |
125 | } |
126 | return -EINVAL; |
127 | } |
128 | |
129 | static int radio_isa_log_status(struct file *file, void *priv) |
130 | { |
131 | struct radio_isa_card *isa = video_drvdata(file); |
132 | |
133 | v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n" , isa->io); |
134 | v4l2_ctrl_handler_log_status(hdl: &isa->hdl, prefix: isa->v4l2_dev.name); |
135 | return 0; |
136 | } |
137 | |
138 | static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = { |
139 | .s_ctrl = radio_isa_s_ctrl, |
140 | }; |
141 | |
142 | static const struct v4l2_file_operations radio_isa_fops = { |
143 | .owner = THIS_MODULE, |
144 | .open = v4l2_fh_open, |
145 | .release = v4l2_fh_release, |
146 | .poll = v4l2_ctrl_poll, |
147 | .unlocked_ioctl = video_ioctl2, |
148 | }; |
149 | |
150 | static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = { |
151 | .vidioc_querycap = radio_isa_querycap, |
152 | .vidioc_g_tuner = radio_isa_g_tuner, |
153 | .vidioc_s_tuner = radio_isa_s_tuner, |
154 | .vidioc_g_frequency = radio_isa_g_frequency, |
155 | .vidioc_s_frequency = radio_isa_s_frequency, |
156 | .vidioc_log_status = radio_isa_log_status, |
157 | .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
158 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
159 | }; |
160 | |
161 | int radio_isa_match(struct device *pdev, unsigned int dev) |
162 | { |
163 | struct radio_isa_driver *drv = pdev->platform_data; |
164 | |
165 | return drv->probe || drv->io_params[dev] >= 0; |
166 | } |
167 | EXPORT_SYMBOL_GPL(radio_isa_match); |
168 | |
169 | static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io) |
170 | { |
171 | int i; |
172 | |
173 | for (i = 0; i < drv->num_of_io_ports; i++) |
174 | if (drv->io_ports[i] == io) |
175 | return true; |
176 | return false; |
177 | } |
178 | |
179 | static struct radio_isa_card *radio_isa_alloc(struct radio_isa_driver *drv, |
180 | struct device *pdev) |
181 | { |
182 | struct v4l2_device *v4l2_dev; |
183 | struct radio_isa_card *isa = drv->ops->alloc(); |
184 | if (!isa) |
185 | return NULL; |
186 | |
187 | dev_set_drvdata(dev: pdev, data: isa); |
188 | isa->drv = drv; |
189 | v4l2_dev = &isa->v4l2_dev; |
190 | strscpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name)); |
191 | |
192 | return isa; |
193 | } |
194 | |
195 | static int radio_isa_common_probe(struct radio_isa_card *isa, |
196 | struct device *pdev, |
197 | int radio_nr, unsigned region_size) |
198 | { |
199 | const struct radio_isa_driver *drv = isa->drv; |
200 | const struct radio_isa_ops *ops = drv->ops; |
201 | struct v4l2_device *v4l2_dev = &isa->v4l2_dev; |
202 | int res; |
203 | |
204 | if (!request_region(isa->io, region_size, v4l2_dev->name)) { |
205 | v4l2_err(v4l2_dev, "port 0x%x already in use\n" , isa->io); |
206 | kfree(objp: isa); |
207 | return -EBUSY; |
208 | } |
209 | |
210 | res = v4l2_device_register(dev: pdev, v4l2_dev); |
211 | if (res < 0) { |
212 | v4l2_err(v4l2_dev, "Could not register v4l2_device\n" ); |
213 | goto err_dev_reg; |
214 | } |
215 | |
216 | v4l2_ctrl_handler_init(&isa->hdl, 1); |
217 | isa->mute = v4l2_ctrl_new_std(hdl: &isa->hdl, ops: &radio_isa_ctrl_ops, |
218 | V4L2_CID_AUDIO_MUTE, min: 0, max: 1, step: 1, def: 1); |
219 | if (drv->max_volume) |
220 | isa->volume = v4l2_ctrl_new_std(hdl: &isa->hdl, ops: &radio_isa_ctrl_ops, |
221 | V4L2_CID_AUDIO_VOLUME, min: 0, max: drv->max_volume, step: 1, |
222 | def: drv->max_volume); |
223 | v4l2_dev->ctrl_handler = &isa->hdl; |
224 | if (isa->hdl.error) { |
225 | res = isa->hdl.error; |
226 | v4l2_err(v4l2_dev, "Could not register controls\n" ); |
227 | goto err_hdl; |
228 | } |
229 | if (drv->max_volume) |
230 | v4l2_ctrl_cluster(ncontrols: 2, controls: &isa->mute); |
231 | v4l2_dev->ctrl_handler = &isa->hdl; |
232 | |
233 | mutex_init(&isa->lock); |
234 | isa->vdev.lock = &isa->lock; |
235 | strscpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name)); |
236 | isa->vdev.v4l2_dev = v4l2_dev; |
237 | isa->vdev.fops = &radio_isa_fops; |
238 | isa->vdev.ioctl_ops = &radio_isa_ioctl_ops; |
239 | isa->vdev.release = video_device_release_empty; |
240 | isa->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; |
241 | video_set_drvdata(vdev: &isa->vdev, data: isa); |
242 | isa->freq = FREQ_LOW; |
243 | isa->stereo = drv->has_stereo; |
244 | |
245 | if (ops->init) |
246 | res = ops->init(isa); |
247 | if (!res) |
248 | res = v4l2_ctrl_handler_setup(hdl: &isa->hdl); |
249 | if (!res) |
250 | res = ops->s_frequency(isa, isa->freq); |
251 | if (!res && ops->s_stereo) |
252 | res = ops->s_stereo(isa, isa->stereo); |
253 | if (res < 0) { |
254 | v4l2_err(v4l2_dev, "Could not setup card\n" ); |
255 | goto err_hdl; |
256 | } |
257 | res = video_register_device(vdev: &isa->vdev, type: VFL_TYPE_RADIO, nr: radio_nr); |
258 | |
259 | if (res < 0) { |
260 | v4l2_err(v4l2_dev, "Could not register device node\n" ); |
261 | goto err_hdl; |
262 | } |
263 | |
264 | v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n" , |
265 | drv->card, isa->io); |
266 | return 0; |
267 | |
268 | err_hdl: |
269 | v4l2_ctrl_handler_free(hdl: &isa->hdl); |
270 | err_dev_reg: |
271 | release_region(isa->io, region_size); |
272 | kfree(objp: isa); |
273 | return res; |
274 | } |
275 | |
276 | static void radio_isa_common_remove(struct radio_isa_card *isa, |
277 | unsigned region_size) |
278 | { |
279 | const struct radio_isa_ops *ops = isa->drv->ops; |
280 | |
281 | ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0); |
282 | video_unregister_device(vdev: &isa->vdev); |
283 | v4l2_ctrl_handler_free(hdl: &isa->hdl); |
284 | v4l2_device_unregister(v4l2_dev: &isa->v4l2_dev); |
285 | release_region(isa->io, region_size); |
286 | v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n" , isa->drv->card); |
287 | kfree(objp: isa); |
288 | } |
289 | |
290 | int radio_isa_probe(struct device *pdev, unsigned int dev) |
291 | { |
292 | struct radio_isa_driver *drv = pdev->platform_data; |
293 | const struct radio_isa_ops *ops = drv->ops; |
294 | struct v4l2_device *v4l2_dev; |
295 | struct radio_isa_card *isa; |
296 | |
297 | isa = radio_isa_alloc(drv, pdev); |
298 | if (!isa) |
299 | return -ENOMEM; |
300 | isa->io = drv->io_params[dev]; |
301 | v4l2_dev = &isa->v4l2_dev; |
302 | |
303 | if (drv->probe && ops->probe) { |
304 | int i; |
305 | |
306 | for (i = 0; i < drv->num_of_io_ports; ++i) { |
307 | int io = drv->io_ports[i]; |
308 | |
309 | if (request_region(io, drv->region_size, v4l2_dev->name)) { |
310 | bool found = ops->probe(isa, io); |
311 | |
312 | release_region(io, drv->region_size); |
313 | if (found) { |
314 | isa->io = io; |
315 | break; |
316 | } |
317 | } |
318 | } |
319 | } |
320 | |
321 | if (!radio_isa_valid_io(drv, io: isa->io)) { |
322 | int i; |
323 | |
324 | if (isa->io < 0) |
325 | return -ENODEV; |
326 | v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x" , |
327 | drv->io_ports[0]); |
328 | for (i = 1; i < drv->num_of_io_ports; i++) |
329 | printk(KERN_CONT "/0x%03x" , drv->io_ports[i]); |
330 | printk(KERN_CONT ".\n" ); |
331 | kfree(objp: isa); |
332 | return -EINVAL; |
333 | } |
334 | |
335 | return radio_isa_common_probe(isa, pdev, radio_nr: drv->radio_nr_params[dev], |
336 | region_size: drv->region_size); |
337 | } |
338 | EXPORT_SYMBOL_GPL(radio_isa_probe); |
339 | |
340 | void radio_isa_remove(struct device *pdev, unsigned int dev) |
341 | { |
342 | struct radio_isa_card *isa = dev_get_drvdata(dev: pdev); |
343 | |
344 | radio_isa_common_remove(isa, region_size: isa->drv->region_size); |
345 | } |
346 | EXPORT_SYMBOL_GPL(radio_isa_remove); |
347 | |
348 | #ifdef CONFIG_PNP |
349 | int radio_isa_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) |
350 | { |
351 | struct pnp_driver *pnp_drv = to_pnp_driver(dev->dev.driver); |
352 | struct radio_isa_driver *drv = container_of(pnp_drv, |
353 | struct radio_isa_driver, pnp_driver); |
354 | struct radio_isa_card *isa; |
355 | |
356 | if (!pnp_port_valid(dev, bar: 0)) |
357 | return -ENODEV; |
358 | |
359 | isa = radio_isa_alloc(drv, pdev: &dev->dev); |
360 | if (!isa) |
361 | return -ENOMEM; |
362 | |
363 | isa->io = pnp_port_start(dev, bar: 0); |
364 | |
365 | return radio_isa_common_probe(isa, pdev: &dev->dev, radio_nr: drv->radio_nr_params[0], |
366 | region_size: pnp_port_len(dev, bar: 0)); |
367 | } |
368 | EXPORT_SYMBOL_GPL(radio_isa_pnp_probe); |
369 | |
370 | void radio_isa_pnp_remove(struct pnp_dev *dev) |
371 | { |
372 | struct radio_isa_card *isa = dev_get_drvdata(dev: &dev->dev); |
373 | |
374 | radio_isa_common_remove(isa, region_size: pnp_port_len(dev, bar: 0)); |
375 | } |
376 | EXPORT_SYMBOL_GPL(radio_isa_pnp_remove); |
377 | #endif |
378 | |