2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* dvbdev.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2000 Ralph Metzler <ralph@convergence.de>
|
|
|
|
* & Marcus Metzler <marcus@convergence.de>
|
|
|
|
* for convergence integrated media GmbH
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2.1
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2016-10-13 16:47:54 +07:00
|
|
|
#define pr_fmt(fmt) "dvbdev: " fmt
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
2018-03-02 22:21:16 +07:00
|
|
|
#include <linux/i2c.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/cdev.h>
|
2006-01-15 16:52:23 +07:00
|
|
|
#include <linux/mutex.h>
|
2017-12-29 01:03:51 +07:00
|
|
|
#include <media/dvbdev.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-08-31 23:23:03 +07:00
|
|
|
/* Due to enum tuner_pad_index */
|
|
|
|
#include <media/tuner.h>
|
|
|
|
|
2010-09-12 00:56:45 +07:00
|
|
|
static DEFINE_MUTEX(dvbdev_mutex);
|
2005-04-17 05:20:36 +07:00
|
|
|
static int dvbdev_debug;
|
|
|
|
|
|
|
|
module_param(dvbdev_debug, int, 0644);
|
|
|
|
MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off).");
|
|
|
|
|
2016-10-13 16:47:54 +07:00
|
|
|
#define dprintk(fmt, arg...) do { \
|
|
|
|
if (dvbdev_debug) \
|
|
|
|
printk(KERN_DEBUG pr_fmt("%s: " fmt), \
|
|
|
|
__func__, ##arg); \
|
|
|
|
} while (0)
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static LIST_HEAD(dvb_adapter_list);
|
2006-01-15 16:52:23 +07:00
|
|
|
static DEFINE_MUTEX(dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static const char * const dnames[] = {
|
2017-09-20 01:22:19 +07:00
|
|
|
[DVB_DEVICE_VIDEO] = "video",
|
|
|
|
[DVB_DEVICE_AUDIO] = "audio",
|
|
|
|
[DVB_DEVICE_SEC] = "sec",
|
|
|
|
[DVB_DEVICE_FRONTEND] = "frontend",
|
|
|
|
[DVB_DEVICE_DEMUX] = "demux",
|
|
|
|
[DVB_DEVICE_DVR] = "dvr",
|
|
|
|
[DVB_DEVICE_CA] = "ca",
|
|
|
|
[DVB_DEVICE_NET] = "net",
|
|
|
|
[DVB_DEVICE_OSD] = "osd"
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2008-10-23 22:11:19 +07:00
|
|
|
#ifdef CONFIG_DVB_DYNAMIC_MINORS
|
|
|
|
#define MAX_DVB_MINORS 256
|
|
|
|
#define DVB_MAX_IDS MAX_DVB_MINORS
|
|
|
|
#else
|
2005-04-17 05:20:36 +07:00
|
|
|
#define DVB_MAX_IDS 4
|
2017-09-20 01:22:19 +07:00
|
|
|
|
2017-10-12 02:29:46 +07:00
|
|
|
static const u8 minor_type[] = {
|
|
|
|
[DVB_DEVICE_VIDEO] = 0,
|
|
|
|
[DVB_DEVICE_AUDIO] = 1,
|
|
|
|
[DVB_DEVICE_SEC] = 2,
|
|
|
|
[DVB_DEVICE_FRONTEND] = 3,
|
|
|
|
[DVB_DEVICE_DEMUX] = 4,
|
|
|
|
[DVB_DEVICE_DVR] = 5,
|
|
|
|
[DVB_DEVICE_CA] = 6,
|
|
|
|
[DVB_DEVICE_NET] = 7,
|
|
|
|
[DVB_DEVICE_OSD] = 8,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define nums2minor(num, type, id) \
|
|
|
|
(((num) << 6) | ((id) << 4) | minor_type[type])
|
2017-09-20 01:22:19 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64)
|
2008-10-23 22:11:19 +07:00
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-03-24 01:01:41 +07:00
|
|
|
static struct class *dvb_class;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-10-23 22:11:19 +07:00
|
|
|
static struct dvb_device *dvb_minors[MAX_DVB_MINORS];
|
|
|
|
static DECLARE_RWSEM(minor_rwsem);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static int dvb_device_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct dvb_device *dvbdev;
|
|
|
|
|
2010-09-12 00:56:45 +07:00
|
|
|
mutex_lock(&dvbdev_mutex);
|
2008-10-23 22:11:19 +07:00
|
|
|
down_read(&minor_rwsem);
|
|
|
|
dvbdev = dvb_minors[iminor(inode)];
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (dvbdev && dvbdev->fops) {
|
|
|
|
int err = 0;
|
2013-09-23 01:17:15 +07:00
|
|
|
const struct file_operations *new_fops;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-09-23 01:17:15 +07:00
|
|
|
new_fops = fops_get(dvbdev->fops);
|
|
|
|
if (!new_fops)
|
2009-01-07 05:40:40 +07:00
|
|
|
goto fail;
|
2013-09-23 01:17:15 +07:00
|
|
|
file->private_data = dvbdev;
|
|
|
|
replace_fops(file, new_fops);
|
|
|
|
if (file->f_op->open)
|
2016-01-15 12:14:59 +07:00
|
|
|
err = file->f_op->open(inode, file);
|
2008-10-23 22:11:19 +07:00
|
|
|
up_read(&minor_rwsem);
|
2010-09-12 00:56:45 +07:00
|
|
|
mutex_unlock(&dvbdev_mutex);
|
2005-12-12 15:37:27 +07:00
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2009-01-07 05:40:40 +07:00
|
|
|
fail:
|
2008-10-23 22:11:19 +07:00
|
|
|
up_read(&minor_rwsem);
|
2010-09-12 00:56:45 +07:00
|
|
|
mutex_unlock(&dvbdev_mutex);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-04-23 00:42:01 +07:00
|
|
|
static const struct file_operations dvb_device_fops =
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = dvb_device_open,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-15 23:52:59 +07:00
|
|
|
.llseek = noop_llseek,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2007-09-13 05:06:57 +07:00
|
|
|
static struct cdev dvb_device_cdev;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
int dvb_generic_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
2005-12-12 15:37:27 +07:00
|
|
|
struct dvb_device *dvbdev = file->private_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-12-12 15:37:27 +07:00
|
|
|
if (!dvbdev)
|
|
|
|
return -ENODEV;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (!dvbdev->users)
|
2005-12-12 15:37:27 +07:00
|
|
|
return -EBUSY;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if ((file->f_flags & O_ACCMODE) == O_RDONLY) {
|
2005-12-12 15:37:27 +07:00
|
|
|
if (!dvbdev->readers)
|
|
|
|
return -EBUSY;
|
2005-04-17 05:20:36 +07:00
|
|
|
dvbdev->readers--;
|
|
|
|
} else {
|
2005-12-12 15:37:27 +07:00
|
|
|
if (!dvbdev->writers)
|
|
|
|
return -EBUSY;
|
2005-04-17 05:20:36 +07:00
|
|
|
dvbdev->writers--;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvbdev->users--;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_generic_open);
|
|
|
|
|
|
|
|
|
|
|
|
int dvb_generic_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
2005-12-12 15:37:27 +07:00
|
|
|
struct dvb_device *dvbdev = file->private_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (!dvbdev)
|
2005-12-12 15:37:27 +07:00
|
|
|
return -ENODEV;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if ((file->f_flags & O_ACCMODE) == O_RDONLY) {
|
|
|
|
dvbdev->readers++;
|
|
|
|
} else {
|
|
|
|
dvbdev->writers++;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvbdev->users++;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_generic_release);
|
|
|
|
|
|
|
|
|
2010-04-27 05:24:00 +07:00
|
|
|
long dvb_generic_ioctl(struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-12-12 15:37:27 +07:00
|
|
|
struct dvb_device *dvbdev = file->private_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-12-12 15:37:27 +07:00
|
|
|
if (!dvbdev)
|
|
|
|
return -ENODEV;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (!dvbdev->kernel_ioctl)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2010-09-12 00:56:45 +07:00
|
|
|
return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_generic_ioctl);
|
|
|
|
|
|
|
|
|
|
|
|
static int dvbdev_get_free_id (struct dvb_adapter *adap, int type)
|
|
|
|
{
|
|
|
|
u32 id = 0;
|
|
|
|
|
|
|
|
while (id < DVB_MAX_IDS) {
|
2007-10-10 15:37:39 +07:00
|
|
|
struct dvb_device *dev;
|
|
|
|
list_for_each_entry(dev, &adap->device_list, list_head)
|
2005-04-17 05:20:36 +07:00
|
|
|
if (dev->type == type && dev->id == id)
|
|
|
|
goto skip;
|
|
|
|
return id;
|
|
|
|
skip:
|
|
|
|
id++;
|
|
|
|
}
|
|
|
|
return -ENFILE;
|
|
|
|
}
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
static void dvb_media_device_free(struct dvb_device *dvbdev)
|
|
|
|
{
|
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
|
|
|
if (dvbdev->entity) {
|
|
|
|
media_device_unregister_entity(dvbdev->entity);
|
|
|
|
kfree(dvbdev->entity);
|
|
|
|
kfree(dvbdev->pads);
|
|
|
|
dvbdev->entity = NULL;
|
|
|
|
dvbdev->pads = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dvbdev->tsout_entity) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < dvbdev->tsout_num_entities; i++) {
|
|
|
|
media_device_unregister_entity(&dvbdev->tsout_entity[i]);
|
|
|
|
kfree(dvbdev->tsout_entity[i].name);
|
|
|
|
}
|
|
|
|
kfree(dvbdev->tsout_entity);
|
|
|
|
kfree(dvbdev->tsout_pads);
|
|
|
|
dvbdev->tsout_entity = NULL;
|
|
|
|
dvbdev->tsout_pads = NULL;
|
|
|
|
|
|
|
|
dvbdev->tsout_num_entities = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dvbdev->intf_devnode) {
|
|
|
|
media_devnode_remove(dvbdev->intf_devnode);
|
|
|
|
dvbdev->intf_devnode = NULL;
|
|
|
|
}
|
2015-12-29 20:52:23 +07:00
|
|
|
|
|
|
|
if (dvbdev->adapter->conn) {
|
|
|
|
media_device_unregister_entity(dvbdev->adapter->conn);
|
|
|
|
dvbdev->adapter->conn = NULL;
|
|
|
|
kfree(dvbdev->adapter->conn_pads);
|
|
|
|
dvbdev->adapter->conn_pads = NULL;
|
|
|
|
}
|
2015-09-05 01:10:29 +07:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-12-28 19:30:06 +07:00
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
2015-09-05 01:10:29 +07:00
|
|
|
static int dvb_create_tsout_entity(struct dvb_device *dvbdev,
|
2015-08-22 02:18:18 +07:00
|
|
|
const char *name, int npads)
|
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
|
|
|
|
dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!dvbdev->tsout_pads)
|
2015-09-05 01:10:29 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2015-08-22 02:18:18 +07:00
|
|
|
dvbdev->tsout_entity = kcalloc(npads, sizeof(*dvbdev->tsout_entity),
|
|
|
|
GFP_KERNEL);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!dvbdev->tsout_entity)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
dvbdev->tsout_num_entities = npads;
|
|
|
|
|
2015-08-22 02:18:18 +07:00
|
|
|
for (i = 0; i < npads; i++) {
|
|
|
|
struct media_pad *pads = &dvbdev->tsout_pads[i];
|
|
|
|
struct media_entity *entity = &dvbdev->tsout_entity[i];
|
|
|
|
|
|
|
|
entity->name = kasprintf(GFP_KERNEL, "%s #%d", name, i);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!entity->name)
|
|
|
|
return -ENOMEM;
|
2015-08-22 02:18:18 +07:00
|
|
|
|
2015-12-11 02:25:41 +07:00
|
|
|
entity->function = MEDIA_ENT_F_IO_DTV;
|
2015-08-22 02:18:18 +07:00
|
|
|
pads->flags = MEDIA_PAD_FL_SINK;
|
|
|
|
|
2015-12-11 16:44:40 +07:00
|
|
|
ret = media_entity_pads_init(entity, 1, pads);
|
2015-08-22 02:18:18 +07:00
|
|
|
if (ret < 0)
|
2015-09-05 01:10:29 +07:00
|
|
|
return ret;
|
2015-08-22 02:18:18 +07:00
|
|
|
|
|
|
|
ret = media_device_register_entity(dvbdev->adapter->mdev,
|
|
|
|
entity);
|
|
|
|
if (ret < 0)
|
2015-09-05 01:10:29 +07:00
|
|
|
return ret;
|
2015-08-22 02:18:18 +07:00
|
|
|
}
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-22 02:18:18 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#define DEMUX_TSOUT "demux-tsout"
|
|
|
|
#define DVR_TSOUT "dvr-tsout"
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
static int dvb_create_media_entity(struct dvb_device *dvbdev,
|
|
|
|
int type, int demux_sink_pads)
|
2015-01-02 22:19:51 +07:00
|
|
|
{
|
2015-09-05 01:10:29 +07:00
|
|
|
int i, ret, npads;
|
2015-01-02 22:19:51 +07:00
|
|
|
|
2015-08-21 18:20:22 +07:00
|
|
|
switch (type) {
|
|
|
|
case DVB_DEVICE_FRONTEND:
|
|
|
|
npads = 2;
|
|
|
|
break;
|
2015-08-22 02:18:18 +07:00
|
|
|
case DVB_DEVICE_DVR:
|
2015-09-05 01:10:29 +07:00
|
|
|
ret = dvb_create_tsout_entity(dvbdev, DVR_TSOUT,
|
|
|
|
demux_sink_pads);
|
|
|
|
return ret;
|
2015-08-21 18:20:22 +07:00
|
|
|
case DVB_DEVICE_DEMUX:
|
2015-08-22 02:18:18 +07:00
|
|
|
npads = 1 + demux_sink_pads;
|
2015-09-05 01:10:29 +07:00
|
|
|
ret = dvb_create_tsout_entity(dvbdev, DEMUX_TSOUT,
|
|
|
|
demux_sink_pads);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2015-08-21 18:20:22 +07:00
|
|
|
break;
|
|
|
|
case DVB_DEVICE_CA:
|
|
|
|
npads = 2;
|
|
|
|
break;
|
|
|
|
case DVB_DEVICE_NET:
|
|
|
|
/*
|
|
|
|
* We should be creating entities for the MPE/ULE
|
|
|
|
* decapsulation hardware (or software implementation).
|
|
|
|
*
|
|
|
|
* However, the number of for the MPE/ULE decaps may not be
|
|
|
|
* fixed. As we don't have yet dynamic support for PADs at
|
|
|
|
* the Media Controller, let's not create the decap
|
|
|
|
* entities yet.
|
|
|
|
*/
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
default:
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
}
|
2015-01-02 22:19:51 +07:00
|
|
|
|
|
|
|
dvbdev->entity = kzalloc(sizeof(*dvbdev->entity), GFP_KERNEL);
|
|
|
|
if (!dvbdev->entity)
|
2015-09-05 01:10:29 +07:00
|
|
|
return -ENOMEM;
|
2015-01-02 22:19:51 +07:00
|
|
|
|
|
|
|
dvbdev->entity->name = dvbdev->name;
|
2015-01-03 11:59:53 +07:00
|
|
|
|
|
|
|
if (npads) {
|
|
|
|
dvbdev->pads = kcalloc(npads, sizeof(*dvbdev->pads),
|
|
|
|
GFP_KERNEL);
|
2019-08-18 10:45:40 +07:00
|
|
|
if (!dvbdev->pads) {
|
|
|
|
kfree(dvbdev->entity);
|
2015-09-05 01:10:29 +07:00
|
|
|
return -ENOMEM;
|
2019-08-18 10:45:40 +07:00
|
|
|
}
|
2015-01-03 11:59:53 +07:00
|
|
|
}
|
|
|
|
|
2015-01-02 22:19:51 +07:00
|
|
|
switch (type) {
|
|
|
|
case DVB_DEVICE_FRONTEND:
|
2015-12-11 02:25:41 +07:00
|
|
|
dvbdev->entity->function = MEDIA_ENT_F_DTV_DEMOD;
|
2015-01-04 02:35:53 +07:00
|
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
|
|
|
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
|
2015-01-02 22:19:51 +07:00
|
|
|
break;
|
|
|
|
case DVB_DEVICE_DEMUX:
|
2015-12-11 02:25:41 +07:00
|
|
|
dvbdev->entity->function = MEDIA_ENT_F_TS_DEMUX;
|
2015-01-04 02:35:53 +07:00
|
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
2015-08-22 02:18:18 +07:00
|
|
|
for (i = 1; i < npads; i++)
|
|
|
|
dvbdev->pads[i].flags = MEDIA_PAD_FL_SOURCE;
|
2015-01-02 22:19:51 +07:00
|
|
|
break;
|
|
|
|
case DVB_DEVICE_CA:
|
2015-12-11 02:25:41 +07:00
|
|
|
dvbdev->entity->function = MEDIA_ENT_F_DTV_CA;
|
2015-01-04 02:35:53 +07:00
|
|
|
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
|
|
|
|
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
|
2015-01-02 22:19:51 +07:00
|
|
|
break;
|
|
|
|
default:
|
2015-09-05 01:10:29 +07:00
|
|
|
/* Should never happen, as the first switch prevents it */
|
2015-01-02 22:19:51 +07:00
|
|
|
kfree(dvbdev->entity);
|
2015-09-05 01:10:29 +07:00
|
|
|
kfree(dvbdev->pads);
|
2015-01-02 22:19:51 +07:00
|
|
|
dvbdev->entity = NULL;
|
2015-09-05 01:10:29 +07:00
|
|
|
dvbdev->pads = NULL;
|
|
|
|
return 0;
|
2015-01-02 22:19:51 +07:00
|
|
|
}
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
if (npads) {
|
2015-12-11 16:44:40 +07:00
|
|
|
ret = media_entity_pads_init(dvbdev->entity, npads, dvbdev->pads);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2015-01-02 22:19:51 +07:00
|
|
|
}
|
2015-09-05 01:10:29 +07:00
|
|
|
ret = media_device_register_entity(dvbdev->adapter->mdev,
|
|
|
|
dvbdev->entity);
|
|
|
|
if (ret)
|
2016-01-15 12:15:00 +07:00
|
|
|
return ret;
|
2015-01-02 22:19:51 +07:00
|
|
|
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_info("%s: media entity '%s' registered.\n",
|
2015-01-02 22:19:51 +07:00
|
|
|
__func__, dvbdev->entity->name);
|
2015-09-05 01:10:29 +07:00
|
|
|
|
|
|
|
return 0;
|
2015-01-02 22:19:51 +07:00
|
|
|
}
|
2015-12-28 19:30:06 +07:00
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
static int dvb_register_media_device(struct dvb_device *dvbdev,
|
|
|
|
int type, int minor,
|
|
|
|
unsigned demux_sink_pads)
|
2015-08-21 18:20:22 +07:00
|
|
|
{
|
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
|
2015-09-05 01:10:29 +07:00
|
|
|
struct media_link *link;
|
2015-08-21 18:20:22 +07:00
|
|
|
u32 intf_type;
|
2015-09-05 01:10:29 +07:00
|
|
|
int ret;
|
2015-08-21 18:20:22 +07:00
|
|
|
|
|
|
|
if (!dvbdev->adapter->mdev)
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
ret = dvb_create_media_entity(dvbdev, type, demux_sink_pads);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2015-08-21 18:20:22 +07:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case DVB_DEVICE_FRONTEND:
|
|
|
|
intf_type = MEDIA_INTF_T_DVB_FE;
|
|
|
|
break;
|
|
|
|
case DVB_DEVICE_DEMUX:
|
|
|
|
intf_type = MEDIA_INTF_T_DVB_DEMUX;
|
|
|
|
break;
|
|
|
|
case DVB_DEVICE_DVR:
|
|
|
|
intf_type = MEDIA_INTF_T_DVB_DVR;
|
|
|
|
break;
|
|
|
|
case DVB_DEVICE_CA:
|
|
|
|
intf_type = MEDIA_INTF_T_DVB_CA;
|
|
|
|
break;
|
|
|
|
case DVB_DEVICE_NET:
|
|
|
|
intf_type = MEDIA_INTF_T_DVB_NET;
|
|
|
|
break;
|
|
|
|
default:
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
dvbdev->intf_devnode = media_devnode_create(dvbdev->adapter->mdev,
|
2015-09-05 01:10:29 +07:00
|
|
|
intf_type, 0,
|
2015-09-09 18:19:25 +07:00
|
|
|
DVB_MAJOR, minor);
|
2015-09-05 01:10:29 +07:00
|
|
|
|
|
|
|
if (!dvbdev->intf_devnode)
|
|
|
|
return -ENOMEM;
|
2015-08-21 18:20:22 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the "obvious" link, e. g. the ones that represent
|
|
|
|
* a direct association between an interface and an entity.
|
|
|
|
* Other links should be created elsewhere, like:
|
|
|
|
* DVB FE intf -> tuner
|
|
|
|
* DVB demux intf -> dvr
|
|
|
|
*/
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!dvbdev->entity)
|
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
|
2018-07-02 19:43:02 +07:00
|
|
|
link = media_create_intf_link(dvbdev->entity,
|
|
|
|
&dvbdev->intf_devnode->intf,
|
|
|
|
MEDIA_LNK_FL_ENABLED |
|
|
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!link)
|
|
|
|
return -ENOMEM;
|
2015-08-21 18:20:22 +07:00
|
|
|
#endif
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-08-21 18:20:22 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
2017-09-20 01:22:19 +07:00
|
|
|
const struct dvb_device *template, void *priv,
|
|
|
|
enum dvb_device_type type, int demux_sink_pads)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct dvb_device *dvbdev;
|
2007-02-13 19:46:55 +07:00
|
|
|
struct file_operations *dvbdevfops;
|
2007-08-16 00:00:09 +07:00
|
|
|
struct device *clsdev;
|
2008-10-23 22:11:19 +07:00
|
|
|
int minor;
|
2015-09-05 01:10:29 +07:00
|
|
|
int id, ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-03-10 16:21:25 +07:00
|
|
|
mutex_lock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-02-13 19:46:55 +07:00
|
|
|
if ((id = dvbdev_get_free_id (adap, type)) < 0){
|
2006-01-15 16:52:23 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
*pdvbdev = NULL;
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_err("%s: couldn't find free device id\n", __func__);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENFILE;
|
|
|
|
}
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
*pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-02-13 19:46:55 +07:00
|
|
|
if (!dvbdev){
|
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2019-07-03 23:28:37 +07:00
|
|
|
dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
|
2007-02-13 19:46:55 +07:00
|
|
|
|
|
|
|
if (!dvbdevfops){
|
|
|
|
kfree (dvbdev);
|
2006-01-15 16:52:23 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(dvbdev, template, sizeof(struct dvb_device));
|
|
|
|
dvbdev->type = type;
|
|
|
|
dvbdev->id = id;
|
|
|
|
dvbdev->adapter = adap;
|
|
|
|
dvbdev->priv = priv;
|
2007-02-13 19:46:55 +07:00
|
|
|
dvbdev->fops = dvbdevfops;
|
2007-04-14 20:18:58 +07:00
|
|
|
init_waitqueue_head (&dvbdev->wait_queue);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-01-11 16:12:43 +07:00
|
|
|
dvbdevfops->owner = adap->module;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
list_add_tail (&dvbdev->list_head, &adap->device_list);
|
|
|
|
|
2008-10-23 22:11:19 +07:00
|
|
|
down_write(&minor_rwsem);
|
|
|
|
#ifdef CONFIG_DVB_DYNAMIC_MINORS
|
|
|
|
for (minor = 0; minor < MAX_DVB_MINORS; minor++)
|
|
|
|
if (dvb_minors[minor] == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (minor == MAX_DVB_MINORS) {
|
|
|
|
kfree(dvbdevfops);
|
|
|
|
kfree(dvbdev);
|
2012-06-23 17:59:54 +07:00
|
|
|
up_write(&minor_rwsem);
|
2008-10-23 22:11:19 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
minor = nums2minor(adap->num, type, id);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
dvbdev->minor = minor;
|
|
|
|
dvb_minors[minor] = dvbdev;
|
|
|
|
up_write(&minor_rwsem);
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
|
|
|
|
if (ret) {
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_err("%s: dvb_register_media_device failed to create the mediagraph\n",
|
2015-09-05 01:10:29 +07:00
|
|
|
__func__);
|
|
|
|
|
|
|
|
dvb_media_device_free(dvbdev);
|
|
|
|
kfree(dvbdevfops);
|
|
|
|
kfree(dvbdev);
|
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-04-04 19:41:47 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
|
|
|
|
2008-07-22 10:03:34 +07:00
|
|
|
clsdev = device_create(dvb_class, adap->device,
|
2008-11-03 15:38:43 +07:00
|
|
|
MKDEV(DVB_MAJOR, minor),
|
2008-10-28 08:27:37 +07:00
|
|
|
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
|
2007-05-07 06:56:14 +07:00
|
|
|
if (IS_ERR(clsdev)) {
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n",
|
2008-04-09 09:20:00 +07:00
|
|
|
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
|
2007-05-07 06:56:14 +07:00
|
|
|
return PTR_ERR(clsdev);
|
|
|
|
}
|
2016-10-13 16:47:54 +07:00
|
|
|
dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
|
2008-10-23 22:11:19 +07:00
|
|
|
adap->num, dnames[type], id, minor, minor);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_register_device);
|
|
|
|
|
|
|
|
|
2016-08-10 04:32:11 +07:00
|
|
|
void dvb_remove_device(struct dvb_device *dvbdev)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (!dvbdev)
|
|
|
|
return;
|
|
|
|
|
2008-10-23 22:11:19 +07:00
|
|
|
down_write(&minor_rwsem);
|
|
|
|
dvb_minors[dvbdev->minor] = NULL;
|
|
|
|
up_write(&minor_rwsem);
|
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
dvb_media_device_free(dvbdev);
|
2015-08-22 02:18:18 +07:00
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor));
|
2015-01-02 22:19:51 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del (&dvbdev->list_head);
|
2016-08-10 04:32:11 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_remove_device);
|
|
|
|
|
|
|
|
|
|
|
|
void dvb_free_device(struct dvb_device *dvbdev)
|
|
|
|
{
|
|
|
|
if (!dvbdev)
|
|
|
|
return;
|
|
|
|
|
2007-02-13 19:46:55 +07:00
|
|
|
kfree (dvbdev->fops);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree (dvbdev);
|
|
|
|
}
|
2016-08-10 04:32:11 +07:00
|
|
|
EXPORT_SYMBOL(dvb_free_device);
|
|
|
|
|
|
|
|
|
|
|
|
void dvb_unregister_device(struct dvb_device *dvbdev)
|
|
|
|
{
|
|
|
|
dvb_remove_device(dvbdev);
|
|
|
|
dvb_free_device(dvbdev);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
EXPORT_SYMBOL(dvb_unregister_device);
|
|
|
|
|
2015-01-04 02:52:34 +07:00
|
|
|
|
|
|
|
#ifdef CONFIG_MEDIA_CONTROLLER_DVB
|
2015-09-06 23:28:48 +07:00
|
|
|
|
|
|
|
static int dvb_create_io_intf_links(struct dvb_adapter *adap,
|
|
|
|
struct media_interface *intf,
|
|
|
|
char *name)
|
|
|
|
{
|
|
|
|
struct media_device *mdev = adap->mdev;
|
|
|
|
struct media_entity *entity;
|
|
|
|
struct media_link *link;
|
|
|
|
|
|
|
|
media_device_for_each_entity(entity, mdev) {
|
|
|
|
if (entity->function == MEDIA_ENT_F_IO_DTV) {
|
|
|
|
if (strncmp(entity->name, name, strlen(name)))
|
|
|
|
continue;
|
|
|
|
link = media_create_intf_link(entity, intf,
|
2018-07-02 19:43:02 +07:00
|
|
|
MEDIA_LNK_FL_ENABLED |
|
|
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
2015-09-06 23:28:48 +07:00
|
|
|
if (!link)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-12-29 20:52:23 +07:00
|
|
|
int dvb_create_media_graph(struct dvb_adapter *adap,
|
|
|
|
bool create_rf_connector)
|
2015-03-02 20:49:04 +07:00
|
|
|
{
|
|
|
|
struct media_device *mdev = adap->mdev;
|
2015-12-29 20:52:23 +07:00
|
|
|
struct media_entity *entity, *tuner = NULL, *demod = NULL, *conn;
|
2015-08-22 02:18:18 +07:00
|
|
|
struct media_entity *demux = NULL, *ca = NULL;
|
2015-09-05 01:10:29 +07:00
|
|
|
struct media_link *link;
|
2015-08-21 19:32:38 +07:00
|
|
|
struct media_interface *intf;
|
2015-08-22 02:18:18 +07:00
|
|
|
unsigned demux_pad = 0;
|
|
|
|
unsigned dvr_pad = 0;
|
2015-12-30 19:11:53 +07:00
|
|
|
unsigned ntuner = 0, ndemod = 0;
|
2018-07-31 23:43:39 +07:00
|
|
|
int ret, pad_source, pad_sink;
|
2015-12-29 20:52:23 +07:00
|
|
|
static const char *connector_name = "Television";
|
2015-01-04 02:52:34 +07:00
|
|
|
|
|
|
|
if (!mdev)
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-01-04 02:52:34 +07:00
|
|
|
|
|
|
|
media_device_for_each_entity(entity, mdev) {
|
2015-09-06 19:33:39 +07:00
|
|
|
switch (entity->function) {
|
2015-12-11 02:25:41 +07:00
|
|
|
case MEDIA_ENT_F_TUNER:
|
2015-01-04 02:52:34 +07:00
|
|
|
tuner = entity;
|
2015-12-30 19:11:53 +07:00
|
|
|
ntuner++;
|
2015-01-04 02:52:34 +07:00
|
|
|
break;
|
2015-12-11 02:25:41 +07:00
|
|
|
case MEDIA_ENT_F_DTV_DEMOD:
|
2015-08-21 21:40:34 +07:00
|
|
|
demod = entity;
|
2015-12-30 19:11:53 +07:00
|
|
|
ndemod++;
|
2015-01-04 02:52:34 +07:00
|
|
|
break;
|
2015-12-11 02:25:41 +07:00
|
|
|
case MEDIA_ENT_F_TS_DEMUX:
|
2015-01-04 02:52:34 +07:00
|
|
|
demux = entity;
|
|
|
|
break;
|
2015-12-11 02:25:41 +07:00
|
|
|
case MEDIA_ENT_F_DTV_CA:
|
2015-01-04 02:52:34 +07:00
|
|
|
ca = entity;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-30 19:11:53 +07:00
|
|
|
/*
|
|
|
|
* Prepare to signalize to media_create_pad_links() that multiple
|
|
|
|
* entities of the same type exists and a 1:n or n:1 links need to be
|
|
|
|
* created.
|
|
|
|
* NOTE: if both tuner and demod have multiple instances, it is up
|
|
|
|
* to the caller driver to create such links.
|
|
|
|
*/
|
|
|
|
if (ntuner > 1)
|
|
|
|
tuner = NULL;
|
|
|
|
if (ndemod > 1)
|
|
|
|
demod = NULL;
|
|
|
|
|
2015-12-29 20:52:23 +07:00
|
|
|
if (create_rf_connector) {
|
|
|
|
conn = kzalloc(sizeof(*conn), GFP_KERNEL);
|
|
|
|
if (!conn)
|
|
|
|
return -ENOMEM;
|
|
|
|
adap->conn = conn;
|
|
|
|
|
2016-01-15 12:14:58 +07:00
|
|
|
adap->conn_pads = kzalloc(sizeof(*adap->conn_pads), GFP_KERNEL);
|
2015-12-29 20:52:23 +07:00
|
|
|
if (!adap->conn_pads)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
conn->flags = MEDIA_ENT_FL_CONNECTOR;
|
|
|
|
conn->function = MEDIA_ENT_F_CONN_RF;
|
|
|
|
conn->name = connector_name;
|
|
|
|
adap->conn_pads->flags = MEDIA_PAD_FL_SOURCE;
|
|
|
|
|
|
|
|
ret = media_entity_pads_init(conn, 1, adap->conn_pads);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = media_device_register_entity(mdev, conn);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-07-31 23:43:39 +07:00
|
|
|
if (!ntuner) {
|
2015-12-30 19:11:53 +07:00
|
|
|
ret = media_create_pad_links(mdev,
|
|
|
|
MEDIA_ENT_F_CONN_RF,
|
|
|
|
conn, 0,
|
|
|
|
MEDIA_ENT_F_DTV_DEMOD,
|
|
|
|
demod, 0,
|
|
|
|
MEDIA_LNK_FL_ENABLED,
|
|
|
|
false);
|
2018-07-31 23:43:39 +07:00
|
|
|
} else {
|
|
|
|
pad_sink = media_get_pad_index(tuner, true,
|
|
|
|
PAD_SIGNAL_ANALOG);
|
|
|
|
if (pad_sink < 0)
|
|
|
|
return -EINVAL;
|
2015-12-30 19:11:53 +07:00
|
|
|
ret = media_create_pad_links(mdev,
|
|
|
|
MEDIA_ENT_F_CONN_RF,
|
|
|
|
conn, 0,
|
|
|
|
MEDIA_ENT_F_TUNER,
|
2018-07-31 23:43:39 +07:00
|
|
|
tuner, pad_sink,
|
2015-12-30 19:11:53 +07:00
|
|
|
MEDIA_LNK_FL_ENABLED,
|
|
|
|
false);
|
2018-07-31 23:43:39 +07:00
|
|
|
}
|
2015-12-29 20:52:23 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-12-30 19:11:53 +07:00
|
|
|
if (ntuner && ndemod) {
|
2018-07-31 23:43:39 +07:00
|
|
|
pad_source = media_get_pad_index(tuner, true,
|
|
|
|
PAD_SIGNAL_ANALOG);
|
|
|
|
if (pad_source)
|
|
|
|
return -EINVAL;
|
2015-12-30 19:11:53 +07:00
|
|
|
ret = media_create_pad_links(mdev,
|
|
|
|
MEDIA_ENT_F_TUNER,
|
2018-07-31 23:43:39 +07:00
|
|
|
tuner, pad_source,
|
2015-12-30 19:11:53 +07:00
|
|
|
MEDIA_ENT_F_DTV_DEMOD,
|
|
|
|
demod, 0, MEDIA_LNK_FL_ENABLED,
|
|
|
|
false);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2015-01-04 02:52:34 +07:00
|
|
|
|
2015-12-30 19:11:53 +07:00
|
|
|
if (ndemod && demux) {
|
|
|
|
ret = media_create_pad_links(mdev,
|
|
|
|
MEDIA_ENT_F_DTV_DEMOD,
|
|
|
|
demod, 1,
|
|
|
|
MEDIA_ENT_F_TS_DEMUX,
|
|
|
|
demux, 0, MEDIA_LNK_FL_ENABLED,
|
|
|
|
false);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (ret)
|
2016-03-19 04:31:35 +07:00
|
|
|
return ret;
|
2015-09-05 01:10:29 +07:00
|
|
|
}
|
|
|
|
if (demux && ca) {
|
|
|
|
ret = media_create_pad_link(demux, 1, ca,
|
|
|
|
0, MEDIA_LNK_FL_ENABLED);
|
2016-03-02 02:28:54 +07:00
|
|
|
if (ret)
|
2016-03-19 04:31:35 +07:00
|
|
|
return ret;
|
2015-09-05 01:10:29 +07:00
|
|
|
}
|
2015-08-21 19:32:38 +07:00
|
|
|
|
2015-08-22 02:18:18 +07:00
|
|
|
/* Create demux links for each ringbuffer/pad */
|
|
|
|
if (demux) {
|
|
|
|
media_device_for_each_entity(entity, mdev) {
|
2015-12-11 02:25:41 +07:00
|
|
|
if (entity->function == MEDIA_ENT_F_IO_DTV) {
|
2015-08-22 02:18:18 +07:00
|
|
|
if (!strncmp(entity->name, DVR_TSOUT,
|
2015-09-05 01:10:29 +07:00
|
|
|
strlen(DVR_TSOUT))) {
|
|
|
|
ret = media_create_pad_link(demux,
|
|
|
|
++dvr_pad,
|
|
|
|
entity, 0, 0);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2015-08-22 02:18:18 +07:00
|
|
|
if (!strncmp(entity->name, DEMUX_TSOUT,
|
2015-09-05 01:10:29 +07:00
|
|
|
strlen(DEMUX_TSOUT))) {
|
|
|
|
ret = media_create_pad_link(demux,
|
2015-08-22 02:18:18 +07:00
|
|
|
++demux_pad,
|
2015-09-05 01:10:29 +07:00
|
|
|
entity, 0, 0);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2015-08-22 02:18:18 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-06 23:38:23 +07:00
|
|
|
/* Create interface links for FE->tuner, DVR->demux and CA->ca */
|
2015-08-23 17:51:22 +07:00
|
|
|
media_device_for_each_intf(intf, mdev) {
|
2015-09-05 01:10:29 +07:00
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_CA && ca) {
|
|
|
|
link = media_create_intf_link(ca, intf,
|
2018-07-02 19:43:02 +07:00
|
|
|
MEDIA_LNK_FL_ENABLED |
|
|
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!link)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2015-08-23 17:51:22 +07:00
|
|
|
|
2015-09-05 01:10:29 +07:00
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_FE && tuner) {
|
|
|
|
link = media_create_intf_link(tuner, intf,
|
2018-07-02 19:43:02 +07:00
|
|
|
MEDIA_LNK_FL_ENABLED |
|
|
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!link)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2015-09-06 23:38:23 +07:00
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
* Indirect link - let's not create yet, as we don't know how
|
|
|
|
* to handle indirect links, nor if this will
|
|
|
|
* actually be needed.
|
|
|
|
*/
|
2015-09-05 01:10:29 +07:00
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_DVR && demux) {
|
|
|
|
link = media_create_intf_link(demux, intf,
|
2018-07-02 19:43:02 +07:00
|
|
|
MEDIA_LNK_FL_ENABLED |
|
|
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
2015-09-05 01:10:29 +07:00
|
|
|
if (!link)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2015-09-06 23:38:23 +07:00
|
|
|
#endif
|
2015-09-06 23:28:48 +07:00
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_DVR) {
|
|
|
|
ret = dvb_create_io_intf_links(adap, intf, DVR_TSOUT);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (intf->type == MEDIA_INTF_T_DVB_DEMUX) {
|
|
|
|
ret = dvb_create_io_intf_links(adap, intf, DEMUX_TSOUT);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2015-08-22 02:18:18 +07:00
|
|
|
}
|
2015-08-21 19:32:38 +07:00
|
|
|
}
|
2015-09-05 01:10:29 +07:00
|
|
|
return 0;
|
2015-01-04 02:52:34 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dvb_create_media_graph);
|
2015-03-02 20:49:04 +07:00
|
|
|
#endif
|
2015-01-04 02:52:34 +07:00
|
|
|
|
2008-04-10 05:13:13 +07:00
|
|
|
static int dvbdev_check_free_adapter_num(int num)
|
|
|
|
{
|
|
|
|
struct list_head *entry;
|
|
|
|
list_for_each(entry, &dvb_adapter_list) {
|
|
|
|
struct dvb_adapter *adap;
|
|
|
|
adap = list_entry(entry, struct dvb_adapter, list_head);
|
|
|
|
if (adap->num == num)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static int dvbdev_get_free_adapter_num (void)
|
|
|
|
{
|
|
|
|
int num = 0;
|
|
|
|
|
|
|
|
while (num < DVB_MAX_ADAPTERS) {
|
2008-04-10 05:13:13 +07:00
|
|
|
if (dvbdev_check_free_adapter_num(num))
|
|
|
|
return num;
|
2005-04-17 05:20:36 +07:00
|
|
|
num++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-04-10 05:13:13 +07:00
|
|
|
int dvb_register_adapter(struct dvb_adapter *adap, const char *name,
|
|
|
|
struct module *module, struct device *device,
|
|
|
|
short *adapter_nums)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-04-10 05:13:13 +07:00
|
|
|
int i, num;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-03-10 16:21:25 +07:00
|
|
|
mutex_lock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-04-10 05:13:13 +07:00
|
|
|
for (i = 0; i < DVB_MAX_ADAPTERS; ++i) {
|
|
|
|
num = adapter_nums[i];
|
|
|
|
if (num >= 0 && num < DVB_MAX_ADAPTERS) {
|
|
|
|
/* use the one the driver asked for */
|
|
|
|
if (dvbdev_check_free_adapter_num(num))
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
num = dvbdev_get_free_adapter_num();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
num = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num < 0) {
|
2006-01-15 16:52:23 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset (adap, 0, sizeof(struct dvb_adapter));
|
|
|
|
INIT_LIST_HEAD (&adap->device_list);
|
|
|
|
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_info("DVB: registering new adapter (%s)\n", name);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
adap->num = num;
|
|
|
|
adap->name = name;
|
|
|
|
adap->module = module;
|
2006-04-10 19:27:37 +07:00
|
|
|
adap->device = device;
|
2008-10-11 21:44:05 +07:00
|
|
|
adap->mfe_shared = 0;
|
|
|
|
adap->mfe_dvbdev = NULL;
|
|
|
|
mutex_init (&adap->mfe_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-05-04 01:12:59 +07:00
|
|
|
#ifdef CONFIG_MEDIA_CONTROLLER_DVB
|
|
|
|
mutex_init(&adap->mdev_lock);
|
|
|
|
#endif
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
list_add_tail (&adap->list_head, &dvb_adapter_list);
|
|
|
|
|
2006-01-15 16:52:23 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_register_adapter);
|
|
|
|
|
|
|
|
|
|
|
|
int dvb_unregister_adapter(struct dvb_adapter *adap)
|
|
|
|
{
|
2007-03-10 16:21:25 +07:00
|
|
|
mutex_lock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del (&adap->list_head);
|
2006-01-15 16:52:23 +07:00
|
|
|
mutex_unlock(&dvbdev_register_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(dvb_unregister_adapter);
|
|
|
|
|
|
|
|
/* if the miracle happens and "generic_usercopy()" is included into
|
|
|
|
the kernel, then this can vanish. please don't make the mistake and
|
2019-02-19 02:28:57 +07:00
|
|
|
define this as video_usercopy(). this will introduce a dependency
|
2005-04-17 05:20:36 +07:00
|
|
|
to the v4l "videodev.o" module, which is unnecessary for some
|
|
|
|
cards (ie. the budget dvb-cards don't need the v4l module...) */
|
2010-04-27 05:24:00 +07:00
|
|
|
int dvb_usercopy(struct file *file,
|
2005-12-12 15:37:27 +07:00
|
|
|
unsigned int cmd, unsigned long arg,
|
2010-04-27 05:24:00 +07:00
|
|
|
int (*func)(struct file *file,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cmd, void *arg))
|
|
|
|
{
|
2005-12-12 15:37:27 +07:00
|
|
|
char sbuf[128];
|
|
|
|
void *mbuf = NULL;
|
|
|
|
void *parg = NULL;
|
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
/* Copy arguments into temp kernel buffer */
|
|
|
|
switch (_IOC_DIR(cmd)) {
|
|
|
|
case _IOC_NONE:
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* For this command, the pointer is actually an integer
|
|
|
|
* argument.
|
|
|
|
*/
|
|
|
|
parg = (void *) arg;
|
|
|
|
break;
|
2005-12-12 15:37:27 +07:00
|
|
|
case _IOC_READ: /* some v4l ioctls are marked wrong ... */
|
|
|
|
case _IOC_WRITE:
|
|
|
|
case (_IOC_WRITE | _IOC_READ):
|
|
|
|
if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
|
2006-01-10 00:25:34 +07:00
|
|
|
parg = sbuf;
|
2005-12-12 15:37:27 +07:00
|
|
|
} else {
|
2006-01-10 00:25:34 +07:00
|
|
|
/* too big to allocate from stack */
|
2016-01-15 12:14:59 +07:00
|
|
|
mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
|
2006-01-10 00:25:34 +07:00
|
|
|
if (NULL == mbuf)
|
|
|
|
return -ENOMEM;
|
|
|
|
parg = mbuf;
|
2005-12-12 15:37:27 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
err = -EFAULT;
|
|
|
|
if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
|
2006-01-10 00:25:34 +07:00
|
|
|
goto out;
|
2005-12-12 15:37:27 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call driver */
|
2010-04-27 05:24:00 +07:00
|
|
|
if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
|
2012-08-27 13:23:14 +07:00
|
|
|
err = -ENOTTY;
|
2005-12-12 15:37:27 +07:00
|
|
|
|
|
|
|
if (err < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* Copy results into user buffer */
|
|
|
|
switch (_IOC_DIR(cmd))
|
|
|
|
{
|
|
|
|
case _IOC_READ:
|
|
|
|
case (_IOC_WRITE | _IOC_READ):
|
|
|
|
if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
|
2006-01-10 00:25:34 +07:00
|
|
|
err = -EFAULT;
|
2005-12-12 15:37:27 +07:00
|
|
|
break;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
out:
|
2005-12-12 15:37:27 +07:00
|
|
|
kfree(mbuf);
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2018-03-07 16:11:50 +07:00
|
|
|
#if IS_ENABLED(CONFIG_I2C)
|
2018-03-02 22:21:16 +07:00
|
|
|
struct i2c_client *dvb_module_probe(const char *module_name,
|
|
|
|
const char *name,
|
|
|
|
struct i2c_adapter *adap,
|
|
|
|
unsigned char addr,
|
|
|
|
void *platform_data)
|
|
|
|
{
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct i2c_board_info *board_info;
|
|
|
|
|
|
|
|
board_info = kzalloc(sizeof(*board_info), GFP_KERNEL);
|
2018-03-22 20:01:11 +07:00
|
|
|
if (!board_info)
|
|
|
|
return NULL;
|
2018-03-02 22:21:16 +07:00
|
|
|
|
|
|
|
if (name)
|
2018-09-10 19:19:14 +07:00
|
|
|
strscpy(board_info->type, name, I2C_NAME_SIZE);
|
2018-03-02 22:21:16 +07:00
|
|
|
else
|
2018-09-10 19:19:14 +07:00
|
|
|
strscpy(board_info->type, module_name, I2C_NAME_SIZE);
|
2018-03-02 22:21:16 +07:00
|
|
|
|
|
|
|
board_info->addr = addr;
|
|
|
|
board_info->platform_data = platform_data;
|
|
|
|
request_module(module_name);
|
2019-12-16 22:51:28 +07:00
|
|
|
client = i2c_new_client_device(adap, board_info);
|
|
|
|
if (!i2c_client_has_driver(client)) {
|
2018-03-02 22:21:16 +07:00
|
|
|
kfree(board_info);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!try_module_get(client->dev.driver->owner)) {
|
|
|
|
i2c_unregister_device(client);
|
|
|
|
client = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(board_info);
|
|
|
|
return client;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dvb_module_probe);
|
|
|
|
|
|
|
|
void dvb_module_release(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
if (!client)
|
|
|
|
return;
|
|
|
|
|
|
|
|
module_put(client->dev.driver->owner);
|
|
|
|
i2c_unregister_device(client);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dvb_module_release);
|
|
|
|
#endif
|
|
|
|
|
2008-10-28 08:27:37 +07:00
|
|
|
static int dvb_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
|
{
|
|
|
|
struct dvb_device *dvbdev = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
add_uevent_var(env, "DVB_ADAPTER_NUM=%d", dvbdev->adapter->num);
|
2009-01-01 09:35:24 +07:00
|
|
|
add_uevent_var(env, "DVB_DEVICE_TYPE=%s", dnames[dvbdev->type]);
|
|
|
|
add_uevent_var(env, "DVB_DEVICE_NUM=%d", dvbdev->id);
|
2008-10-28 08:27:37 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-07-24 07:24:48 +07:00
|
|
|
static char *dvb_devnode(struct device *dev, umode_t *mode)
|
2009-04-30 20:23:42 +07:00
|
|
|
{
|
|
|
|
struct dvb_device *dvbdev = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return kasprintf(GFP_KERNEL, "dvb/adapter%d/%s%d",
|
|
|
|
dvbdev->adapter->num, dnames[dvbdev->type], dvbdev->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static int __init init_dvbdev(void)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
dev_t dev = MKDEV(DVB_MAJOR, 0);
|
|
|
|
|
|
|
|
if ((retval = register_chrdev_region(dev, MAX_DVB_MINORS, "DVB")) != 0) {
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_err("dvb-core: unable to get major %d\n", DVB_MAJOR);
|
2005-04-17 05:20:36 +07:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdev_init(&dvb_device_cdev, &dvb_device_fops);
|
|
|
|
if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) {
|
2016-10-13 16:47:54 +07:00
|
|
|
pr_err("dvb-core: unable register character device\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2005-03-24 01:01:41 +07:00
|
|
|
dvb_class = class_create(THIS_MODULE, "dvb");
|
2005-04-17 05:20:36 +07:00
|
|
|
if (IS_ERR(dvb_class)) {
|
|
|
|
retval = PTR_ERR(dvb_class);
|
|
|
|
goto error;
|
|
|
|
}
|
2008-10-28 08:27:37 +07:00
|
|
|
dvb_class->dev_uevent = dvb_uevent;
|
2009-09-19 04:01:12 +07:00
|
|
|
dvb_class->devnode = dvb_devnode;
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
cdev_del(&dvb_device_cdev);
|
|
|
|
unregister_chrdev_region(dev, MAX_DVB_MINORS);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __exit exit_dvbdev(void)
|
|
|
|
{
|
2005-03-24 01:01:41 +07:00
|
|
|
class_destroy(dvb_class);
|
2005-04-17 05:20:36 +07:00
|
|
|
cdev_del(&dvb_device_cdev);
|
2005-12-12 15:37:27 +07:00
|
|
|
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2007-05-07 06:56:14 +07:00
|
|
|
subsys_initcall(init_dvbdev);
|
2005-04-17 05:20:36 +07:00
|
|
|
module_exit(exit_dvbdev);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("DVB Core Driver");
|
|
|
|
MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler");
|
|
|
|
MODULE_LICENSE("GPL");
|