1d2b2a6d8c
As defined at ETSI TS 101 162, original network IDs up to 0xfebf are reserved for registration at dvb.org. Let's use, instead, an original network ID at the range 0xff00-0xffff, as this is for private temporary usage. As the same value is also used for the network ID, the range 0xff01-0xffff also fits better, as values lower than that depend if the network is used for satellite, terrestrial, cable of CI. While here, move the TS ID to the bridge code, where it is used, and change its value, as it was identical to the value previously used by network ID. While we could keep the same value, let's change it, just to make easier to check for the new code while reading it with DVB tools like dvbinspector. Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
581 lines
14 KiB
C
581 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* The Virtual DTV test driver serves as a reference DVB driver and helps
|
|
* validate the existing APIs in the media subsystem. It can also aid
|
|
* developers working on userspace applications.
|
|
*
|
|
* When this module is loaded, it will attempt to modprobe 'dvb_vidtv_tuner'
|
|
* and 'dvb_vidtv_demod'.
|
|
*
|
|
* Copyright (C) 2020 Daniel W. S. Almeida
|
|
*/
|
|
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/time.h>
|
|
#include <linux/types.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "vidtv_bridge.h"
|
|
#include "vidtv_common.h"
|
|
#include "vidtv_demod.h"
|
|
#include "vidtv_mux.h"
|
|
#include "vidtv_ts.h"
|
|
#include "vidtv_tuner.h"
|
|
|
|
#define MUX_BUF_MIN_SZ 90164
|
|
#define MUX_BUF_MAX_SZ (MUX_BUF_MIN_SZ * 10)
|
|
#define TUNER_DEFAULT_ADDR 0x68
|
|
#define DEMOD_DEFAULT_ADDR 0x60
|
|
#define VIDTV_DEFAULT_NETWORK_ID 0xff44
|
|
#define VIDTV_DEFAULT_NETWORK_NAME "LinuxTV.org"
|
|
#define VIDTV_DEFAULT_TS_ID 0x4081
|
|
|
|
/*
|
|
* The LNBf fake parameters here are the ranges used by an
|
|
* Universal (extended) European LNBf, which is likely the most common LNBf
|
|
* found on Satellite digital TV system nowadays.
|
|
*/
|
|
#define LNB_CUT_FREQUENCY 11700000 /* high IF frequency */
|
|
#define LNB_LOW_FREQ 9750000 /* low IF frequency */
|
|
#define LNB_HIGH_FREQ 10600000 /* transition frequency */
|
|
|
|
static unsigned int drop_tslock_prob_on_low_snr;
|
|
module_param(drop_tslock_prob_on_low_snr, uint, 0);
|
|
MODULE_PARM_DESC(drop_tslock_prob_on_low_snr,
|
|
"Probability of losing the TS lock if the signal quality is bad");
|
|
|
|
static unsigned int recover_tslock_prob_on_good_snr;
|
|
module_param(recover_tslock_prob_on_good_snr, uint, 0);
|
|
MODULE_PARM_DESC(recover_tslock_prob_on_good_snr,
|
|
"Probability recovering the TS lock when the signal improves");
|
|
|
|
static unsigned int mock_power_up_delay_msec;
|
|
module_param(mock_power_up_delay_msec, uint, 0);
|
|
MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up delay");
|
|
|
|
static unsigned int mock_tune_delay_msec;
|
|
module_param(mock_tune_delay_msec, uint, 0);
|
|
MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay");
|
|
|
|
static unsigned int vidtv_valid_dvb_t_freqs[NUM_VALID_TUNER_FREQS] = {
|
|
474000000
|
|
};
|
|
|
|
module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0);
|
|
MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs,
|
|
"Valid DVB-T frequencies to simulate, in Hz");
|
|
|
|
static unsigned int vidtv_valid_dvb_c_freqs[NUM_VALID_TUNER_FREQS] = {
|
|
474000000
|
|
};
|
|
|
|
module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0);
|
|
MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs,
|
|
"Valid DVB-C frequencies to simulate, in Hz");
|
|
|
|
static unsigned int vidtv_valid_dvb_s_freqs[NUM_VALID_TUNER_FREQS] = {
|
|
11362000
|
|
};
|
|
module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0);
|
|
MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs,
|
|
"Valid DVB-S/S2 frequencies to simulate at Ku-Band, in kHz");
|
|
|
|
static unsigned int max_frequency_shift_hz;
|
|
module_param(max_frequency_shift_hz, uint, 0);
|
|
MODULE_PARM_DESC(max_frequency_shift_hz,
|
|
"Maximum shift in HZ allowed when tuning in a channel");
|
|
|
|
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);
|
|
|
|
/*
|
|
* Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113.
|
|
*/
|
|
static unsigned int si_period_msec = 40;
|
|
module_param(si_period_msec, uint, 0);
|
|
MODULE_PARM_DESC(si_period_msec, "How often to send SI packets. Default: 40ms");
|
|
|
|
static unsigned int pcr_period_msec = 40;
|
|
module_param(pcr_period_msec, uint, 0);
|
|
MODULE_PARM_DESC(pcr_period_msec,
|
|
"How often to send PCR packets. Default: 40ms");
|
|
|
|
static unsigned int mux_rate_kbytes_sec = 4096;
|
|
module_param(mux_rate_kbytes_sec, uint, 0);
|
|
MODULE_PARM_DESC(mux_rate_kbytes_sec, "Mux rate: will pad stream if below");
|
|
|
|
static unsigned int pcr_pid = 0x200;
|
|
module_param(pcr_pid, uint, 0);
|
|
MODULE_PARM_DESC(pcr_pid, "PCR PID for all channels: defaults to 0x200");
|
|
|
|
static unsigned int mux_buf_sz_pkts;
|
|
module_param(mux_buf_sz_pkts, uint, 0);
|
|
MODULE_PARM_DESC(mux_buf_sz_pkts,
|
|
"Size for the internal mux buffer in multiples of 188 bytes");
|
|
|
|
static u32 vidtv_bridge_mux_buf_sz_for_mux_rate(void)
|
|
{
|
|
u32 max_elapsed_time_msecs = VIDTV_MAX_SLEEP_USECS / USEC_PER_MSEC;
|
|
u32 mux_buf_sz = mux_buf_sz_pkts * TS_PACKET_LEN;
|
|
u32 nbytes_expected;
|
|
|
|
nbytes_expected = mux_rate_kbytes_sec;
|
|
nbytes_expected *= max_elapsed_time_msecs;
|
|
|
|
mux_buf_sz = roundup(nbytes_expected, TS_PACKET_LEN);
|
|
mux_buf_sz += mux_buf_sz / 10;
|
|
|
|
if (mux_buf_sz < MUX_BUF_MIN_SZ)
|
|
mux_buf_sz = MUX_BUF_MIN_SZ;
|
|
|
|
if (mux_buf_sz > MUX_BUF_MAX_SZ)
|
|
mux_buf_sz = MUX_BUF_MAX_SZ;
|
|
|
|
return mux_buf_sz;
|
|
}
|
|
|
|
static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n)
|
|
{
|
|
enum fe_status status;
|
|
|
|
dvb->fe[n]->ops.read_status(dvb->fe[n], &status);
|
|
|
|
return status == (FE_HAS_SIGNAL |
|
|
FE_HAS_CARRIER |
|
|
FE_HAS_VITERBI |
|
|
FE_HAS_SYNC |
|
|
FE_HAS_LOCK);
|
|
}
|
|
|
|
/*
|
|
* called on a separate thread by the mux when new packets become available
|
|
*/
|
|
static void vidtv_bridge_on_new_pkts_avail(void *priv, u8 *buf, u32 npkts)
|
|
{
|
|
struct vidtv_dvb *dvb = priv;
|
|
|
|
/* drop packets if we lose the lock */
|
|
if (vidtv_bridge_check_demod_lock(dvb, 0))
|
|
dvb_dmx_swfilter_packets(&dvb->demux, buf, npkts);
|
|
}
|
|
|
|
static int vidtv_start_streaming(struct vidtv_dvb *dvb)
|
|
{
|
|
struct vidtv_mux_init_args mux_args = {
|
|
.mux_rate_kbytes_sec = mux_rate_kbytes_sec,
|
|
.on_new_packets_available_cb = vidtv_bridge_on_new_pkts_avail,
|
|
.pcr_period_usecs = pcr_period_msec * USEC_PER_MSEC,
|
|
.si_period_usecs = si_period_msec * USEC_PER_MSEC,
|
|
.pcr_pid = pcr_pid,
|
|
.transport_stream_id = VIDTV_DEFAULT_TS_ID,
|
|
.network_id = VIDTV_DEFAULT_NETWORK_ID,
|
|
.network_name = VIDTV_DEFAULT_NETWORK_NAME,
|
|
.priv = dvb,
|
|
};
|
|
struct device *dev = &dvb->pdev->dev;
|
|
u32 mux_buf_sz;
|
|
|
|
if (dvb->streaming) {
|
|
dev_warn_ratelimited(dev, "Already streaming. Skipping.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (mux_buf_sz_pkts)
|
|
mux_buf_sz = mux_buf_sz_pkts;
|
|
else
|
|
mux_buf_sz = vidtv_bridge_mux_buf_sz_for_mux_rate();
|
|
|
|
mux_args.mux_buf_sz = mux_buf_sz;
|
|
|
|
dvb->streaming = true;
|
|
dvb->mux = vidtv_mux_init(dvb->fe[0], dev, &mux_args);
|
|
if (!dvb->mux)
|
|
return -ENOMEM;
|
|
vidtv_mux_start_thread(dvb->mux);
|
|
|
|
dev_dbg_ratelimited(dev, "Started streaming\n");
|
|
return 0;
|
|
}
|
|
|
|
static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
|
|
{
|
|
struct device *dev = &dvb->pdev->dev;
|
|
|
|
dvb->streaming = false;
|
|
vidtv_mux_stop_thread(dvb->mux);
|
|
vidtv_mux_destroy(dvb->mux);
|
|
dvb->mux = NULL;
|
|
|
|
dev_dbg_ratelimited(dev, "Stopped streaming\n");
|
|
return 0;
|
|
}
|
|
|
|
static int vidtv_start_feed(struct dvb_demux_feed *feed)
|
|
{
|
|
struct dvb_demux *demux = feed->demux;
|
|
struct vidtv_dvb *dvb = demux->priv;
|
|
int ret;
|
|
int rc;
|
|
|
|
if (!demux->dmx.frontend)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dvb->feed_lock);
|
|
|
|
dvb->nfeeds++;
|
|
rc = dvb->nfeeds;
|
|
|
|
if (dvb->nfeeds == 1) {
|
|
ret = vidtv_start_streaming(dvb);
|
|
if (ret < 0)
|
|
rc = ret;
|
|
}
|
|
|
|
mutex_unlock(&dvb->feed_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int vidtv_stop_feed(struct dvb_demux_feed *feed)
|
|
{
|
|
struct dvb_demux *demux = feed->demux;
|
|
struct vidtv_dvb *dvb = demux->priv;
|
|
int err = 0;
|
|
|
|
mutex_lock(&dvb->feed_lock);
|
|
dvb->nfeeds--;
|
|
|
|
if (!dvb->nfeeds)
|
|
err = vidtv_stop_streaming(dvb);
|
|
|
|
mutex_unlock(&dvb->feed_lock);
|
|
return err;
|
|
}
|
|
|
|
static struct dvb_frontend *vidtv_get_frontend_ptr(struct i2c_client *c)
|
|
{
|
|
struct vidtv_demod_state *state = i2c_get_clientdata(c);
|
|
|
|
/* the demod will set this when its probe function runs */
|
|
return &state->frontend;
|
|
}
|
|
|
|
static int vidtv_master_xfer(struct i2c_adapter *i2c_adap,
|
|
struct i2c_msg msgs[],
|
|
int num)
|
|
{
|
|
/*
|
|
* Right now, this virtual driver doesn't really send or receive
|
|
* messages from I2C. A real driver will require an implementation
|
|
* here.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static u32 vidtv_i2c_func(struct i2c_adapter *adapter)
|
|
{
|
|
return I2C_FUNC_I2C;
|
|
}
|
|
|
|
static const struct i2c_algorithm vidtv_i2c_algorithm = {
|
|
.master_xfer = vidtv_master_xfer,
|
|
.functionality = vidtv_i2c_func,
|
|
};
|
|
|
|
static int vidtv_bridge_i2c_register_adap(struct vidtv_dvb *dvb)
|
|
{
|
|
struct i2c_adapter *i2c_adapter = &dvb->i2c_adapter;
|
|
|
|
strscpy(i2c_adapter->name, "vidtv_i2c", sizeof(i2c_adapter->name));
|
|
i2c_adapter->owner = THIS_MODULE;
|
|
i2c_adapter->algo = &vidtv_i2c_algorithm;
|
|
i2c_adapter->algo_data = NULL;
|
|
i2c_adapter->timeout = 500;
|
|
i2c_adapter->retries = 3;
|
|
i2c_adapter->dev.parent = &dvb->pdev->dev;
|
|
|
|
i2c_set_adapdata(i2c_adapter, dvb);
|
|
return i2c_add_adapter(&dvb->i2c_adapter);
|
|
}
|
|
|
|
static int vidtv_bridge_register_adap(struct vidtv_dvb *dvb)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = dvb_register_adapter(&dvb->adapter,
|
|
KBUILD_MODNAME,
|
|
THIS_MODULE,
|
|
&dvb->i2c_adapter.dev,
|
|
adapter_nums);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int vidtv_bridge_dmx_init(struct vidtv_dvb *dvb)
|
|
{
|
|
dvb->demux.dmx.capabilities = DMX_TS_FILTERING |
|
|
DMX_SECTION_FILTERING;
|
|
|
|
dvb->demux.priv = dvb;
|
|
dvb->demux.filternum = 256;
|
|
dvb->demux.feednum = 256;
|
|
dvb->demux.start_feed = vidtv_start_feed;
|
|
dvb->demux.stop_feed = vidtv_stop_feed;
|
|
|
|
return dvb_dmx_init(&dvb->demux);
|
|
}
|
|
|
|
static int vidtv_bridge_dmxdev_init(struct vidtv_dvb *dvb)
|
|
{
|
|
dvb->dmx_dev.filternum = 256;
|
|
dvb->dmx_dev.demux = &dvb->demux.dmx;
|
|
dvb->dmx_dev.capabilities = 0;
|
|
|
|
return dvb_dmxdev_init(&dvb->dmx_dev, &dvb->adapter);
|
|
}
|
|
|
|
static int vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n)
|
|
{
|
|
struct vidtv_demod_config cfg = {
|
|
.drop_tslock_prob_on_low_snr = drop_tslock_prob_on_low_snr,
|
|
.recover_tslock_prob_on_good_snr = recover_tslock_prob_on_good_snr,
|
|
};
|
|
dvb->i2c_client_demod[n] = dvb_module_probe("dvb_vidtv_demod",
|
|
NULL,
|
|
&dvb->i2c_adapter,
|
|
DEMOD_DEFAULT_ADDR,
|
|
&cfg);
|
|
|
|
/* driver will not work anyways so bail out */
|
|
if (!dvb->i2c_client_demod[n])
|
|
return -ENODEV;
|
|
|
|
/* retrieve a ptr to the frontend state */
|
|
dvb->fe[n] = vidtv_get_frontend_ptr(dvb->i2c_client_demod[n]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n)
|
|
{
|
|
struct vidtv_tuner_config cfg = {
|
|
.fe = dvb->fe[n],
|
|
.mock_power_up_delay_msec = mock_power_up_delay_msec,
|
|
.mock_tune_delay_msec = mock_tune_delay_msec,
|
|
};
|
|
u32 freq;
|
|
int i;
|
|
|
|
/* TODO: check if the frequencies are at a valid range */
|
|
|
|
memcpy(cfg.vidtv_valid_dvb_t_freqs,
|
|
vidtv_valid_dvb_t_freqs,
|
|
sizeof(vidtv_valid_dvb_t_freqs));
|
|
|
|
memcpy(cfg.vidtv_valid_dvb_c_freqs,
|
|
vidtv_valid_dvb_c_freqs,
|
|
sizeof(vidtv_valid_dvb_c_freqs));
|
|
|
|
/*
|
|
* Convert Satellite frequencies from Ku-band in kHZ into S-band
|
|
* frequencies in Hz.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(vidtv_valid_dvb_s_freqs); i++) {
|
|
freq = vidtv_valid_dvb_s_freqs[i];
|
|
if (freq) {
|
|
if (freq < LNB_CUT_FREQUENCY)
|
|
freq = abs(freq - LNB_LOW_FREQ);
|
|
else
|
|
freq = abs(freq - LNB_HIGH_FREQ);
|
|
}
|
|
cfg.vidtv_valid_dvb_s_freqs[i] = freq;
|
|
}
|
|
|
|
cfg.max_frequency_shift_hz = max_frequency_shift_hz;
|
|
|
|
dvb->i2c_client_tuner[n] = dvb_module_probe("dvb_vidtv_tuner",
|
|
NULL,
|
|
&dvb->i2c_adapter,
|
|
TUNER_DEFAULT_ADDR,
|
|
&cfg);
|
|
|
|
return (dvb->i2c_client_tuner[n]) ? 0 : -ENODEV;
|
|
}
|
|
|
|
static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb)
|
|
{
|
|
int ret, i, j;
|
|
|
|
ret = vidtv_bridge_i2c_register_adap(dvb);
|
|
if (ret < 0)
|
|
goto fail_i2c;
|
|
|
|
ret = vidtv_bridge_register_adap(dvb);
|
|
if (ret < 0)
|
|
goto fail_adapter;
|
|
|
|
for (i = 0; i < NUM_FE; ++i) {
|
|
ret = vidtv_bridge_probe_demod(dvb, i);
|
|
if (ret < 0)
|
|
goto fail_demod_probe;
|
|
|
|
ret = vidtv_bridge_probe_tuner(dvb, i);
|
|
if (ret < 0)
|
|
goto fail_tuner_probe;
|
|
|
|
ret = dvb_register_frontend(&dvb->adapter, dvb->fe[i]);
|
|
if (ret < 0)
|
|
goto fail_fe;
|
|
}
|
|
|
|
ret = vidtv_bridge_dmx_init(dvb);
|
|
if (ret < 0)
|
|
goto fail_dmx;
|
|
|
|
ret = vidtv_bridge_dmxdev_init(dvb);
|
|
if (ret < 0)
|
|
goto fail_dmx_dev;
|
|
|
|
for (j = 0; j < NUM_FE; ++j) {
|
|
ret = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx,
|
|
&dvb->dmx_fe[j]);
|
|
if (ret < 0)
|
|
goto fail_dmx_conn;
|
|
|
|
/*
|
|
* The source of the demux is a frontend connected
|
|
* to the demux.
|
|
*/
|
|
dvb->dmx_fe[j].source = DMX_FRONTEND_0;
|
|
}
|
|
|
|
return ret;
|
|
|
|
fail_dmx_conn:
|
|
for (j = j - 1; j >= 0; --j)
|
|
dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
|
|
&dvb->dmx_fe[j]);
|
|
fail_dmx_dev:
|
|
dvb_dmxdev_release(&dvb->dmx_dev);
|
|
fail_dmx:
|
|
dvb_dmx_release(&dvb->demux);
|
|
fail_fe:
|
|
for (j = i; j >= 0; --j)
|
|
dvb_unregister_frontend(dvb->fe[j]);
|
|
fail_tuner_probe:
|
|
for (j = i; j >= 0; --j)
|
|
if (dvb->i2c_client_tuner[j])
|
|
dvb_module_release(dvb->i2c_client_tuner[j]);
|
|
|
|
fail_demod_probe:
|
|
for (j = i; j >= 0; --j)
|
|
if (dvb->i2c_client_demod[j])
|
|
dvb_module_release(dvb->i2c_client_demod[j]);
|
|
|
|
fail_adapter:
|
|
dvb_unregister_adapter(&dvb->adapter);
|
|
|
|
fail_i2c:
|
|
i2c_del_adapter(&dvb->i2c_adapter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int vidtv_bridge_probe(struct platform_device *pdev)
|
|
{
|
|
struct vidtv_dvb *dvb;
|
|
int ret;
|
|
|
|
dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
|
|
if (!dvb)
|
|
return -ENOMEM;
|
|
|
|
dvb->pdev = pdev;
|
|
|
|
ret = vidtv_bridge_dvb_init(dvb);
|
|
if (ret < 0)
|
|
goto err_dvb;
|
|
|
|
mutex_init(&dvb->feed_lock);
|
|
|
|
platform_set_drvdata(pdev, dvb);
|
|
|
|
dev_info(&pdev->dev, "Successfully initialized vidtv!\n");
|
|
return ret;
|
|
|
|
err_dvb:
|
|
kfree(dvb);
|
|
return ret;
|
|
}
|
|
|
|
static int vidtv_bridge_remove(struct platform_device *pdev)
|
|
{
|
|
struct vidtv_dvb *dvb;
|
|
u32 i;
|
|
|
|
dvb = platform_get_drvdata(pdev);
|
|
|
|
mutex_destroy(&dvb->feed_lock);
|
|
|
|
for (i = 0; i < NUM_FE; ++i) {
|
|
dvb_unregister_frontend(dvb->fe[i]);
|
|
dvb_module_release(dvb->i2c_client_tuner[i]);
|
|
dvb_module_release(dvb->i2c_client_demod[i]);
|
|
}
|
|
|
|
dvb_dmxdev_release(&dvb->dmx_dev);
|
|
dvb_dmx_release(&dvb->demux);
|
|
dvb_unregister_adapter(&dvb->adapter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vidtv_bridge_dev_release(struct device *dev)
|
|
{
|
|
}
|
|
|
|
static struct platform_device vidtv_bridge_dev = {
|
|
.name = "vidtv_bridge",
|
|
.dev.release = vidtv_bridge_dev_release,
|
|
};
|
|
|
|
static struct platform_driver vidtv_bridge_driver = {
|
|
.driver = {
|
|
.name = "vidtv_bridge",
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
.probe = vidtv_bridge_probe,
|
|
.remove = vidtv_bridge_remove,
|
|
};
|
|
|
|
static void __exit vidtv_bridge_exit(void)
|
|
{
|
|
platform_driver_unregister(&vidtv_bridge_driver);
|
|
platform_device_unregister(&vidtv_bridge_dev);
|
|
}
|
|
|
|
static int __init vidtv_bridge_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_device_register(&vidtv_bridge_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = platform_driver_register(&vidtv_bridge_driver);
|
|
if (ret)
|
|
platform_device_unregister(&vidtv_bridge_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
module_init(vidtv_bridge_init);
|
|
module_exit(vidtv_bridge_exit);
|
|
|
|
MODULE_DESCRIPTION("Virtual Digital TV Test Driver");
|
|
MODULE_AUTHOR("Daniel W. S. Almeida");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("vidtv");
|
|
MODULE_ALIAS("dvb_vidtv");
|