linux-stable/drivers/media/dvb-core/dvbdev.c
Mauro Carvalho Chehab 04bf12c2d3 [media] dvbdev: enable all interface links at init
Interface links are normally enabled, meaning that the interfaces are
bound to the entities. So, any ioctl sent to the interface are reflected
at the entities managed by the interface.

However, when a device is in use, other interfaces for the same hardware
could be decoupled from the entities linked to them, because the
hardware may have some parts busy.

That's for example, what happens when an hybrid TV device is in use.
If it is streaming analog TV or capturing signals from S-Video/Composite
connectors, typically the digital part of the hardware can't be used and
vice-versa.

This is generally due to some internal hardware or firmware limitation,
that it is not easily mapped via data pipelines.

What the Kernel drivers do internally is that they decouple the hardware
from the interface. So, all changes, if allowed, are done only at some
interface cache, but not physically changed at the hardware.

The usage is similar to the usage of the MEDIA_LNK_FL_ENABLED on data
links. So, let's use the same flag to indicate if either the interface
to entity link is bound/enabled or not.

Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
2016-01-11 12:18:56 -02:00

824 lines
19 KiB
C

/*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include "dvbdev.h"
/* Due to enum tuner_pad_index */
#include <media/tuner.h>
static DEFINE_MUTEX(dvbdev_mutex);
static int dvbdev_debug;
module_param(dvbdev_debug, int, 0644);
MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off).");
#define dprintk if (dvbdev_debug) printk
static LIST_HEAD(dvb_adapter_list);
static DEFINE_MUTEX(dvbdev_register_lock);
static const char * const dnames[] = {
"video", "audio", "sec", "frontend", "demux", "dvr", "ca",
"net", "osd"
};
#ifdef CONFIG_DVB_DYNAMIC_MINORS
#define MAX_DVB_MINORS 256
#define DVB_MAX_IDS MAX_DVB_MINORS
#else
#define DVB_MAX_IDS 4
#define nums2minor(num,type,id) ((num << 6) | (id << 4) | type)
#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64)
#endif
static struct class *dvb_class;
static struct dvb_device *dvb_minors[MAX_DVB_MINORS];
static DECLARE_RWSEM(minor_rwsem);
static int dvb_device_open(struct inode *inode, struct file *file)
{
struct dvb_device *dvbdev;
mutex_lock(&dvbdev_mutex);
down_read(&minor_rwsem);
dvbdev = dvb_minors[iminor(inode)];
if (dvbdev && dvbdev->fops) {
int err = 0;
const struct file_operations *new_fops;
new_fops = fops_get(dvbdev->fops);
if (!new_fops)
goto fail;
file->private_data = dvbdev;
replace_fops(file, new_fops);
if (file->f_op->open)
err = file->f_op->open(inode,file);
up_read(&minor_rwsem);
mutex_unlock(&dvbdev_mutex);
return err;
}
fail:
up_read(&minor_rwsem);
mutex_unlock(&dvbdev_mutex);
return -ENODEV;
}
static const struct file_operations dvb_device_fops =
{
.owner = THIS_MODULE,
.open = dvb_device_open,
.llseek = noop_llseek,
};
static struct cdev dvb_device_cdev;
int dvb_generic_open(struct inode *inode, struct file *file)
{
struct dvb_device *dvbdev = file->private_data;
if (!dvbdev)
return -ENODEV;
if (!dvbdev->users)
return -EBUSY;
if ((file->f_flags & O_ACCMODE) == O_RDONLY) {
if (!dvbdev->readers)
return -EBUSY;
dvbdev->readers--;
} else {
if (!dvbdev->writers)
return -EBUSY;
dvbdev->writers--;
}
dvbdev->users--;
return 0;
}
EXPORT_SYMBOL(dvb_generic_open);
int dvb_generic_release(struct inode *inode, struct file *file)
{
struct dvb_device *dvbdev = file->private_data;
if (!dvbdev)
return -ENODEV;
if ((file->f_flags & O_ACCMODE) == O_RDONLY) {
dvbdev->readers++;
} else {
dvbdev->writers++;
}
dvbdev->users++;
return 0;
}
EXPORT_SYMBOL(dvb_generic_release);
long dvb_generic_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct dvb_device *dvbdev = file->private_data;
if (!dvbdev)
return -ENODEV;
if (!dvbdev->kernel_ioctl)
return -EINVAL;
return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl);
}
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) {
struct dvb_device *dev;
list_for_each_entry(dev, &adap->device_list, list_head)
if (dev->type == type && dev->id == id)
goto skip;
return id;
skip:
id++;
}
return -ENFILE;
}
static void dvb_create_tsout_entity(struct dvb_device *dvbdev,
const char *name, int npads)
{
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
int i, ret = 0;
dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads),
GFP_KERNEL);
if (!dvbdev->tsout_pads)
return;
dvbdev->tsout_entity = kcalloc(npads, sizeof(*dvbdev->tsout_entity),
GFP_KERNEL);
if (!dvbdev->tsout_entity) {
kfree(dvbdev->tsout_pads);
dvbdev->tsout_pads = NULL;
return;
}
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);
if (!entity->name) {
ret = -ENOMEM;
break;
}
entity->type = MEDIA_ENT_T_DVB_TSOUT;
pads->flags = MEDIA_PAD_FL_SINK;
ret = media_entity_init(entity, 1, pads);
if (ret < 0)
break;
ret = media_device_register_entity(dvbdev->adapter->mdev,
entity);
if (ret < 0)
break;
}
if (!ret) {
dvbdev->tsout_num_entities = npads;
return;
}
for (i--; i >= 0; i--) {
media_device_unregister_entity(&dvbdev->tsout_entity[i]);
kfree(dvbdev->tsout_entity[i].name);
}
printk(KERN_ERR
"%s: media_device_register_entity failed for %s\n",
__func__, name);
kfree(dvbdev->tsout_entity);
kfree(dvbdev->tsout_pads);
dvbdev->tsout_entity = NULL;
dvbdev->tsout_pads = NULL;
#endif
}
#define DEMUX_TSOUT "demux-tsout"
#define DVR_TSOUT "dvr-tsout"
static void dvb_create_media_entity(struct dvb_device *dvbdev,
int type, int demux_sink_pads)
{
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
int i, ret = 0, npads;
switch (type) {
case DVB_DEVICE_FRONTEND:
npads = 2;
break;
case DVB_DEVICE_DVR:
dvb_create_tsout_entity(dvbdev, DVR_TSOUT, demux_sink_pads);
return;
case DVB_DEVICE_DEMUX:
npads = 1 + demux_sink_pads;
dvb_create_tsout_entity(dvbdev, DEMUX_TSOUT, demux_sink_pads);
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.
*/
return;
default:
return;
}
dvbdev->entity = kzalloc(sizeof(*dvbdev->entity), GFP_KERNEL);
if (!dvbdev->entity)
return;
dvbdev->entity->name = dvbdev->name;
if (npads) {
dvbdev->pads = kcalloc(npads, sizeof(*dvbdev->pads),
GFP_KERNEL);
if (!dvbdev->pads) {
kfree(dvbdev->entity);
return;
}
}
switch (type) {
case DVB_DEVICE_FRONTEND:
dvbdev->entity->type = MEDIA_ENT_T_DVB_DEMOD;
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
break;
case DVB_DEVICE_DEMUX:
dvbdev->entity->type = MEDIA_ENT_T_DVB_DEMUX;
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
for (i = 1; i < npads; i++)
dvbdev->pads[i].flags = MEDIA_PAD_FL_SOURCE;
break;
case DVB_DEVICE_CA:
dvbdev->entity->type = MEDIA_ENT_T_DVB_CA;
dvbdev->pads[0].flags = MEDIA_PAD_FL_SINK;
dvbdev->pads[1].flags = MEDIA_PAD_FL_SOURCE;
break;
default:
kfree(dvbdev->entity);
dvbdev->entity = NULL;
return;
}
if (npads)
ret = media_entity_init(dvbdev->entity, npads, dvbdev->pads);
if (!ret)
ret = media_device_register_entity(dvbdev->adapter->mdev,
dvbdev->entity);
if (ret < 0) {
printk(KERN_ERR
"%s: media_device_register_entity failed for %s\n",
__func__, dvbdev->entity->name);
media_device_unregister_entity(dvbdev->entity);
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->pads);
kfree(dvbdev->entity);
kfree(dvbdev->tsout_pads);
kfree(dvbdev->tsout_entity);
dvbdev->entity = NULL;
return;
}
printk(KERN_DEBUG "%s: media entity '%s' registered.\n",
__func__, dvbdev->entity->name);
#endif
}
static void dvb_register_media_device(struct dvb_device *dvbdev,
int type, int minor,
unsigned demux_sink_pads)
{
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
u32 intf_type;
if (!dvbdev->adapter->mdev)
return;
dvb_create_media_entity(dvbdev, type, demux_sink_pads);
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:
return;
}
dvbdev->intf_devnode = media_devnode_create(dvbdev->adapter->mdev,
intf_type, 0,
DVB_MAJOR, minor,
GFP_KERNEL);
/*
* 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
*/
if (!dvbdev->entity || !dvbdev->intf_devnode)
return;
media_create_intf_link(dvbdev->entity, &dvbdev->intf_devnode->intf,
MEDIA_LNK_FL_ENABLED);
#endif
}
int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
const struct dvb_device *template, void *priv, int type,
int demux_sink_pads)
{
struct dvb_device *dvbdev;
struct file_operations *dvbdevfops;
struct device *clsdev;
int minor;
int id;
mutex_lock(&dvbdev_register_lock);
if ((id = dvbdev_get_free_id (adap, type)) < 0){
mutex_unlock(&dvbdev_register_lock);
*pdvbdev = NULL;
printk(KERN_ERR "%s: couldn't find free device id\n", __func__);
return -ENFILE;
}
*pdvbdev = dvbdev = kmalloc(sizeof(struct dvb_device), GFP_KERNEL);
if (!dvbdev){
mutex_unlock(&dvbdev_register_lock);
return -ENOMEM;
}
dvbdevfops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
if (!dvbdevfops){
kfree (dvbdev);
mutex_unlock(&dvbdev_register_lock);
return -ENOMEM;
}
memcpy(dvbdev, template, sizeof(struct dvb_device));
dvbdev->type = type;
dvbdev->id = id;
dvbdev->adapter = adap;
dvbdev->priv = priv;
dvbdev->fops = dvbdevfops;
init_waitqueue_head (&dvbdev->wait_queue);
memcpy(dvbdevfops, template->fops, sizeof(struct file_operations));
dvbdevfops->owner = adap->module;
list_add_tail (&dvbdev->list_head, &adap->device_list);
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);
up_write(&minor_rwsem);
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);
mutex_unlock(&dvbdev_register_lock);
clsdev = device_create(dvb_class, adap->device,
MKDEV(DVB_MAJOR, minor),
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
if (IS_ERR(clsdev)) {
printk(KERN_ERR "%s: failed to create device dvb%d.%s%d (%ld)\n",
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
return PTR_ERR(clsdev);
}
dprintk(KERN_DEBUG "DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
adap->num, dnames[type], id, minor, minor);
dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
return 0;
}
EXPORT_SYMBOL(dvb_register_device);
void dvb_unregister_device(struct dvb_device *dvbdev)
{
if (!dvbdev)
return;
down_write(&minor_rwsem);
dvb_minors[dvbdev->minor] = NULL;
up_write(&minor_rwsem);
device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor));
#if defined(CONFIG_MEDIA_CONTROLLER_DVB)
if (dvbdev->entity) {
int i;
media_device_unregister_entity(dvbdev->entity);
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->entity);
kfree(dvbdev->pads);
kfree(dvbdev->tsout_entity);
kfree(dvbdev->tsout_pads);
}
#endif
list_del (&dvbdev->list_head);
kfree (dvbdev->fops);
kfree (dvbdev);
}
EXPORT_SYMBOL(dvb_unregister_device);
#ifdef CONFIG_MEDIA_CONTROLLER_DVB
void dvb_create_media_graph(struct dvb_adapter *adap)
{
struct media_device *mdev = adap->mdev;
struct media_entity *entity, *tuner = NULL, *demod = NULL;
struct media_entity *demux = NULL, *ca = NULL;
struct media_interface *intf;
unsigned demux_pad = 0;
unsigned dvr_pad = 0;
if (!mdev)
return;
media_device_for_each_entity(entity, mdev) {
switch (entity->type) {
case MEDIA_ENT_T_V4L2_SUBDEV_TUNER:
tuner = entity;
break;
case MEDIA_ENT_T_DVB_DEMOD:
demod = entity;
break;
case MEDIA_ENT_T_DVB_DEMUX:
demux = entity;
break;
case MEDIA_ENT_T_DVB_CA:
ca = entity;
break;
}
}
if (tuner && demod)
media_create_pad_link(tuner, TUNER_PAD_IF_OUTPUT, demod, 0, 0);
if (demod && demux)
media_create_pad_link(demod, 1, demux, 0, MEDIA_LNK_FL_ENABLED);
if (demux && ca)
media_create_pad_link(demux, 1, ca, 0, MEDIA_LNK_FL_ENABLED);
/* Create demux links for each ringbuffer/pad */
if (demux) {
media_device_for_each_entity(entity, mdev) {
if (entity->type == MEDIA_ENT_T_DVB_TSOUT) {
if (!strncmp(entity->name, DVR_TSOUT,
strlen(DVR_TSOUT)))
media_create_pad_link(demux,
++dvr_pad,
entity, 0, 0);
if (!strncmp(entity->name, DEMUX_TSOUT,
strlen(DEMUX_TSOUT)))
media_create_pad_link(demux,
++demux_pad,
entity, 0, 0);
}
}
}
/* Create indirect interface links for FE->tuner, DVR->demux and CA->ca */
media_device_for_each_intf(intf, mdev) {
if (intf->type == MEDIA_INTF_T_DVB_CA && ca)
media_create_intf_link(ca, intf, MEDIA_LNK_FL_ENABLED);
if (intf->type == MEDIA_INTF_T_DVB_FE && tuner)
media_create_intf_link(tuner, intf,
MEDIA_LNK_FL_ENABLED);
if (intf->type == MEDIA_INTF_T_DVB_DVR && demux)
media_create_intf_link(demux, intf,
MEDIA_LNK_FL_ENABLED);
media_device_for_each_entity(entity, mdev) {
if (entity->type == MEDIA_ENT_T_DVB_TSOUT) {
if (!strcmp(entity->name, DVR_TSOUT))
media_create_intf_link(entity, intf,
MEDIA_LNK_FL_ENABLED);
if (!strcmp(entity->name, DEMUX_TSOUT))
media_create_intf_link(entity, intf,
MEDIA_LNK_FL_ENABLED);
break;
}
}
}
}
EXPORT_SYMBOL_GPL(dvb_create_media_graph);
#endif
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;
}
static int dvbdev_get_free_adapter_num (void)
{
int num = 0;
while (num < DVB_MAX_ADAPTERS) {
if (dvbdev_check_free_adapter_num(num))
return num;
num++;
}
return -ENFILE;
}
int dvb_register_adapter(struct dvb_adapter *adap, const char *name,
struct module *module, struct device *device,
short *adapter_nums)
{
int i, num;
mutex_lock(&dvbdev_register_lock);
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) {
mutex_unlock(&dvbdev_register_lock);
return -ENFILE;
}
memset (adap, 0, sizeof(struct dvb_adapter));
INIT_LIST_HEAD (&adap->device_list);
printk(KERN_INFO "DVB: registering new adapter (%s)\n", name);
adap->num = num;
adap->name = name;
adap->module = module;
adap->device = device;
adap->mfe_shared = 0;
adap->mfe_dvbdev = NULL;
mutex_init (&adap->mfe_lock);
list_add_tail (&adap->list_head, &dvb_adapter_list);
mutex_unlock(&dvbdev_register_lock);
return num;
}
EXPORT_SYMBOL(dvb_register_adapter);
int dvb_unregister_adapter(struct dvb_adapter *adap)
{
mutex_lock(&dvbdev_register_lock);
list_del (&adap->list_head);
mutex_unlock(&dvbdev_register_lock);
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
define this as video_usercopy(). this will introduce a dependecy
to the v4l "videodev.o" module, which is unnecessary for some
cards (ie. the budget dvb-cards don't need the v4l module...) */
int dvb_usercopy(struct file *file,
unsigned int cmd, unsigned long arg,
int (*func)(struct file *file,
unsigned int cmd, void *arg))
{
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:
/*
* For this command, the pointer is actually an integer
* argument.
*/
parg = (void *) arg;
break;
case _IOC_READ: /* some v4l ioctls are marked wrong ... */
case _IOC_WRITE:
case (_IOC_WRITE | _IOC_READ):
if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
parg = sbuf;
} else {
/* too big to allocate from stack */
mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL);
if (NULL == mbuf)
return -ENOMEM;
parg = mbuf;
}
err = -EFAULT;
if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
goto out;
break;
}
/* call driver */
if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
err = -ENOTTY;
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)))
err = -EFAULT;
break;
}
out:
kfree(mbuf);
return err;
}
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);
add_uevent_var(env, "DVB_DEVICE_TYPE=%s", dnames[dvbdev->type]);
add_uevent_var(env, "DVB_DEVICE_NUM=%d", dvbdev->id);
return 0;
}
static char *dvb_devnode(struct device *dev, umode_t *mode)
{
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);
}
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) {
printk(KERN_ERR "dvb-core: unable to get major %d\n", DVB_MAJOR);
return retval;
}
cdev_init(&dvb_device_cdev, &dvb_device_fops);
if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) {
printk(KERN_ERR "dvb-core: unable register character device\n");
goto error;
}
dvb_class = class_create(THIS_MODULE, "dvb");
if (IS_ERR(dvb_class)) {
retval = PTR_ERR(dvb_class);
goto error;
}
dvb_class->dev_uevent = dvb_uevent;
dvb_class->devnode = dvb_devnode;
return 0;
error:
cdev_del(&dvb_device_cdev);
unregister_chrdev_region(dev, MAX_DVB_MINORS);
return retval;
}
static void __exit exit_dvbdev(void)
{
class_destroy(dvb_class);
cdev_del(&dvb_device_cdev);
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
}
subsys_initcall(init_dvbdev);
module_exit(exit_dvbdev);
MODULE_DESCRIPTION("DVB Core Driver");
MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler");
MODULE_LICENSE("GPL");