Commit | Line | Data |
---|---|---|
41b44e04 PH |
1 | /* |
2 | * Abilis Systems Single DVB-T Receiver | |
3 | * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> | |
c6ccdca9 | 4 | * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> |
41b44e04 PH |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
41b44e04 PH |
15 | */ |
16 | #include <linux/kernel.h> | |
17 | #include <linux/errno.h> | |
41b44e04 PH |
18 | #include <linux/slab.h> |
19 | #include <linux/module.h> | |
20 | #include <linux/mm.h> | |
21 | #include <linux/kref.h> | |
3288e201 | 22 | #include <linux/uaccess.h> |
41b44e04 PH |
23 | #include <linux/usb.h> |
24 | ||
b47acf2a | 25 | /* header file for usb device driver*/ |
41b44e04 | 26 | #include "as102_drv.h" |
47f79129 MCC |
27 | #include "as10x_cmd.h" |
28 | #include "as102_fe.h" | |
41b44e04 | 29 | #include "as102_fw.h" |
fada1935 | 30 | #include <media/dvbdev.h> |
41b44e04 | 31 | |
c6ccdca9 | 32 | int dual_tuner; |
41b44e04 | 33 | module_param_named(dual_tuner, dual_tuner, int, 0644); |
c6ccdca9 | 34 | MODULE_PARM_DESC(dual_tuner, "Activate Dual-Tuner config (default: off)"); |
41b44e04 PH |
35 | |
36 | static int fw_upload = 1; | |
37 | module_param_named(fw_upload, fw_upload, int, 0644); | |
38 | MODULE_PARM_DESC(fw_upload, "Turn on/off default FW upload (default: on)"); | |
39 | ||
c6ccdca9 | 40 | static int pid_filtering; |
41b44e04 PH |
41 | module_param_named(pid_filtering, pid_filtering, int, 0644); |
42 | MODULE_PARM_DESC(pid_filtering, "Activate HW PID filtering (default: off)"); | |
43 | ||
c6ccdca9 | 44 | static int ts_auto_disable; |
41b44e04 PH |
45 | module_param_named(ts_auto_disable, ts_auto_disable, int, 0644); |
46 | MODULE_PARM_DESC(ts_auto_disable, "Stream Auto Enable on FW (default: off)"); | |
47 | ||
48 | int elna_enable = 1; | |
49 | module_param_named(elna_enable, elna_enable, int, 0644); | |
50 | MODULE_PARM_DESC(elna_enable, "Activate eLNA (default: on)"); | |
51 | ||
41b44e04 | 52 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); |
41b44e04 | 53 | |
c6ccdca9 DH |
54 | static void as102_stop_stream(struct as102_dev_t *dev) |
55 | { | |
34490a0a | 56 | struct as10x_bus_adapter_t *bus_adap; |
41b44e04 PH |
57 | |
58 | if (dev != NULL) | |
59 | bus_adap = &dev->bus_adap; | |
60 | else | |
61 | return; | |
62 | ||
63 | if (bus_adap->ops->stop_stream != NULL) | |
64 | bus_adap->ops->stop_stream(dev); | |
65 | ||
66 | if (ts_auto_disable) { | |
67 | if (mutex_lock_interruptible(&dev->bus_adap.lock)) | |
68 | return; | |
69 | ||
c6ccdca9 | 70 | if (as10x_cmd_stop_streaming(bus_adap) < 0) |
2179de60 MK |
71 | dev_dbg(&dev->bus_adap.usb_dev->dev, |
72 | "as10x_cmd_stop_streaming failed\n"); | |
41b44e04 PH |
73 | |
74 | mutex_unlock(&dev->bus_adap.lock); | |
75 | } | |
76 | } | |
77 | ||
c6ccdca9 DH |
78 | static int as102_start_stream(struct as102_dev_t *dev) |
79 | { | |
34490a0a | 80 | struct as10x_bus_adapter_t *bus_adap; |
41b44e04 PH |
81 | int ret = -EFAULT; |
82 | ||
83 | if (dev != NULL) | |
84 | bus_adap = &dev->bus_adap; | |
85 | else | |
86 | return ret; | |
87 | ||
c6ccdca9 | 88 | if (bus_adap->ops->start_stream != NULL) |
41b44e04 | 89 | ret = bus_adap->ops->start_stream(dev); |
41b44e04 PH |
90 | |
91 | if (ts_auto_disable) { | |
92 | if (mutex_lock_interruptible(&dev->bus_adap.lock)) | |
93 | return -EFAULT; | |
94 | ||
95 | ret = as10x_cmd_start_streaming(bus_adap); | |
96 | ||
97 | mutex_unlock(&dev->bus_adap.lock); | |
98 | } | |
99 | ||
100 | return ret; | |
101 | } | |
102 | ||
103 | static int as10x_pid_filter(struct as102_dev_t *dev, | |
104 | int index, u16 pid, int onoff) { | |
105 | ||
34490a0a | 106 | struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap; |
41b44e04 PH |
107 | int ret = -EFAULT; |
108 | ||
41b44e04 | 109 | if (mutex_lock_interruptible(&dev->bus_adap.lock)) { |
2179de60 MK |
110 | dev_dbg(&dev->bus_adap.usb_dev->dev, |
111 | "amutex_lock_interruptible(lock) failed !\n"); | |
41b44e04 PH |
112 | return -EBUSY; |
113 | } | |
114 | ||
c6ccdca9 DH |
115 | switch (onoff) { |
116 | case 0: | |
14e0e4bf | 117 | ret = as10x_cmd_del_PID_filter(bus_adap, (uint16_t) pid); |
2179de60 MK |
118 | dev_dbg(&dev->bus_adap.usb_dev->dev, |
119 | "DEL_PID_FILTER([%02d] 0x%04x) ret = %d\n", | |
14e0e4bf SN |
120 | index, pid, ret); |
121 | break; | |
c6ccdca9 DH |
122 | case 1: |
123 | { | |
14e0e4bf | 124 | struct as10x_ts_filter filter; |
c6ccdca9 | 125 | |
14e0e4bf SN |
126 | filter.type = TS_PID_TYPE_TS; |
127 | filter.idx = 0xFF; | |
128 | filter.pid = pid; | |
c6ccdca9 | 129 | |
14e0e4bf | 130 | ret = as10x_cmd_add_PID_filter(bus_adap, &filter); |
2179de60 | 131 | dev_dbg(&dev->bus_adap.usb_dev->dev, |
f52e9828 | 132 | "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n", |
14e0e4bf SN |
133 | index, filter.idx, filter.pid, ret); |
134 | break; | |
c6ccdca9 | 135 | } |
41b44e04 PH |
136 | } |
137 | ||
138 | mutex_unlock(&dev->bus_adap.lock); | |
41b44e04 PH |
139 | return ret; |
140 | } | |
141 | ||
c6ccdca9 DH |
142 | static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed) |
143 | { | |
41b44e04 PH |
144 | int ret = 0; |
145 | struct dvb_demux *demux = dvbdmxfeed->demux; | |
146 | struct as102_dev_t *as102_dev = demux->priv; | |
147 | ||
41b44e04 PH |
148 | if (mutex_lock_interruptible(&as102_dev->sem)) |
149 | return -ERESTARTSYS; | |
150 | ||
14e0e4bf SN |
151 | if (pid_filtering) |
152 | as10x_pid_filter(as102_dev, dvbdmxfeed->index, | |
153 | dvbdmxfeed->pid, 1); | |
41b44e04 | 154 | |
c6ccdca9 | 155 | if (as102_dev->streaming++ == 0) |
41b44e04 | 156 | ret = as102_start_stream(as102_dev); |
41b44e04 PH |
157 | |
158 | mutex_unlock(&as102_dev->sem); | |
41b44e04 PH |
159 | return ret; |
160 | } | |
161 | ||
c6ccdca9 DH |
162 | static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) |
163 | { | |
41b44e04 PH |
164 | struct dvb_demux *demux = dvbdmxfeed->demux; |
165 | struct as102_dev_t *as102_dev = demux->priv; | |
166 | ||
41b44e04 PH |
167 | if (mutex_lock_interruptible(&as102_dev->sem)) |
168 | return -ERESTARTSYS; | |
169 | ||
c6ccdca9 | 170 | if (--as102_dev->streaming == 0) |
41b44e04 | 171 | as102_stop_stream(as102_dev); |
41b44e04 | 172 | |
14e0e4bf SN |
173 | if (pid_filtering) |
174 | as10x_pid_filter(as102_dev, dvbdmxfeed->index, | |
175 | dvbdmxfeed->pid, 0); | |
41b44e04 PH |
176 | |
177 | mutex_unlock(&as102_dev->sem); | |
41b44e04 PH |
178 | return 0; |
179 | } | |
41b44e04 | 180 | |
47f79129 MCC |
181 | static int as102_set_tune(void *priv, struct as10x_tune_args *tune_args) |
182 | { | |
183 | struct as10x_bus_adapter_t *bus_adap = priv; | |
184 | int ret; | |
185 | ||
186 | /* Set frontend arguments */ | |
187 | if (mutex_lock_interruptible(&bus_adap->lock)) | |
188 | return -EBUSY; | |
189 | ||
190 | ret = as10x_cmd_set_tune(bus_adap, tune_args); | |
191 | if (ret != 0) | |
192 | dev_dbg(&bus_adap->usb_dev->dev, | |
193 | "as10x_cmd_set_tune failed. (err = %d)\n", ret); | |
194 | ||
195 | mutex_unlock(&bus_adap->lock); | |
196 | ||
197 | return ret; | |
198 | } | |
199 | ||
200 | static int as102_get_tps(void *priv, struct as10x_tps *tps) | |
201 | { | |
202 | struct as10x_bus_adapter_t *bus_adap = priv; | |
203 | int ret; | |
204 | ||
205 | if (mutex_lock_interruptible(&bus_adap->lock)) | |
206 | return -EBUSY; | |
207 | ||
208 | /* send abilis command: GET_TPS */ | |
209 | ret = as10x_cmd_get_tps(bus_adap, tps); | |
210 | ||
211 | mutex_unlock(&bus_adap->lock); | |
212 | ||
213 | return ret; | |
214 | } | |
215 | ||
216 | static int as102_get_status(void *priv, struct as10x_tune_status *tstate) | |
217 | { | |
218 | struct as10x_bus_adapter_t *bus_adap = priv; | |
219 | int ret; | |
220 | ||
221 | if (mutex_lock_interruptible(&bus_adap->lock)) | |
222 | return -EBUSY; | |
223 | ||
224 | /* send abilis command: GET_TUNE_STATUS */ | |
225 | ret = as10x_cmd_get_tune_status(bus_adap, tstate); | |
226 | if (ret < 0) { | |
227 | dev_dbg(&bus_adap->usb_dev->dev, | |
228 | "as10x_cmd_get_tune_status failed (err = %d)\n", | |
229 | ret); | |
230 | } | |
231 | ||
232 | mutex_unlock(&bus_adap->lock); | |
233 | ||
234 | return ret; | |
235 | } | |
236 | ||
237 | static int as102_get_stats(void *priv, struct as10x_demod_stats *demod_stats) | |
238 | { | |
239 | struct as10x_bus_adapter_t *bus_adap = priv; | |
240 | int ret; | |
241 | ||
242 | if (mutex_lock_interruptible(&bus_adap->lock)) | |
243 | return -EBUSY; | |
244 | ||
245 | /* send abilis command: GET_TUNE_STATUS */ | |
246 | ret = as10x_cmd_get_demod_stats(bus_adap, demod_stats); | |
247 | if (ret < 0) { | |
248 | dev_dbg(&bus_adap->usb_dev->dev, | |
249 | "as10x_cmd_get_demod_stats failed (probably not tuned)\n"); | |
250 | } else { | |
251 | dev_dbg(&bus_adap->usb_dev->dev, | |
252 | "demod status: fc: 0x%08x, bad fc: 0x%08x, bytes corrected: 0x%08x , MER: 0x%04x\n", | |
253 | demod_stats->frame_count, | |
254 | demod_stats->bad_frame_count, | |
255 | demod_stats->bytes_fixed_by_rs, | |
256 | demod_stats->mer); | |
257 | } | |
258 | mutex_unlock(&bus_adap->lock); | |
259 | ||
260 | return ret; | |
261 | } | |
262 | ||
263 | static int as102_stream_ctrl(void *priv, int acquire, uint32_t elna_cfg) | |
264 | { | |
265 | struct as10x_bus_adapter_t *bus_adap = priv; | |
266 | int ret; | |
267 | ||
268 | if (mutex_lock_interruptible(&bus_adap->lock)) | |
269 | return -EBUSY; | |
270 | ||
271 | if (acquire) { | |
272 | if (elna_enable) | |
273 | as10x_cmd_set_context(bus_adap, | |
274 | CONTEXT_LNA, elna_cfg); | |
275 | ||
276 | ret = as10x_cmd_turn_on(bus_adap); | |
277 | } else { | |
278 | ret = as10x_cmd_turn_off(bus_adap); | |
279 | } | |
280 | ||
281 | mutex_unlock(&bus_adap->lock); | |
282 | ||
283 | return ret; | |
284 | } | |
285 | ||
286 | static const struct as102_fe_ops as102_fe_ops = { | |
287 | .set_tune = as102_set_tune, | |
288 | .get_tps = as102_get_tps, | |
289 | .get_status = as102_get_status, | |
290 | .get_stats = as102_get_stats, | |
291 | .stream_ctrl = as102_stream_ctrl, | |
292 | }; | |
293 | ||
c6ccdca9 DH |
294 | int as102_dvb_register(struct as102_dev_t *as102_dev) |
295 | { | |
88010289 SN |
296 | struct device *dev = &as102_dev->bus_adap.usb_dev->dev; |
297 | int ret; | |
41b44e04 | 298 | |
41b44e04 | 299 | ret = dvb_register_adapter(&as102_dev->dvb_adap, |
88010289 SN |
300 | as102_dev->name, THIS_MODULE, |
301 | dev, adapter_nr); | |
41b44e04 | 302 | if (ret < 0) { |
88010289 SN |
303 | dev_err(dev, "%s: dvb_register_adapter() failed: %d\n", |
304 | __func__, ret); | |
305 | return ret; | |
41b44e04 PH |
306 | } |
307 | ||
308 | as102_dev->dvb_dmx.priv = as102_dev; | |
309 | as102_dev->dvb_dmx.filternum = pid_filtering ? 16 : 256; | |
310 | as102_dev->dvb_dmx.feednum = 256; | |
311 | as102_dev->dvb_dmx.start_feed = as102_dvb_dmx_start_feed; | |
312 | as102_dev->dvb_dmx.stop_feed = as102_dvb_dmx_stop_feed; | |
313 | ||
314 | as102_dev->dvb_dmx.dmx.capabilities = DMX_TS_FILTERING | | |
315 | DMX_SECTION_FILTERING; | |
316 | ||
317 | as102_dev->dvb_dmxdev.filternum = as102_dev->dvb_dmx.filternum; | |
318 | as102_dev->dvb_dmxdev.demux = &as102_dev->dvb_dmx.dmx; | |
319 | as102_dev->dvb_dmxdev.capabilities = 0; | |
320 | ||
c6ccdca9 DH |
321 | ret = dvb_dmx_init(&as102_dev->dvb_dmx); |
322 | if (ret < 0) { | |
1ec9a35e | 323 | dev_err(dev, "%s: dvb_dmx_init() failed: %d\n", __func__, ret); |
88010289 | 324 | goto edmxinit; |
41b44e04 PH |
325 | } |
326 | ||
327 | ret = dvb_dmxdev_init(&as102_dev->dvb_dmxdev, &as102_dev->dvb_adap); | |
328 | if (ret < 0) { | |
88010289 SN |
329 | dev_err(dev, "%s: dvb_dmxdev_init() failed: %d\n", |
330 | __func__, ret); | |
331 | goto edmxdinit; | |
41b44e04 PH |
332 | } |
333 | ||
b601d9a5 MCC |
334 | /* Attach the frontend */ |
335 | as102_dev->dvb_fe = dvb_attach(as102_attach, as102_dev->name, | |
47f79129 | 336 | &as102_fe_ops, |
b601d9a5 MCC |
337 | &as102_dev->bus_adap, |
338 | as102_dev->elna_cfg); | |
339 | if (!as102_dev->dvb_fe) { | |
bfe40b79 | 340 | ret = -ENODEV; |
b601d9a5 MCC |
341 | dev_err(dev, "%s: as102_attach() failed: %d", |
342 | __func__, ret); | |
343 | goto efereg; | |
344 | } | |
345 | ||
346 | ret = dvb_register_frontend(&as102_dev->dvb_adap, as102_dev->dvb_fe); | |
41b44e04 | 347 | if (ret < 0) { |
88010289 | 348 | dev_err(dev, "%s: as102_dvb_register_frontend() failed: %d", |
c6ccdca9 | 349 | __func__, ret); |
88010289 | 350 | goto efereg; |
41b44e04 | 351 | } |
41b44e04 PH |
352 | |
353 | /* init bus mutex for token locking */ | |
354 | mutex_init(&as102_dev->bus_adap.lock); | |
355 | ||
356 | /* init start / stop stream mutex */ | |
357 | mutex_init(&as102_dev->sem); | |
358 | ||
41b44e04 PH |
359 | /* |
360 | * try to load as102 firmware. If firmware upload failed, we'll be | |
361 | * able to upload it later. | |
362 | */ | |
363 | if (fw_upload) | |
364 | try_then_request_module(as102_fw_upload(&as102_dev->bus_adap), | |
365 | "firmware_class"); | |
88010289 SN |
366 | |
367 | pr_info("Registered device %s", as102_dev->name); | |
368 | return 0; | |
369 | ||
370 | efereg: | |
371 | dvb_dmxdev_release(&as102_dev->dvb_dmxdev); | |
372 | edmxdinit: | |
373 | dvb_dmx_release(&as102_dev->dvb_dmx); | |
374 | edmxinit: | |
375 | dvb_unregister_adapter(&as102_dev->dvb_adap); | |
41b44e04 PH |
376 | return ret; |
377 | } | |
378 | ||
c6ccdca9 DH |
379 | void as102_dvb_unregister(struct as102_dev_t *as102_dev) |
380 | { | |
41b44e04 | 381 | /* unregister as102 frontend */ |
b601d9a5 MCC |
382 | dvb_unregister_frontend(as102_dev->dvb_fe); |
383 | ||
384 | /* detach frontend */ | |
385 | dvb_frontend_detach(as102_dev->dvb_fe); | |
41b44e04 PH |
386 | |
387 | /* unregister demux device */ | |
388 | dvb_dmxdev_release(&as102_dev->dvb_dmxdev); | |
389 | dvb_dmx_release(&as102_dev->dvb_dmx); | |
390 | ||
391 | /* unregister dvb adapter */ | |
392 | dvb_unregister_adapter(&as102_dev->dvb_adap); | |
ff7029f5 | 393 | |
88010289 | 394 | pr_info("Unregistered device %s", as102_dev->name); |
41b44e04 PH |
395 | } |
396 | ||
9451df0e | 397 | module_usb_driver(as102_usb_driver); |
41b44e04 | 398 | |
41b44e04 PH |
399 | /* modinfo details */ |
400 | MODULE_DESCRIPTION(DRIVER_FULL_NAME); | |
401 | MODULE_LICENSE("GPL"); | |
402 | MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>"); |