diff options
Diffstat (limited to 'drivers/media/usb/dvb-usb-v2/dvb_usb_core.c')
-rw-r--r-- | drivers/media/usb/dvb-usb-v2/dvb_usb_core.c | 1126 |
1 files changed, 1126 insertions, 0 deletions
diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c new file mode 100644 index 0000000000..f1c79f351e --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c @@ -0,0 +1,1126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DVB USB framework + * + * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@posteo.de> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + */ + +#include "dvb_usb_common.h" +#include <media/media-device.h> + +static int dvb_usbv2_disable_rc_polling; +module_param_named(disable_rc_polling, dvb_usbv2_disable_rc_polling, int, 0644); +MODULE_PARM_DESC(disable_rc_polling, + "disable remote control polling (default: 0)"); +static int dvb_usb_force_pid_filter_usage; +module_param_named(force_pid_filter_usage, dvb_usb_force_pid_filter_usage, + int, 0444); +MODULE_PARM_DESC(force_pid_filter_usage, + "force all DVB USB devices to use a PID filter, if any (default: 0)"); + +static int dvb_usbv2_download_firmware(struct dvb_usb_device *d, + const char *name) +{ + int ret; + const struct firmware *fw; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (!d->props->download_firmware) { + ret = -EINVAL; + goto err; + } + + ret = request_firmware(&fw, name, &d->udev->dev); + if (ret < 0) { + dev_err(&d->udev->dev, + "%s: Did not find the firmware file '%s' (status %d). You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware\n", + KBUILD_MODNAME, name, ret); + goto err; + } + + dev_info(&d->udev->dev, "%s: downloading firmware from file '%s'\n", + KBUILD_MODNAME, name); + + ret = d->props->download_firmware(d, fw); + release_firmware(fw); + if (ret < 0) + goto err; + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_i2c_init(struct dvb_usb_device *d) +{ + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (!d->props->i2c_algo) + return 0; + + strscpy(d->i2c_adap.name, d->name, sizeof(d->i2c_adap.name)); + d->i2c_adap.algo = d->props->i2c_algo; + d->i2c_adap.dev.parent = &d->udev->dev; + i2c_set_adapdata(&d->i2c_adap, d); + + ret = i2c_add_adapter(&d->i2c_adap); + if (ret < 0) { + d->i2c_adap.algo = NULL; + goto err; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_i2c_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (d->i2c_adap.algo) + i2c_del_adapter(&d->i2c_adap); + + return 0; +} + +#if IS_ENABLED(CONFIG_RC_CORE) +static void dvb_usb_read_remote_control(struct work_struct *work) +{ + struct dvb_usb_device *d = container_of(work, + struct dvb_usb_device, rc_query_work.work); + int ret; + + /* + * When the parameter has been set to 1 via sysfs while the + * driver was running, or when bulk mode is enabled after IR init. + */ + if (dvb_usbv2_disable_rc_polling || d->rc.bulk_mode) { + d->rc_polling_active = false; + return; + } + + ret = d->rc.query(d); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: rc.query() failed=%d\n", + KBUILD_MODNAME, ret); + d->rc_polling_active = false; + return; /* stop polling */ + } + + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); +} + +static int dvb_usbv2_remote_init(struct dvb_usb_device *d) +{ + int ret; + struct rc_dev *dev; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (dvb_usbv2_disable_rc_polling || !d->props->get_rc_config) + return 0; + + d->rc.map_name = d->rc_map; + ret = d->props->get_rc_config(d, &d->rc); + if (ret < 0) + goto err; + + /* disable rc when there is no keymap defined */ + if (!d->rc.map_name) + return 0; + + dev = rc_allocate_device(d->rc.driver_type); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + dev->dev.parent = &d->udev->dev; + dev->device_name = d->name; + usb_make_path(d->udev, d->rc_phys, sizeof(d->rc_phys)); + strlcat(d->rc_phys, "/ir0", sizeof(d->rc_phys)); + dev->input_phys = d->rc_phys; + usb_to_input_id(d->udev, &dev->input_id); + dev->driver_name = d->props->driver_name; + dev->map_name = d->rc.map_name; + dev->allowed_protocols = d->rc.allowed_protos; + dev->change_protocol = d->rc.change_protocol; + dev->timeout = d->rc.timeout; + dev->priv = d; + + ret = rc_register_device(dev); + if (ret < 0) { + rc_free_device(dev); + goto err; + } + + d->rc_dev = dev; + + /* start polling if needed */ + if (d->rc.query && !d->rc.bulk_mode) { + /* initialize a work queue for handling polling */ + INIT_DELAYED_WORK(&d->rc_query_work, + dvb_usb_read_remote_control); + dev_info(&d->udev->dev, + "%s: schedule remote query interval to %d msecs\n", + KBUILD_MODNAME, d->rc.interval); + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); + d->rc_polling_active = true; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_remote_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (d->rc_dev) { + cancel_delayed_work_sync(&d->rc_query_work); + rc_unregister_device(d->rc_dev); + d->rc_dev = NULL; + } + + return 0; +} +#else + #define dvb_usbv2_remote_init(args...) 0 + #define dvb_usbv2_remote_exit(args...) +#endif + +static void dvb_usb_data_complete(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter(&adap->demux, buf, len); +} + +static void dvb_usb_data_complete_204(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter_204(&adap->demux, buf, len); +} + +static void dvb_usb_data_complete_raw(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter_raw(&adap->demux, buf, len); +} + +static int dvb_usbv2_adapter_stream_init(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + adap->stream.udev = adap_to_d(adap)->udev; + adap->stream.user_priv = adap; + adap->stream.complete = dvb_usb_data_complete; + + return usb_urb_initv2(&adap->stream, &adap->props->stream); +} + +static int dvb_usbv2_adapter_stream_exit(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + return usb_urb_exitv2(&adap->stream); +} + +static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv; + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + struct usb_data_stream_properties stream_props; + dev_dbg(&d->udev->dev, + "%s: adap=%d active_fe=%d feed_type=%d setting pid [%s]: %04x (%04d) at index %d\n", + __func__, adap->id, adap->active_fe, dvbdmxfeed->type, + adap->pid_filtering ? "yes" : "no", dvbdmxfeed->pid, + dvbdmxfeed->pid, dvbdmxfeed->index); + + /* wait init is done */ + wait_on_bit(&adap->state_bits, ADAP_INIT, TASK_UNINTERRUPTIBLE); + + if (adap->active_fe == -1) + return -EINVAL; + + /* skip feed setup if we are already feeding */ + if (adap->feed_count++ > 0) + goto skip_feed_start; + + /* set 'streaming' status bit */ + set_bit(ADAP_STREAMING, &adap->state_bits); + + /* resolve input and output streaming parameters */ + if (d->props->get_stream_config) { + memcpy(&stream_props, &adap->props->stream, + sizeof(struct usb_data_stream_properties)); + ret = d->props->get_stream_config(adap->fe[adap->active_fe], + &adap->ts_type, &stream_props); + if (ret) + dev_err(&d->udev->dev, + "%s: get_stream_config() failed=%d\n", + KBUILD_MODNAME, ret); + } else { + stream_props = adap->props->stream; + } + + switch (adap->ts_type) { + case DVB_USB_FE_TS_TYPE_204: + adap->stream.complete = dvb_usb_data_complete_204; + break; + case DVB_USB_FE_TS_TYPE_RAW: + adap->stream.complete = dvb_usb_data_complete_raw; + break; + case DVB_USB_FE_TS_TYPE_188: + default: + adap->stream.complete = dvb_usb_data_complete; + break; + } + + /* submit USB streaming packets */ + usb_urb_submitv2(&adap->stream, &stream_props); + + /* enable HW PID filter */ + if (adap->pid_filtering && adap->props->pid_filter_ctrl) { + ret = adap->props->pid_filter_ctrl(adap, 1); + if (ret) + dev_err(&d->udev->dev, + "%s: pid_filter_ctrl() failed=%d\n", + KBUILD_MODNAME, ret); + } + + /* ask device to start streaming */ + if (d->props->streaming_ctrl) { + ret = d->props->streaming_ctrl(adap->fe[adap->active_fe], 1); + if (ret) + dev_err(&d->udev->dev, + "%s: streaming_ctrl() failed=%d\n", + KBUILD_MODNAME, ret); + } +skip_feed_start: + + /* add PID to device HW PID filter */ + if (adap->pid_filtering && adap->props->pid_filter) { + ret = adap->props->pid_filter(adap, dvbdmxfeed->index, + dvbdmxfeed->pid, 1); + if (ret) + dev_err(&d->udev->dev, "%s: pid_filter() failed=%d\n", + KBUILD_MODNAME, ret); + } + + if (ret) + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv; + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + dev_dbg(&d->udev->dev, + "%s: adap=%d active_fe=%d feed_type=%d setting pid [%s]: %04x (%04d) at index %d\n", + __func__, adap->id, adap->active_fe, dvbdmxfeed->type, + adap->pid_filtering ? "yes" : "no", dvbdmxfeed->pid, + dvbdmxfeed->pid, dvbdmxfeed->index); + + if (adap->active_fe == -1) + return -EINVAL; + + /* remove PID from device HW PID filter */ + if (adap->pid_filtering && adap->props->pid_filter) { + ret = adap->props->pid_filter(adap, dvbdmxfeed->index, + dvbdmxfeed->pid, 0); + if (ret) + dev_err(&d->udev->dev, "%s: pid_filter() failed=%d\n", + KBUILD_MODNAME, ret); + } + + /* we cannot stop streaming until last PID is removed */ + if (--adap->feed_count > 0) + goto skip_feed_stop; + + /* ask device to stop streaming */ + if (d->props->streaming_ctrl) { + ret = d->props->streaming_ctrl(adap->fe[adap->active_fe], 0); + if (ret) + dev_err(&d->udev->dev, + "%s: streaming_ctrl() failed=%d\n", + KBUILD_MODNAME, ret); + } + + /* disable HW PID filter */ + if (adap->pid_filtering && adap->props->pid_filter_ctrl) { + ret = adap->props->pid_filter_ctrl(adap, 0); + if (ret) + dev_err(&d->udev->dev, + "%s: pid_filter_ctrl() failed=%d\n", + KBUILD_MODNAME, ret); + } + + /* kill USB streaming packets */ + usb_urb_killv2(&adap->stream); + + /* clear 'streaming' status bit */ + clear_bit(ADAP_STREAMING, &adap->state_bits); + smp_mb__after_atomic(); + wake_up_bit(&adap->state_bits, ADAP_STREAMING); +skip_feed_stop: + + if (ret) + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_media_device_init(struct dvb_usb_adapter *adap) +{ +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + struct media_device *mdev; + struct dvb_usb_device *d = adap_to_d(adap); + struct usb_device *udev = d->udev; + + mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + media_device_usb_init(mdev, udev, d->name); + + dvb_register_media_controller(&adap->dvb_adap, mdev); + + dev_info(&d->udev->dev, "media controller created\n"); +#endif + return 0; +} + +static int dvb_usbv2_media_device_register(struct dvb_usb_adapter *adap) +{ +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + return media_device_register(adap->dvb_adap.mdev); +#else + return 0; +#endif +} + +static void dvb_usbv2_media_device_unregister(struct dvb_usb_adapter *adap) +{ +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + + if (!adap->dvb_adap.mdev) + return; + + media_device_unregister(adap->dvb_adap.mdev); + media_device_cleanup(adap->dvb_adap.mdev); + kfree(adap->dvb_adap.mdev); + adap->dvb_adap.mdev = NULL; + +#endif +} + +static int dvb_usbv2_adapter_dvb_init(struct dvb_usb_adapter *adap) +{ + int ret; + struct dvb_usb_device *d = adap_to_d(adap); + + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); + + ret = dvb_register_adapter(&adap->dvb_adap, d->name, d->props->owner, + &d->udev->dev, d->props->adapter_nr); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: dvb_register_adapter() failed=%d\n", + __func__, ret); + goto err_dvb_register_adapter; + } + + adap->dvb_adap.priv = adap; + + ret = dvb_usbv2_media_device_init(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: dvb_usbv2_media_device_init() failed=%d\n", + __func__, ret); + goto err_dvb_register_mc; + } + + if (d->props->read_mac_address) { + ret = d->props->read_mac_address(adap, + adap->dvb_adap.proposed_mac); + if (ret < 0) + goto err_dvb_dmx_init; + + dev_info(&d->udev->dev, "%s: MAC address: %pM\n", + KBUILD_MODNAME, adap->dvb_adap.proposed_mac); + } + + adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + adap->demux.priv = adap; + adap->demux.filternum = 0; + adap->demux.filternum = adap->max_feed_count; + adap->demux.feednum = adap->demux.filternum; + adap->demux.start_feed = dvb_usb_start_feed; + adap->demux.stop_feed = dvb_usb_stop_feed; + adap->demux.write_to_decoder = NULL; + ret = dvb_dmx_init(&adap->demux); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_dmx_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_dmx_init; + } + + adap->dmxdev.filternum = adap->demux.filternum; + adap->dmxdev.demux = &adap->demux.dmx; + adap->dmxdev.capabilities = 0; + ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_dmxdev_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_dmxdev_init; + } + + ret = dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_net_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_net_init; + } + + return 0; +err_dvb_net_init: + dvb_dmxdev_release(&adap->dmxdev); +err_dvb_dmxdev_init: + dvb_dmx_release(&adap->demux); +err_dvb_dmx_init: + dvb_usbv2_media_device_unregister(adap); +err_dvb_register_mc: + dvb_unregister_adapter(&adap->dvb_adap); +err_dvb_register_adapter: + adap->dvb_adap.priv = NULL; + return ret; +} + +static int dvb_usbv2_adapter_dvb_exit(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + if (adap->dvb_adap.priv) { + dvb_net_release(&adap->dvb_net); + adap->demux.dmx.close(&adap->demux.dmx); + dvb_dmxdev_release(&adap->dmxdev); + dvb_dmx_release(&adap->demux); + dvb_unregister_adapter(&adap->dvb_adap); + } + + return 0; +} + +static int dvb_usbv2_device_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + + if (onoff) + d->powered++; + else + d->powered--; + + if (d->powered == 0 || (onoff && d->powered == 1)) { + /* when switching from 1 to 0 or from 0 to 1 */ + dev_dbg(&d->udev->dev, "%s: power=%d\n", __func__, onoff); + if (d->props->power_ctrl) { + ret = d->props->power_ctrl(d, onoff); + if (ret < 0) + goto err; + } + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_fe_init(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id, + fe->id); + + if (!adap->suspend_resume_active) { + adap->active_fe = fe->id; + set_bit(ADAP_INIT, &adap->state_bits); + } + + ret = dvb_usbv2_device_power_ctrl(d, 1); + if (ret < 0) + goto err; + + if (d->props->frontend_ctrl) { + ret = d->props->frontend_ctrl(fe, 1); + if (ret < 0) + goto err; + } + + if (adap->fe_init[fe->id]) { + ret = adap->fe_init[fe->id](fe); + if (ret < 0) + goto err; + } +err: + if (!adap->suspend_resume_active) { + clear_bit(ADAP_INIT, &adap->state_bits); + smp_mb__after_atomic(); + wake_up_bit(&adap->state_bits, ADAP_INIT); + } + + dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_fe_sleep(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id, + fe->id); + + if (!adap->suspend_resume_active) { + set_bit(ADAP_SLEEP, &adap->state_bits); + wait_on_bit(&adap->state_bits, ADAP_STREAMING, + TASK_UNINTERRUPTIBLE); + } + + if (adap->fe_sleep[fe->id]) { + ret = adap->fe_sleep[fe->id](fe); + if (ret < 0) + goto err; + } + + if (d->props->frontend_ctrl) { + ret = d->props->frontend_ctrl(fe, 0); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_device_power_ctrl(d, 0); + +err: + if (!adap->suspend_resume_active) { + adap->active_fe = -1; + clear_bit(ADAP_SLEEP, &adap->state_bits); + smp_mb__after_atomic(); + wake_up_bit(&adap->state_bits, ADAP_SLEEP); + } + + dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_adapter_frontend_init(struct dvb_usb_adapter *adap) +{ + int ret, i, count_registered = 0; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); + + memset(adap->fe, 0, sizeof(adap->fe)); + adap->active_fe = -1; + + if (d->props->frontend_attach) { + ret = d->props->frontend_attach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, + "%s: frontend_attach() failed=%d\n", + __func__, ret); + goto err_dvb_frontend_detach; + } + } else { + dev_dbg(&d->udev->dev, "%s: frontend_attach() do not exists\n", + __func__); + ret = 0; + goto err; + } + + for (i = 0; i < MAX_NO_OF_FE_PER_ADAP && adap->fe[i]; i++) { + adap->fe[i]->id = i; + /* re-assign sleep and wakeup functions */ + adap->fe_init[i] = adap->fe[i]->ops.init; + adap->fe[i]->ops.init = dvb_usb_fe_init; + adap->fe_sleep[i] = adap->fe[i]->ops.sleep; + adap->fe[i]->ops.sleep = dvb_usb_fe_sleep; + + ret = dvb_register_frontend(&adap->dvb_adap, adap->fe[i]); + if (ret < 0) { + dev_err(&d->udev->dev, + "%s: frontend%d registration failed\n", + KBUILD_MODNAME, i); + goto err_dvb_unregister_frontend; + } + + count_registered++; + } + + if (d->props->tuner_attach) { + ret = d->props->tuner_attach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: tuner_attach() failed=%d\n", + __func__, ret); + goto err_dvb_unregister_frontend; + } + } + + ret = dvb_create_media_graph(&adap->dvb_adap, true); + if (ret < 0) + goto err_dvb_unregister_frontend; + + ret = dvb_usbv2_media_device_register(adap); + + return ret; + +err_dvb_unregister_frontend: + for (i = count_registered - 1; i >= 0; i--) + dvb_unregister_frontend(adap->fe[i]); + +err_dvb_frontend_detach: + for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) { + if (adap->fe[i]) { + dvb_frontend_detach(adap->fe[i]); + adap->fe[i] = NULL; + } + } + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_adapter_frontend_exit(struct dvb_usb_adapter *adap) +{ + int ret, i; + struct dvb_usb_device *d = adap_to_d(adap); + + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); + + for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) { + if (adap->fe[i]) { + dvb_unregister_frontend(adap->fe[i]); + dvb_frontend_detach(adap->fe[i]); + } + } + + if (d->props->tuner_detach) { + ret = d->props->tuner_detach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: tuner_detach() failed=%d\n", + __func__, ret); + } + } + + if (d->props->frontend_detach) { + ret = d->props->frontend_detach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, + "%s: frontend_detach() failed=%d\n", + __func__, ret); + } + } + + return 0; +} + +static int dvb_usbv2_adapter_init(struct dvb_usb_device *d) +{ + struct dvb_usb_adapter *adap; + int ret, i, adapter_count; + + /* resolve adapter count */ + adapter_count = d->props->num_adapters; + if (d->props->get_adapter_count) { + ret = d->props->get_adapter_count(d); + if (ret < 0) + goto err; + + adapter_count = ret; + } + + for (i = 0; i < adapter_count; i++) { + adap = &d->adapter[i]; + adap->id = i; + adap->props = &d->props->adapter[i]; + + /* speed - when running at FULL speed we need a HW PID filter */ + if (d->udev->speed == USB_SPEED_FULL && + !(adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER)) { + dev_err(&d->udev->dev, + "%s: this USB2.0 device cannot be run on a USB1.1 port (it lacks a hardware PID filter)\n", + KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } else if ((d->udev->speed == USB_SPEED_FULL && + adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) || + (adap->props->caps & DVB_USB_ADAP_NEED_PID_FILTERING)) { + dev_info(&d->udev->dev, + "%s: will use the device's hardware PID filter (table count: %d)\n", + KBUILD_MODNAME, + adap->props->pid_filter_count); + adap->pid_filtering = 1; + adap->max_feed_count = adap->props->pid_filter_count; + } else { + dev_info(&d->udev->dev, + "%s: will pass the complete MPEG2 transport stream to the software demuxer\n", + KBUILD_MODNAME); + adap->pid_filtering = 0; + adap->max_feed_count = 255; + } + + if (!adap->pid_filtering && dvb_usb_force_pid_filter_usage && + adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) { + dev_info(&d->udev->dev, + "%s: PID filter enabled by module option\n", + KBUILD_MODNAME); + adap->pid_filtering = 1; + adap->max_feed_count = adap->props->pid_filter_count; + } + + ret = dvb_usbv2_adapter_stream_init(adap); + if (ret) + goto err; + + ret = dvb_usbv2_adapter_dvb_init(adap); + if (ret) + goto err; + + ret = dvb_usbv2_adapter_frontend_init(adap); + if (ret) + goto err; + + /* use exclusive FE lock if there is multiple shared FEs */ + if (adap->fe[1]) + adap->dvb_adap.mfe_shared = 1; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_adapter_exit(struct dvb_usb_device *d) +{ + int i; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) { + if (d->adapter[i].props) { + dvb_usbv2_adapter_dvb_exit(&d->adapter[i]); + dvb_usbv2_adapter_stream_exit(&d->adapter[i]); + dvb_usbv2_adapter_frontend_exit(&d->adapter[i]); + dvb_usbv2_media_device_unregister(&d->adapter[i]); + } + } + + return 0; +} + +/* general initialization functions */ +static int dvb_usbv2_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_remote_exit(d); + dvb_usbv2_adapter_exit(d); + dvb_usbv2_i2c_exit(d); + + return 0; +} + +static int dvb_usbv2_init(struct dvb_usb_device *d) +{ + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_device_power_ctrl(d, 1); + + if (d->props->read_config) { + ret = d->props->read_config(d); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_i2c_init(d); + if (ret < 0) + goto err; + + ret = dvb_usbv2_adapter_init(d); + if (ret < 0) + goto err; + + if (d->props->init) { + ret = d->props->init(d); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_remote_init(d); + if (ret < 0) + goto err; + + dvb_usbv2_device_power_ctrl(d, 0); + + return 0; +err: + dvb_usbv2_device_power_ctrl(d, 0); + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int dvb_usbv2_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct dvb_usb_device *d; + struct usb_device *udev = interface_to_usbdev(intf); + struct dvb_usb_driver_info *driver_info = + (struct dvb_usb_driver_info *) id->driver_info; + + dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (!id->driver_info) { + dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); + if (!d) { + dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + ret = -ENOMEM; + goto err; + } + + d->intf = intf; + d->name = driver_info->name; + d->rc_map = driver_info->rc_map; + d->udev = udev; + d->props = driver_info->props; + + if (intf->cur_altsetting->desc.bInterfaceNumber != + d->props->bInterfaceNumber) { + ret = -ENODEV; + goto err_kfree_d; + } + + mutex_init(&d->usb_mutex); + mutex_init(&d->i2c_mutex); + + if (d->props->size_of_priv) { + d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL); + if (!d->priv) { + dev_err(&d->udev->dev, "%s: kzalloc() failed\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto err_kfree_d; + } + } + + if (d->props->probe) { + ret = d->props->probe(d); + if (ret) + goto err_kfree_priv; + } + + if (d->props->identify_state) { + const char *name = NULL; + ret = d->props->identify_state(d, &name); + if (ret == COLD) { + dev_info(&d->udev->dev, + "%s: found a '%s' in cold state\n", + KBUILD_MODNAME, d->name); + + if (!name) + name = d->props->firmware; + + ret = dvb_usbv2_download_firmware(d, name); + if (ret == 0) { + /* device is warm, continue initialization */ + ; + } else if (ret == RECONNECTS_USB) { + /* + * USB core will call disconnect() and then + * probe() as device reconnects itself from the + * USB bus. disconnect() will release all driver + * resources and probe() is called for 'new' + * device. As 'new' device is warm we should + * never go here again. + */ + goto exit; + } else { + goto err_free_all; + } + } else if (ret != WARM) { + goto err_free_all; + } + } + + dev_info(&d->udev->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, d->name); + + ret = dvb_usbv2_init(d); + if (ret < 0) + goto err_free_all; + + dev_info(&d->udev->dev, + "%s: '%s' successfully initialized and connected\n", + KBUILD_MODNAME, d->name); +exit: + usb_set_intfdata(intf, d); + + return 0; +err_free_all: + dvb_usbv2_exit(d); + if (d->props->disconnect) + d->props->disconnect(d); +err_kfree_priv: + kfree(d->priv); +err_kfree_d: + kfree(d); +err: + dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_probe); + +void dvb_usbv2_disconnect(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + const char *devname = kstrdup(dev_name(&d->udev->dev), GFP_KERNEL); + const char *drvname = d->name; + + dev_dbg(&d->udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (d->props->exit) + d->props->exit(d); + + dvb_usbv2_exit(d); + + if (d->props->disconnect) + d->props->disconnect(d); + + kfree(d->priv); + kfree(d); + + pr_info("%s: '%s:%s' successfully deinitialized and disconnected\n", + KBUILD_MODNAME, drvname, devname); + kfree(devname); +} +EXPORT_SYMBOL(dvb_usbv2_disconnect); + +int dvb_usbv2_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + int ret = 0, i, active_fe; + struct dvb_frontend *fe; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* stop remote controller poll */ + if (d->rc_polling_active) + cancel_delayed_work_sync(&d->rc_query_work); + + for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) { + active_fe = d->adapter[i].active_fe; + if (d->adapter[i].dvb_adap.priv && active_fe != -1) { + fe = d->adapter[i].fe[active_fe]; + d->adapter[i].suspend_resume_active = true; + + if (d->props->streaming_ctrl) + d->props->streaming_ctrl(fe, 0); + + /* stop usb streaming */ + usb_urb_killv2(&d->adapter[i].stream); + + ret = dvb_frontend_suspend(fe); + } + } + + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_suspend); + +static int dvb_usbv2_resume_common(struct dvb_usb_device *d) +{ + int ret = 0, i, active_fe; + struct dvb_frontend *fe; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + for (i = 0; i < MAX_NO_OF_ADAPTER_PER_DEVICE; i++) { + active_fe = d->adapter[i].active_fe; + if (d->adapter[i].dvb_adap.priv && active_fe != -1) { + fe = d->adapter[i].fe[active_fe]; + + ret = dvb_frontend_resume(fe); + + /* resume usb streaming */ + usb_urb_submitv2(&d->adapter[i].stream, NULL); + + if (d->props->streaming_ctrl) + d->props->streaming_ctrl(fe, 1); + + d->adapter[i].suspend_resume_active = false; + } + } + + /* start remote controller poll */ + if (d->rc_polling_active) + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); + + return ret; +} + +int dvb_usbv2_resume(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + return dvb_usbv2_resume_common(d); +} +EXPORT_SYMBOL(dvb_usbv2_resume); + +int dvb_usbv2_reset_resume(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_device_power_ctrl(d, 1); + + if (d->props->init) + d->props->init(d); + + ret = dvb_usbv2_resume_common(d); + + dvb_usbv2_device_power_ctrl(d, 0); + + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_reset_resume); + +MODULE_VERSION("2.0"); +MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>"); +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("DVB USB common"); +MODULE_LICENSE("GPL"); |