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