[linux-dvb] [PATCH] Userspace tuner

Markus Rechberger markus.rechberger at amd.com
Tue Aug 14 16:31:33 CEST 2007


Following patch adds the possibility to implement tuner drivers in 
userspace.
The tuner drivers themself are implemented as shared libraries and will 
get loaded dynamically by a userspace daemon.
Every kernelspace driver loads one instance of a userspace driver, which 
will communicate with the kernel stub driver.
Also this tuner module supports getting loaded by the DVB framework, as 
for the analogue TV (v4l framework) it's a yet
incomplete but complete enough driver to support some newer silicon 
tuners and it is independent of the tuner-core
module.

Project website which provides udev, init.d scripts and the userspace 
drivers can be found at [1], the provided helper scripts in the
repository will automatically start the userspace daemon if that driver 
is compiled into the kernel or as a module.

This work can immediatelly be reused with ivtv [2], saa7134, cx88, 
em28xx [3] and cxusb drivers and add support for many more devices
which are on the market now.

This driver is work in progress, although the kernel and userland API 
will remain stable, the code applies against linux 2.6.23 rc3

[1] http://mcentral.de/wiki/index.php/Userspace_tuner
[2] http://thread.gmane.org/gmane.comp.video.video4linux/34276
[3] http://mcentral.de/hg/~mrec/em28xx-userspace2/

Signed-off-by: Markus Rechberger <markus.rechberger at amd.com>

diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index d9d033e..aed4832 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -114,4 +114,6 @@ config USB_DABUSB
       module will be called dabusb.
 endif # DAB
 
+source "drivers/media/userspace/Kconfig"
+
 endmenu
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 8fa1993..72be058 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -6,3 +6,4 @@ obj-y := common/
 obj-$(CONFIG_VIDEO_DEV) += video/
 obj-$(CONFIG_VIDEO_DEV) += radio/
 obj-$(CONFIG_DVB_CORE)  += dvb/
+obj-y += userspace/
diff --git a/drivers/media/userspace/Kconfig 
b/drivers/media/userspace/Kconfig
new file mode 100644
index 0000000..1766181
--- /dev/null
+++ b/drivers/media/userspace/Kconfig
@@ -0,0 +1 @@
+source "drivers/media/userspace/tuner/Kconfig"
diff --git a/drivers/media/userspace/Makefile 
b/drivers/media/userspace/Makefile
new file mode 100644
index 0000000..4964520
--- /dev/null
+++ b/drivers/media/userspace/Makefile
@@ -0,0 +1 @@
+obj-y := tuner/
diff --git a/drivers/media/userspace/tuner/Kconfig 
b/drivers/media/userspace/tuner/Kconfig
new file mode 100644
index 0000000..85caa61
--- /dev/null
+++ b/drivers/media/userspace/tuner/Kconfig
@@ -0,0 +1,8 @@
+config USERSPACE_TUNER
+    tristate "Support for userspace tuners (EXPERIMENTAL)"
+    depends on EXPERIMENTAL && I2C
+    default y
+    ---help---
+          Enables support for tuners from userspace
+
+      If you are unsure as to whether this is required, answer Y.
diff --git a/drivers/media/userspace/tuner/Makefile 
b/drivers/media/userspace/tuner/Makefile
new file mode 100644
index 0000000..99068da
--- /dev/null
+++ b/drivers/media/userspace/tuner/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_USERSPACE_TUNER) += tuner-stub.o
+
+EXTRA_CFLAGS += -Idrivers/media/video -Idrivers/media/dvb/frontends 
-Idrivers/media/dvb/dvb-core -Idrivers/media/userspace/tuners/
diff --git a/drivers/media/userspace/tuner/tuner-stub.c 
b/drivers/media/userspace/tuner/tuner-stub.c
new file mode 100644
index 0000000..5f06bff
--- /dev/null
+++ b/drivers/media/userspace/tuner/tuner-stub.c
@@ -0,0 +1,1314 @@
+/*
+   tuner-stub.c
+
+   Copyright (C) 2007 Markus Rechberger <markus.rechberger at amd.com>
+
+   this module provides a kernel<->userspace interface for tuner modules
+   the algorithms can be put into userspace and hidden from kernelspace.
+
+   http://mcentral.de/wiki/index.php/Userspace_tuner
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 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 General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/list.h>
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+
+#include <linux/poll.h>
+
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/i2c.h>
+#include <linux/tuner-stub.h>
+#include "dvb_frontend.h"
+/* v4l header */
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+/* dvb header */
+#include "dvb_frontend.h"
+
+
+//#define debug
+
+#define TUNER_API_VERSION 0x01
+
+
+static struct class *tuner_class;
+struct class_device *cdev;
+static int tuner_major;
+
+static int daemon_client;
+
+static DEFINE_MUTEX(tuner_lock);
+static LIST_HEAD(tuner_devlist);
+static LIST_HEAD(tuner_clients);
+
+static unsigned long tuner_devices;
+
+#ifdef debug
+static void print_ioctl(unsigned int cmd) {
+    switch (cmd) {
+    case TUNER_DAEMON_GET_CMD:
+        printk("TUNER_DAEMON_GET_CMD\n");
+        break;
+    case TUNER_DAEMON_COMPLETE_CMD:
+        printk("TUNER_DAEMON_COMPLETE_CMD\n");
+        break;
+    case TUNER_ADD_DAEMON:
+        printk("TUNER_ADD_DAEMON\n");
+        break;
+    case TUNER_GET_VERSION:
+        printk("TUNER_GET_VERSION\n");
+        break;
+    case TUNER_ADD_TUNER:
+        printk("TUNER_ADD_TUNER\n");
+        break;
+    case TUNER_GET_TUNING_PARAM:
+        printk("TUNER_GET_TUNING_PARAM\n");
+        break;
+    case TUNER_GET_CURRENT_TUNING_PARAM:
+        printk("TUNER_GET_CURRENT_TUNING_PARAM\n");
+        break;
+    case TUNER_GET_CMD:
+        printk("TUNER_GET_CMD\n");
+        break;
+    case TUNER_SEND_I2C_CMD:
+        printk("TUNER_SEND_I2C_CMD\n");
+        break;
+    case TUNER_READ_I2C_CMD:
+        printk("TUNER_READ_I2C_CMD\n");
+        break;
+    case TUNER_SET_MODE:
+        printk("TUNER_SET_MODE\n");
+        break;
+    case TUNER_COMPLETE_CMD:
+        printk("TUNER_COMPLETE_CMD\n");
+        break;
+    case TUNER_SEND_CTRL:
+        printk("TUNER_SEND_CTRL\n");
+        break;
+    case VIDIOC_S_STD:
+        printk("VIDIOC_S_STD\n");
+        break;
+    case VIDIOC_G_TUNER:
+        printk("VIDIOC_G_TUNER\n");
+        break;
+    case VIDIOC_S_FREQUENCY:
+        printk("VIDIOC_S_FREQUENCY\n");
+        break;
+    case VIDIOC_G_FREQUENCY:
+        printk("VIDIOC_G_FREQUENCY\n");
+        break;
+    case VIDIOC_S_TUNER:
+        printk("VIDIOC_S_TUNER\n");
+        break;
+    case VIDIOC_LOG_STATUS:
+        printk("VIDIOC_LOG_STATUS\n");
+        break;
+    case TUNER_CMD_G_FREQ:
+        printk("TUNER_CMD_G_FREQ\n");
+        break;
+    case TUNER_CMD_G_BANDWIDTH:
+        printk("TUNER_CMD_G_BANDWIDTH\n");
+        break;
+    case TUNER_CMD_S_STD:
+        printk("TUNER_CMD_S_STD\n");
+        break;
+    case TUNER_CMD_INIT:
+        printk("TUNER_CMD_INIT\n");
+        break;
+    case TUNER_CMD_S_FREQ:
+        printk("TUNER_CMD_S_FREQ\n");
+        break;
+    case TUNER_CMD_G_STATUS:
+        printk("TUNER_CMD_G_STATUS\n");
+        break;
+    case TUNER_CMD_G_MODE:
+        printk("TUNER_CMD_G_MODE\n");
+        break;
+    case TUNER_CMD_G_DVBINFO:
+        printk("TUNER_CMD_G_DVBINFO\n");
+        break;
+    case TUNER_CMD_CALC_REGS:
+        printk("TUNER_CMD_CALC_REGS\n");
+        break;
+    case TUNER_CMD_SLEEP:
+        printk("TUNER_CMD_SLEEP\n");
+        break;
+    case TUNER_CMD_S_BANDWIDTH:
+        printk("TUNER_CMD_S_BANDWIDTH\n");
+        break;
+    case TUNER_CMD_S_GATE:
+        printk("TUNER_CMD_S_GATE\n");
+        break;
+    case TUNER_CMD_S_MODE:
+        printk("TUNER_CMD_S_MODE\n");
+        break;
+    default:
+        printk("unhandled IOCTL!\n");
+    }
+}
+#endif
+
+static int tuner_open(struct inode *inode, struct file *file)
+{
+    struct tuner_info *info = kzalloc(sizeof(struct tuner_info), 
GFP_KERNEL);
+    init_waitqueue_head(&info->waitqueue);
+    mutex_init(&info->lock);
+    INIT_LIST_HEAD(&info->tuner_commands);
+    file->private_data = info;
+    return 0;
+}
+
+static int tuner_close(struct inode *inode, struct file *file)
+{
+    struct tuner_info *info, *tuner_info;
+    struct list_head *list;
+
+    if (file->private_data == NULL)
+        return 0;
+
+    info = file->private_data;
+
+    if(info == NULL) {
+        printk("BUG: info == NULL\n");
+        return 0;
+    }
+
+    mutex_lock(&info->lock);
+    file->private_data = NULL;
+    
+    switch(info->client_type) {
+    case TUNER_UNDEF:
+        mutex_unlock(&info->lock);
+        kfree(info);
+        break;
+    case TUNER_DAEMON:
+        daemon_client = 0;
+        /* jump through */
+    case TUNER_CLIENT:
+        mutex_unlock(&info->lock);
+
+        mutex_lock(&tuner_lock);
+        list_for_each(list, &tuner_clients) {
+            tuner_info = list_entry(list, struct tuner_info, list);
+            if (tuner_info->tunerid == info->tunerid) {
+                list_del(&info->list);
+                if(info->tunerid > 0)
+                    printk("removing support for %s - 
%s\n",info->company, info->device);
+                break;
+            }
+        }
+        kfree(info);
+        mutex_unlock(&tuner_lock);
+    }
+    return 0;
+}
+
+
+static long tuner_ioctl(struct file *file, unsigned int cmd, unsigned 
long arg)
+{
+    struct tuner_info *info = file->private_data;
+#ifdef debug
+    printk("entered ioctl!\n");
+    print_ioctl(cmd);
+#endif
+    mutex_lock(&info->lock);
+    if (info->client_type == TUNER_DAEMON) {
+        switch(cmd) {
+        case TUNER_DAEMON_GET_CMD:
+            {
+                struct list_head *list;
+                struct tuner_daemon_cmd *qcmd;
+                struct tuner_daemon_user_cmd *cmd = (struct 
tuner_daemon_user_cmd *)arg;
+                struct tuner_daemon_user_cmd icmd;
+                if(copy_from_user(&icmd, cmd, sizeof(struct 
tuner_daemon_user_cmd))) {
+                    mutex_unlock(&info->lock);
+                    goto efault;
+                }
+                list_for_each(list, &info->tuner_commands) {
+                    qcmd = list_entry(list, struct tuner_daemon_cmd, list);
+                    icmd.command = TUNER_LOAD_MODULE;
+                    icmd.clientid = 0;
+                    icmd.tunerid = qcmd->tunerid;
+                    if(copy_to_user(cmd, &icmd, sizeof(struct 
tuner_daemon_user_cmd))) {
+                        mutex_unlock(&info->lock);
+                        goto efault;
+                    }
+
+                    mutex_unlock(&info->lock);
+                    goto e_ok;
+                }
+                break;
+            }
+        case TUNER_DAEMON_COMPLETE_CMD:
+            {
+                struct tuner_daemon_user_cmd *cmd = (struct 
tuner_daemon_user_cmd *)arg;
+                struct tuner_daemon_user_cmd icmd;
+                struct tuner_daemon_cmd *qcmd;
+                struct tuner_dev *dev;
+                struct list_head *list;
+                if(copy_from_user(&icmd, cmd, sizeof(struct 
tuner_daemon_user_cmd))) {
+                    mutex_unlock(&info->lock);
+                    goto efault;
+                }
+                list_for_each(list, &info->tuner_commands) {
+                    qcmd = list_entry(list, struct tuner_daemon_cmd, list);
+                    if (qcmd->clientid == icmd.clientid) {
+                        list_del(&qcmd->list);
+                        dev = qcmd->dev;
+                        if (dev == NULL) {
+                            mutex_unlock(&info->lock);
+                            return -EINVAL;
+                        }
+                        qcmd->retval = icmd.retval;
+                        atomic_set(&dev->pending, 0);
+                        wake_up_interruptible(&dev->waitqueue);
+                        mutex_unlock(&info->lock);
+                        goto e_ok;
+                    }
+                }
+                mutex_unlock(&info->lock);
+                goto einval;
+            }
+        default:
+            printk("invalid argument!\n");
+        }
+    }
+
+    switch(cmd) {
+    case TUNER_ADD_DAEMON:
+    {
+        if (daemon_client) {
+            printk("a userspace daemon seems to be attached already\n");
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+        info->client_type = TUNER_DAEMON;
+        daemon_client = 1;
+        mutex_unlock(&info->lock);
+        mutex_lock(&tuner_lock);
+        list_add(&info->list, &tuner_clients);
+        mutex_unlock(&tuner_lock);
+        goto e_ok;
+    }
+    case TUNER_GET_VERSION:
+    {
+        int *version = (int*)arg;
+        put_user(TUNER_API_VERSION, version);
+        break;
+    }
+
+    case TUNER_ADD_TUNER:
+    {
+        struct tuner_user_info *user_info = (struct tuner_user_info *)arg;
+        struct tuner_user_info user_data;
+
+        if(copy_from_user(&user_data, user_info, sizeof(struct 
tuner_user_info))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+
+        info->tunerid = user_data.tunerid;
+        info->client_type = TUNER_CLIENT;
+        strncpy(info->company, user_info->company, 50);
+        strncpy(info->device, user_data.device, 50);
+        strncpy(info->copyright, user_data.copyright, 100);
+        info->cap = user_data.cap;
+        info->company[49]=0;
+        info->device[49]=0;
+        info->copyright[99]=0;
+        info->version = user_data.version;
+
+        /* this is moreover relevant for DVB/hybrid tuners */
+        info->frequency_min = user_data.frequency_min;
+        info->frequency_max = user_data.frequency_max;
+        info->frequency_step = user_data.frequency_step;
+
+        info->bandwidth_min = user_data.bandwidth_min;
+        info->bandwidth_max = user_data.bandwidth_max;
+        info->bandwidth_step = user_data.bandwidth_step;
+
+        printk("tuner-stub: adding support for %s - %s tuner\n", 
info->company, info->device);
+        printk("tuner-stub: userspace driver version %d\n", info->version);
+        printk("tuner-stub: Copyright: %s\n",info->copyright);
+        mutex_unlock(&info->lock);
+        mutex_lock(&tuner_lock);
+        list_add(&info->list, &tuner_clients);
+        mutex_unlock(&tuner_lock);
+        goto e_ok;
+    }
+
+    case TUNER_GET_TUNING_PARAM:
+    {
+        struct list_head *list;
+        struct tuner_cmd *qcmd;
+        struct tuner_user_params *cmd = (struct tuner_user_params *)arg;
+        list_for_each(list, &info->tuner_commands) {
+            qcmd = list_entry(list, struct tuner_cmd, list);
+            if (copy_to_user(cmd, (struct 
tuner_user_params*)qcmd->data, sizeof(struct tuner_user_params))) {
+                mutex_unlock(&info->lock);
+                goto efault;
+            }
+            mutex_unlock(&info->lock);
+            goto e_ok;
+        }
+        break;
+
+    }
+
+    case TUNER_GET_CURRENT_TUNING_PARAM:
+    {
+        struct list_head *list;
+        struct tuner_user_params *cmd = (struct tuner_user_params *)arg;
+        struct tuner_user_params icmd;
+        struct tuner_dev *devlist,*dev=NULL;
+        
+        if(copy_from_user(&icmd, cmd, sizeof(struct tuner_user_params))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        list_for_each(list, &tuner_devlist) {
+            devlist = list_entry(list, struct tuner_dev, devlist);
+            if (devlist->clientid == icmd.clientid) {
+                dev = devlist;
+                break;
+            }
+        }
+
+        if (dev == NULL) {
+            printk("device not found!\n");
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        if (copy_to_user(cmd, &dev->tuning_params, sizeof(struct 
tuner_user_params))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+
+        mutex_unlock(&info->lock);
+        goto e_ok;
+    }
+
+
+    case TUNER_GET_CMD:
+    {
+        struct list_head *list;
+        struct tuner_cmd *qcmd;
+        struct tuner_user_cmd *cmd = (struct tuner_user_cmd *)arg;
+        struct tuner_user_cmd icmd;
+        if(copy_from_user(&icmd, cmd, sizeof(struct tuner_user_cmd))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        list_for_each(list, &info->tuner_commands) {
+            qcmd = list_entry(list, struct tuner_cmd, list);
+            icmd.command = qcmd->command;
+            icmd.data = qcmd->data;
+            icmd.clientid = qcmd->clientid;
+            if(copy_to_user(cmd, &icmd, sizeof(struct tuner_user_cmd))) {
+                mutex_unlock(&info->lock);
+                goto efault;
+            }
+
+            mutex_unlock(&info->lock);
+            goto e_ok;
+        }
+        break;
+    }
+
+    /* simple i2c wrapper */
+    case TUNER_SEND_I2C_CMD:
+    {
+        struct tuner_i2c_msg *i2ccmd = (struct tuner_i2c_msg *)arg;
+        struct tuner_i2c_msg icmd;
+        struct i2c_msg msg = { .flags = 0 };
+        struct list_head *list;
+        int retval = 0;
+        struct tuner_dev *devlist,*dev=NULL;
+        if(copy_from_user(&icmd, i2ccmd, sizeof(struct tuner_i2c_msg))) {
+            mutex_unlock(&info->lock);
+            printk("unable to copy from user!\n");
+            goto efault;
+        }
+        list_for_each(list, &tuner_devlist) {
+            devlist = list_entry(list, struct tuner_dev, devlist);
+            if (devlist->clientid == icmd.clientid) {
+                dev = devlist;
+                break;
+            }
+        }
+        if(dev == NULL) {
+            printk("DEV is zero (clientid: %d)!!!\n", icmd.clientid);
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        /* took this restriction from i2c-dev.c */
+        if (icmd.len>8192) {
+            printk("len > 8192 error!\n");
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        msg.buf = kzalloc(icmd.len, GFP_KERNEL);
+        msg.len = icmd.len;
+        msg.addr = icmd.addr;
+
+        if (msg.buf == NULL) {
+            mutex_unlock(&info->lock);
+            goto enomem;
+        }
+
+        if(copy_from_user(msg.buf, i2ccmd->buf,i2ccmd->len)) {
+            kfree(msg.buf);
+            mutex_unlock(&info->lock);
+            printk("unable to copy from user!\n");
+            goto efault;
+        }
+
+
+
+        if(!(retval=i2c_transfer(dev->adap,&msg, 1))) {
+            printk("retval: %d\n",retval);
+            kfree(msg.buf);
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+        kfree(msg.buf);
+        mutex_unlock(&info->lock);
+        goto e_ok;
+    }
+
+    case TUNER_READ_I2C_CMD:
+    {
+        struct tuner_i2c_msg *i2ccmd = (struct tuner_i2c_msg *)arg;
+        struct tuner_i2c_msg icmd;
+        struct i2c_msg msg = { .flags = I2C_M_RD };
+        struct tuner_dev *devlist,*dev=NULL;
+        int retval = 0;
+        struct list_head *list;
+        if(copy_from_user(&icmd, i2ccmd, sizeof(struct tuner_i2c_msg))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        list_for_each(list, &tuner_devlist) {
+            devlist = list_entry(list, struct tuner_dev, devlist);
+            if (devlist->clientid == icmd.clientid) {
+                dev = devlist;
+                break;
+            }
+        }
+        if(dev == NULL) {
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+        
+        msg.len = icmd.len;
+        msg.addr = icmd.addr;
+
+        if (msg.len>8192) {
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        msg.buf = kzalloc(icmd.len, GFP_KERNEL);
+
+        if (msg.buf == NULL) {
+            mutex_unlock(&info->lock);
+            goto enomem;
+        }
+
+        if(!(retval=i2c_transfer(dev->adap,&msg, 1))) {
+            printk("retval: %d\n",retval);
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        if (copy_to_user(i2ccmd->buf, msg.buf, msg.len)) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        kfree(msg.buf);
+        mutex_unlock(&info->lock);
+        goto e_ok;
+    }
+
+    case TUNER_SET_MODE:
+    {
+        struct tuner_user_mode mode, *argmode = (struct 
tuner_user_mode*)arg;
+
+        struct list_head *list;
+        struct tuner_dev *devlist,*dev=NULL;
+
+        if(copy_from_user(&mode, argmode, sizeof(struct 
tuner_user_mode))) {
+            mutex_unlock(&info->lock);
+            printk("unable to copy from user!\n");
+            goto efault;
+        }
+        list_for_each(list, &tuner_devlist) {
+            devlist = list_entry(list, struct tuner_dev, devlist);
+            if (devlist->clientid == mode.clientid) {
+                dev = devlist;
+                break;
+            }
+        }
+        if(dev == NULL) {
+            mutex_unlock(&info->lock);
+            goto einval;
+        }
+
+        dev->tuning_params.mode = mode.mode;
+
+        mutex_unlock(&info->lock);
+        goto e_ok;
+    }
+    case TUNER_COMPLETE_CMD:
+    {
+        struct tuner_user_cmd *cmd = (struct tuner_user_cmd *)arg;
+        struct tuner_user_cmd icmd;
+        struct tuner_cmd *qcmd;
+        struct tuner_dev *dev;
+        struct list_head *list;
+        if(copy_from_user(&icmd, cmd, sizeof(struct tuner_user_cmd))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        list_for_each(list, &info->tuner_commands) {
+            qcmd = list_entry(list, struct tuner_cmd, list);
+            if (qcmd->clientid == icmd.clientid) {
+                list_del(&qcmd->list);
+                dev = qcmd->dev;
+                if(qcmd->command == TUNER_CMD_S_FREQ) {
+                    memcpy(&dev->tuning_params, qcmd->data, 
sizeof(struct tuner_user_params));
+                }
+                    
+                atomic_set(&dev->pending, 0);
+                wake_up_interruptible(&dev->waitqueue);
+                mutex_unlock(&info->lock);
+                goto e_ok;
+            }
+        }
+        mutex_unlock(&info->lock);
+        goto einval;
+    }
+
+    /* this is to submit non i2c messages to a driver, eg for resetting 
an i2c chip */
+    case TUNER_SEND_CTRL:
+    {
+        struct tuner_user_ctrl *cmd = (struct tuner_user_ctrl *)arg;
+        struct list_head *list;
+        struct tuner_dev *dev;
+        struct tuner_user_ctrl icmd;
+        if(copy_from_user(&icmd, cmd, sizeof(struct tuner_user_ctrl))) {
+            mutex_unlock(&info->lock);
+            goto efault;
+        }
+        list_for_each(list, &tuner_devlist) {
+            dev = list_entry(list, struct tuner_dev, devlist);
+            if (dev->clientid == icmd.clientid) {
+                if (dev->callback) {
+                    dev->callback(dev->priv, icmd.control, icmd.value);
+                    mutex_unlock(&info->lock);
+                    goto e_ok;
+                } else {
+                    printk("*WARNING* userspace requested a callback, 
but no callback function\n");
+                    printk("*WARNING* is implemented in the driver\n");
+                }
+                break;
+            }
+        }
+        break;
+    }
+    default:
+        printk("unknown command %d!\n",cmd);
+    }
+    mutex_unlock(&info->lock);
+e_ok:
+#ifdef debug
+    printk("left ioctl!\n");
+#endif
+    return 0;
+efault:
+#ifdef debug
+    printk("left ioctl!\n");
+#endif
+    return -EFAULT;
+einval:
+#ifdef debug
+    printk("left ioctl!\n");
+#endif
+    return -EINVAL;
+enomem:
+#ifdef debug
+    printk("left ioctl!\n");
+#endif
+    return -ENOMEM;
+}
+
+static unsigned int tuner_poll(struct file *file, poll_table *wait)
+{
+    unsigned int mask = 0;
+    struct tuner_info *info = file->private_data;
+    poll_wait(file, &info->waitqueue, wait);
+    mutex_lock(&info->lock);
+    if (!list_empty(&info->tuner_commands)) {
+        mask |= POLLIN | POLLRDNORM;
+    }
+    mutex_unlock(&info->lock);
+    return mask;
+}
+
+struct file_operations fops = {
+    .owner = THIS_MODULE,
+    .open = tuner_open,
+    .release = tuner_close,
+    .unlocked_ioctl = tuner_ioctl,
+    .poll = tuner_poll,
+};
+
+static int register_command(struct tuner_dev *dev, struct tuner_info 
*info, int clientid, int cmd, void *data) {
+    struct tuner_cmd intcmd;
+    
+    memset(&intcmd, 0x0, sizeof(struct tuner_cmd));
+    intcmd.clientid = clientid;
+    intcmd.dev = dev;
+    atomic_set(&dev->pending, 1);
+    intcmd.command = cmd;
+    intcmd.data = data;
+    INIT_LIST_HEAD(&intcmd.list);
+
+    list_add(&intcmd.list, &info->tuner_commands);
+
+    /* wake up userspace process to process the commands */
+    wake_up_interruptible(&info->waitqueue);
+    mutex_unlock(&info->lock);
+
+    /* sleep till the userspace daemon is done with the tuner setup,
+       this will block the process which issued the command till it's
+       completed */
+    wait_event_interruptible(dev->waitqueue, 
(atomic_read(&dev->pending) == 0));
+    return 0;
+}
+
+/* wrapper over the i2c command interface
+   clientid ... tuner clientid which got obtained when registering the 
tuner
+   cmd      ... V4L command
+   arg      ... V4L argument
+*/
+
+static unsigned int radio_range[2] = { 65, 108 };
+
+int tuner_v4l_cmd(int clientid, unsigned int cmd, void *arg) {
+    switch(cmd) {
+    case VIDIOC_S_STD:
+    {
+        v4l2_std_id *id = arg;
+        struct tuner_user_params params;
+        memset(&params, 0x0, sizeof(struct tuner_user_params));
+        params.mode = TUNER_STUB_ANALOG_TV;
+        params.std = *id;
+        tuner_run_cmd(clientid, TUNER_CMD_S_FREQ, &params);
+        break;
+    }
+        case AUDC_SET_RADIO:
+        {
+                struct tuner_user_params params;
+                memset(&params, 0x0, sizeof(struct tuner_user_params));
+                params.mode = TUNER_STUB_RADIO;
+        tuner_run_cmd(clientid, TUNER_CMD_S_FREQ, &params);
+                break;
+        }
+        case VIDIOC_G_TUNER:
+        {
+                struct v4l2_tuner *tuner = arg;
+                enum tuner_user_type mode;
+        struct tuner_user_status status;
+                tuner_run_cmd(clientid, TUNER_CMD_G_MODE, &mode);
+                switch(mode) {
+                case TUNER_STUB_RADIO:
+                        tuner->type = V4L2_TUNER_RADIO;
+                        break;
+                case TUNER_STUB_ANALOG_TV:
+                        tuner->type = V4L2_TUNER_ANALOG_TV;
+                        break;
+                default:
+                        printk("tuner-stub.c: FIXME: something unknown 
is requested\n");
+                }
+
+                /* has_signal */
+
+                /* is_stereo */
+                tuner_run_cmd(clientid, TUNER_CMD_G_STATUS, &status);
+
+                tuner->capability |= V4L2_TUNER_CAP_STEREO;
+        if (mode == TUNER_STUB_RADIO)
+            tuner->capability |= V4L2_TUNER_CAP_LOW;
+                tuner->rangelow  = radio_range[0] * 16000;
+                tuner->rangehigh = radio_range[1] * 16000;
+                break;
+        }
+    case VIDIOC_S_TUNER:
+    {
+#if 0
+        struct v4l2_tuner *tuner = arg;
+#endif
+        struct tuner_user_params params;
+        enum tuner_user_type mode;
+
+        tuner_run_cmd(clientid, TUNER_CMD_G_MODE, &mode);
+
+        memset(&params, 0x0, sizeof(struct tuner_user_params));
+
+        if (mode != V4L2_TUNER_RADIO)
+            break;
+
+        tuner_run_cmd(clientid, TUNER_CMD_S_FREQ, &params);
+        break;
+    }
+    case VIDIOC_S_FREQUENCY:
+    {
+        struct v4l2_frequency *f = arg;
+        struct tuner_user_params params;
+        enum tuner_user_type mode;
+        tuner_run_cmd(clientid, TUNER_CMD_G_MODE, &mode);
+        memset(&params, 0x0, sizeof(struct tuner_user_params));
+        params.mode = mode;
+        params.frequency = f->frequency;
+        tuner_run_cmd(clientid, TUNER_CMD_S_FREQ, &params);
+        break;
+    }
+    case VIDIOC_G_FREQUENCY:
+    {
+                struct v4l2_frequency *f = arg;
+                memset(f, 0, sizeof(*f));
+                f->type = V4L2_TUNER_ANALOG_TV;
+        tuner_run_cmd(clientid, TUNER_CMD_G_FREQ, &f->frequency);
+        break;
+    }
+        /* low priority */
+    case VIDIOC_LOG_STATUS:
+        break;
+    default:
+        printk("tuner_v4l_cmd: unhandled\n");
+    }
+    return 0;
+}
+
+int tuner_run_cmd(int clientid, int cmd, void *data)
+{
+    struct list_head *list;
+    struct tuner_dev *cmddev, *dev=0;
+    struct tuner_info *info = 0;
+    int tuner_found = 0;
+    mutex_lock(&tuner_lock);
+    list_for_each(list, &tuner_devlist) {
+        cmddev = list_entry(list, struct tuner_dev, devlist);
+        if(cmddev->clientid == clientid) {
+            dev = cmddev;
+            break;
+        }
+    }
+
+    if(dev == NULL) {
+        mutex_unlock(&tuner_lock);
+        return -EINVAL;
+    }
+
+    list_for_each(list, &tuner_clients) {
+        info = list_entry(list, struct tuner_info, list);
+        if (info->tunerid == dev->tunerid) {
+            tuner_found = 1;
+            break;
+        }
+    }
+
+    if(tuner_found == 0) {
+        mutex_unlock(&tuner_lock);
+        return -EINVAL;
+    }
+
+    mutex_unlock(&tuner_lock);
+    mutex_lock(&info->lock);
+
+    switch(cmd) {
+    case TUNER_CMD_G_FREQ:
+    {
+        __u32 *frequency = (__u32*)data;
+        *frequency = dev->tuning_params.frequency;
+        mutex_unlock(&info->lock);
+        break;
+    }
+    case TUNER_CMD_G_BANDWIDTH:
+    {
+        __u32 *bandwidth = (__u32*)data;
+        switch (dev->tuning_params.mode) {
+        case TUNER_STUB_DVBT_TV:
+            *bandwidth = dev->tuning_params.u.ofdm.bandwidth;
+        default:
+            printk("TODO TUNER_CMD_G_BANDWIDTH!\n");
+        }
+        mutex_unlock(&info->lock);
+        break;
+    }
+    case TUNER_CMD_S_STD:
+    {
+        struct tuner_user_params params;
+        cmd = TUNER_CMD_S_FREQ; /* rewrite command */
+        memcpy(&params, &dev->tuning_params, sizeof(struct 
tuner_user_params));
+        params.std = *(__u64*)data;
+        register_command(dev, info, clientid, cmd, data);
+        break;
+    }
+    case TUNER_CMD_INIT:
+    {
+        dev->mode = (enum tuner_user_type)data;
+        register_command(dev, info, clientid, cmd, data);
+        break;
+    }
+    case TUNER_CMD_S_FREQ:
+    {
+        struct tuner_user_params *params = (struct tuner_user_params 
*)data;
+        dev->mode = params->mode;
+
+        if (params->frequency == 0)
+            params->frequency = dev->tuning_params.frequency;
+
+        if (params->mode == 0)
+            params->mode = dev->tuning_params.mode;
+
+        if (params->std == 0)
+            params->std = dev->tuning_params.std;
+        
+        register_command(dev, info, clientid, cmd, data);
+        break;
+    }
+    case TUNER_CMD_G_STATUS:
+    {
+        register_command(dev, info, clientid, cmd, data);
+        break;
+    }
+    case TUNER_CMD_G_MODE:
+    {
+        enum tuner_user_type *mode = (enum tuner_user_type *)data;
+        *mode = dev->mode;
+        mutex_unlock(&info->lock);
+        break;
+    }
+    case TUNER_CMD_G_DVBINFO:
+    {
+        struct dvb_tuner_info *dvbinfo = (struct dvb_tuner_info *)data;
+
+        strncpy(dvbinfo->name, info->device, 50);
+        if(dvbinfo->frequency_min)
+            dvbinfo->frequency_min = info->frequency_min;
+        if(dvbinfo->frequency_max)
+            dvbinfo->frequency_max = info->frequency_max;
+        if(dvbinfo->frequency_step)
+            dvbinfo->frequency_step = info->frequency_step;
+        
+        if(dvbinfo->bandwidth_min)
+            dvbinfo->bandwidth_min = info->bandwidth_min;
+        if(dvbinfo->bandwidth_max)
+            dvbinfo->bandwidth_max = info->bandwidth_max;
+        if(dvbinfo->bandwidth_step)
+            dvbinfo->bandwidth_step = info->bandwidth_step;
+        
+        mutex_unlock(&info->lock);
+        return 0;
+    }
+    case TUNER_CMD_CALC_REGS:
+        printk("unhandled CALC_REGS!\n");
+        mutex_unlock(&info->lock);
+        break;
+    case TUNER_CMD_SLEEP:
+        printk("unhandled: CMD_SLEEP\n");
+        mutex_unlock(&info->lock);
+        break;
+    case TUNER_CMD_S_BANDWIDTH:
+        printk("unhandled: S_BANDWIDTH\n");
+        mutex_unlock(&info->lock);
+        break;
+    case TUNER_CMD_S_GATE:
+        printk("unhandled: S_GATE\n");
+        mutex_unlock(&info->lock);
+        break;
+    case TUNER_CMD_S_MODE:
+        printk("unhandled: S_MODE\n");
+        mutex_unlock(&info->lock);
+        break;
+    default:
+        printk("unhandled command: %d\n",cmd);
+        mutex_unlock(&info->lock);
+        break;
+    }
+    return 0;
+}
+
+int tuner_request_module(int tunerid) {
+    struct tuner_dev dev;
+    struct list_head *list;
+    struct tuner_info *info;
+    struct tuner_daemon_cmd intcmd;
+    int daemon_found = 0;
+
+    memset(&dev, 0x0, sizeof(struct tuner_dev));
+
+    init_waitqueue_head(&dev.waitqueue);
+    
+    list_for_each(list, &tuner_clients) {
+        info = list_entry(list, struct tuner_info, list);
+        if (info->client_type == TUNER_DAEMON) {
+            daemon_found = 1;
+            break;
+        }
+    }
+
+    if (daemon_found == 0) {
+        printk("tuner daemon not initialized!\n");
+        return -EINVAL;
+    }
+    
+    mutex_lock(&info->lock);
+
+    intcmd.clientid = 0;
+    intcmd.dev = &dev;
+    atomic_set(&dev.pending, 1);
+    intcmd.command = TUNER_LOAD_MODULE;
+    intcmd.tunerid = tunerid;
+    INIT_LIST_HEAD(&intcmd.list);
+
+    list_add(&intcmd.list, &info->tuner_commands);
+
+    /* wake up userspace process to process the commands */
+    wake_up_interruptible(&info->waitqueue);
+    mutex_unlock(&info->lock);
+
+    /* sleep till the userspace daemon is done with the tuner setup,
+       this will block the process which issued the command till it's
+       completed */
+
+    wait_event_interruptible(dev.waitqueue, atomic_read(&dev.pending) 
== 0);
+
+    return intcmd.retval;
+}
+
+int tuner_register_client(struct i2c_adapter *adap, int 
(*tuner_callback)(void *priv, int cmd, int data), void *priv, struct 
tuner_config *config)
+{
+    struct tuner_dev *dev = NULL, *listdev;
+    int id;
+    struct list_head *list;
+    struct tuner_info *info;
+    int tuner_found=0;
+
+    mutex_lock(&tuner_lock);
+
+    /* check if tuner is already loaded */
+    list_for_each(list, &tuner_clients) {
+        info = list_entry(list, struct tuner_info, list);
+        if (info->tunerid == config->tunerid && info->sclient == 0) {
+            tuner_found = 1;
+            break;
+        }
+    }
+
+    if (tuner_found == 0) {
+        printk("requested tuner client %d is not attached!\n", 
config->tunerid);
+        list_for_each(list, &tuner_clients) {
+            info = list_entry(list, struct tuner_info, list);
+            if(info->client_type == TUNER_DAEMON) {
+                break;
+            }
+        }
+
+        mutex_unlock(&tuner_lock);
+
+        tuner_request_module(config->tunerid);
+
+        mutex_lock(&tuner_lock);
+
+        /* retry if the tuner is available now */
+        list_for_each(list, &tuner_clients) {
+            info = list_entry(list, struct tuner_info, list);
+            if (info->tunerid == config->tunerid && info->sclient == 0) {
+                tuner_found = 1;
+                break;
+            }
+        }
+
+        if (tuner_found == 0) {
+            printk("unable to attach tuner client %d is not 
attached!\n", config->tunerid);
+            mutex_unlock(&tuner_lock);
+            return 0;
+        }
+    }
+
+    if (config->clientid > 0) {
+        list_for_each(list, &tuner_devlist) {
+            listdev = list_entry(list, struct tuner_dev, devlist);
+            if(listdev->clientid == config->clientid)
+                dev = listdev;
+                break;
+        }
+        if (dev == NULL) {
+            printk("tuner-stub: invalid clientid %d\n", config->clientid);
+            mutex_unlock(&tuner_lock);
+            return 0;
+        }
+        dev->users++;
+    } else {
+        id = find_first_zero_bit(&tuner_devices, sizeof(unsigned long)*8);
+
+        if (id == sizeof(unsigned long)*8) {
+            mutex_unlock(&tuner_lock);
+            return 0;
+        }
+        tuner_devices|=1<<id;
+
+        dev = kzalloc(sizeof(struct tuner_dev), GFP_KERNEL);
+        dev->clientid = id;
+        dev->adap = adap;
+        dev->priv = priv;
+        dev->callback = tuner_callback;
+        init_waitqueue_head(&dev->waitqueue);
+        dev->tunerid = config->tunerid;
+        dev->users=1;
+
+        INIT_LIST_HEAD(&dev->devlist);
+
+        list_add(&dev->devlist, &tuner_devlist);
+    }
+    info->sclient = dev->clientid;
+
+    mutex_unlock(&tuner_lock);
+
+    return dev->clientid;
+}
+
+int tuner_unregister_client(int clientid)
+{
+    struct list_head *list;
+    struct tuner_dev *dev;
+    struct tuner_info *tuner_info;
+
+    mutex_lock(&tuner_lock);
+    list_for_each(list, &tuner_clients) {
+        tuner_info = list_entry(list, struct tuner_info, list);
+        if (tuner_info->sclient == clientid) {
+            tuner_info->sclient = 0;
+        }
+    }
+    list_for_each(list, &tuner_devlist) {
+        dev = list_entry(list, struct tuner_dev, devlist);
+        if(dev->clientid == clientid) {
+            tuner_devices&=~(1<<clientid);
+            /* only free if the last user is gone */
+            if(--dev->users == 0) {
+                list_del(&dev->devlist);
+                kfree(dev);
+                mutex_unlock(&tuner_lock);
+                return 0;
+            }
+        }
+    }
+    mutex_unlock(&tuner_lock);
+    return 0;
+}
+
+/* DVB part */
+
+struct tuner_priv {
+    int clientid;
+};
+
+static int tuner_stub_set_params(struct dvb_frontend *fe, struct 
dvb_frontend_parameters *params)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_S_FREQ, V4L_OPS(params));
+        return 0;
+}
+
+static int tuner_stub_release(struct dvb_frontend *fe)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+    fe->tuner_priv = NULL;
+        tuner_unregister_client(priv->clientid);
+    kfree(priv);
+        return 0;
+}
+
+static int tuner_stub_init(struct dvb_frontend *fe)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_INIT, 
(void*)TUNER_STUB_DVBT_TV);
+        return 0;
+}
+
+static int tuner_stub_sleep(struct dvb_frontend *fe)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_SLEEP, NULL);
+        return 0;
+}
+
+static int tuner_stub_set_frequency(struct dvb_frontend *fe, u32 frequency)
+{
+    struct tuner_priv *priv = (struct tuner_priv *)fe->tuner_priv;
+    struct tuner_user_params params;
+
+    memset(&params, 0x0, sizeof(struct tuner_user_params));
+    params.mode = TUNER_STUB_DVBT_TV;
+    params.frequency = frequency;
+
+        tuner_run_cmd(priv->clientid, TUNER_CMD_S_FREQ, &params);
+        return 0;
+}
+
+static int tuner_stub_set_bandwidth(struct dvb_frontend *fe, u32 bandwidth)
+{
+
+    printk("TODO set_bandwidth is unhandled\n");
+#if 0
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_S_BANDWIDTH, 
(void*)bandwidth);
+#endif
+        return 0;
+}
+
+static int tuner_stub_get_frequency(struct dvb_frontend *fe, u32 
*frequency)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_G_FREQ, frequency);
+        return 0;
+}
+
+static int tuner_stub_get_bandwidth(struct dvb_frontend *fe, u32 
*bandwidth)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        tuner_run_cmd(priv->clientid, TUNER_CMD_G_BANDWIDTH, bandwidth);
+        return 0;
+}
+
+static int tuner_stub_calc_regs(struct dvb_frontend *fe, struct 
dvb_frontend_parameters *p, u8 *buf, int buf_len)
+{
+    struct tuner_priv *priv = fe->tuner_priv;
+        struct tuner_calc_params dvb_params;
+        dvb_params.params = V4L_OPS(p);
+        dvb_params.buf = buf;
+        dvb_params.buf_len = buf_len;
+
+        tuner_run_cmd(priv->clientid, TUNER_CMD_CALC_REGS, &dvb_params);
+        return 0;
+}
+
+static int tuner_stub_get_status(struct dvb_frontend *fe, u32 *status)
+{
+    struct tuner_user_status __status;
+    struct tuner_priv *priv = fe->tuner_priv;
+    
+        tuner_run_cmd(priv->clientid, TUNER_CMD_G_STATUS, &__status);
+
+        /* TODO: bogus conversion here */
+    if (__status.lock_status)
+        *status |= 1;
+
+        return 0;
+}
+
+static const struct dvb_tuner_ops tuner_stub_ops = {
+        .info = {
+                .name           = "tuner stub",
+                .frequency_min  =  48000000,
+                .frequency_max  = 860000000,
+                .frequency_step =     50000,
+        },
+
+        .release       = tuner_stub_release,
+        .init          = tuner_stub_init,
+        .sleep         = tuner_stub_sleep,
+        .set_params    = tuner_stub_set_params,
+        .get_frequency = tuner_stub_get_frequency,
+        .get_bandwidth = tuner_stub_get_bandwidth,
+        .set_frequency = tuner_stub_set_frequency,
+        .set_bandwidth = tuner_stub_set_bandwidth,
+        .calc_regs     = tuner_stub_calc_regs,
+        .get_status    = tuner_stub_get_status
+};
+
+struct dvb_frontend *tuner_stub_attach(struct dvb_frontend *fe, struct 
i2c_adapter *i2c, int (*tuner_callback)(void *priv, int cmd, int data), 
void *priv, struct tuner_config *config)
+{
+        int id;
+    struct tuner_priv *tpriv;
+        id = tuner_register_client(i2c, tuner_callback, priv, config);
+
+        if (id<=0)
+                return NULL;
+
+    tpriv = kzalloc(sizeof(struct tuner_priv), GFP_KERNEL);
+
+    tpriv->clientid = id;
+
+        memcpy(&fe->ops.tuner_ops, &tuner_stub_ops, sizeof(struct 
dvb_tuner_ops));
+
+        tuner_run_cmd(tpriv->clientid, TUNER_CMD_G_DVBINFO, 
&fe->ops.tuner_ops.info);
+
+        return fe;
+}
+
+static int __init tuner_init(void)
+{
+    tuner_major = register_chrdev(0, "tuner", &fops);
+    
+    tuner_class = class_create(THIS_MODULE, "tuner");
+
+    if (!tuner_class)
+        return -EINVAL;
+
+    cdev = class_device_create(tuner_class, NULL, MKDEV(tuner_major, 
0), NULL, "tuner");
+
+    /* the first tunerid (0) is reserved for the userspace daemon */
+    tuner_devices=1;
+
+    return 0;
+}
+
+static void __exit tuner_exit(void)
+{
+    class_device_destroy(tuner_class, MKDEV(tuner_major, 0));
+    unregister_chrdev(0, "tuner");
+    class_destroy(tuner_class);
+}
+
+EXPORT_SYMBOL_GPL(tuner_register_client);
+EXPORT_SYMBOL_GPL(tuner_unregister_client);
+EXPORT_SYMBOL_GPL(tuner_run_cmd);
+EXPORT_SYMBOL_GPL(tuner_v4l_cmd);
+EXPORT_SYMBOL_GPL(tuner_stub_attach);
+
+MODULE_AUTHOR("Markus Rechberger <markus.rechberger at amd.com>");
+MODULE_DESCRIPTION("Userspace i2c tuner interface");
+MODULE_LICENSE("GPL");
+
+module_init(tuner_init);
+module_exit(tuner_exit);
diff --git a/include/linux/tuner-stub.h b/include/linux/tuner-stub.h
new file mode 100644
index 0000000..8a7698d
--- /dev/null
+++ b/include/linux/tuner-stub.h
@@ -0,0 +1,275 @@
+#ifndef _TUNER_STUB_H
+#define _TUNER_STUB_H
+
+#define TC_TUNER_XC3028 1
+#define TC_TUNER_QT1010 2
+#define TC_TUNER_MT2060 3
+
+enum tuner_user_type {
+        TUNER_STUB_RADIO             = 1,
+        TUNER_STUB_ANALOG_TV         = 2,
+        TUNER_STUB_DIGITAL_TV        = 3,
+        TUNER_STUB_DVBT_TV           = 4,
+        TUNER_STUB_DVBC_TV           = 5,
+        TUNER_STUB_ATSC_TV           = 6,
+        TUNER_STUB_DVBS_TV           = 7,
+        TUNER_STUB_NONE              = 8
+};
+#include <linux/dvb/frontend.h>
+
+/* userspace definitions */
+#define TUNER_CMD_INIT        1
+#define TUNER_CMD_S_FREQ      2
+#define TUNER_CMD_G_FREQ      3
+#define TUNER_CMD_G_STATUS    4
+#define TUNER_CMD_S_STD       5
+#define TUNER_CMD_S_MODE      6
+#define TUNER_CMD_G_MODE      7
+#define TUNER_CMD_S_GATE      8
+#define TUNER_CMD_G_BANDWIDTH 9
+#define TUNER_CMD_S_BANDWIDTH 10
+#define TUNER_CMD_SLEEP       11
+#define TUNER_CMD_CALC_REGS   12
+#define TUNER_CMD_G_DVBINFO   13
+
+/* tuner client/daemon definitions */
+#define TUNER_LOAD_MODULE     14
+
+struct tuner_user_params {
+    int clientid;
+        __u32 frequency;     /* (absolute) frequency in Hz for 
QAM/OFDM/ATSC */
+                             /* intermediate frequency in kHz for QPSK */
+        enum tuner_user_type  mode;
+        __u64             std;
+        fe_spectral_inversion_t inversion;
+        union {
+                struct dvb_qpsk_parameters qpsk;
+                struct dvb_qam_parameters  qam;
+                struct dvb_ofdm_parameters ofdm;
+                struct dvb_vsb_parameters vsb;
+        } u;
+};
+
+struct tuner_user_cmd {
+    int commandid;
+    int clientid;
+    int tunerid;
+    int command;
+    void *data;
+};
+
+struct tuner_user_ctrl {
+    int clientid;
+    int control;
+    int value;
+};
+
+struct tuner_i2c_cmd {
+    int clientid;
+    char *buffer;
+    int address;
+};
+
+#define TUNER_CAP_DVBT     0x01
+#define TUNER_CAP_DVBC     0x02
+#define TUNER_CAP_DVBS     0x04
+#define TUNER_CAP_ATSC     0x08
+#define TUNER_CAP_ANALOGTV 0x10
+#define TUNER_CAP_RADIO    0x20
+    
+struct tuner_user_info {
+    int tunerid;
+    int version;
+    char company[50];
+    char device[50];
+    char copyright[100];
+    int cap;
+    __u32 frequency_min;
+    __u32 frequency_max;
+    __u32 frequency_step;
+
+    __u32 bandwidth_min;
+    __u32 bandwidth_max;
+    __u32 bandwidth_step;
+};
+
+struct tuner_i2c_msg {
+    int clientid;
+    __u16 len;
+    __u8 addr;
+    __u8 *buf;
+};
+
+#define TUNER_LOCKED 1
+
+struct tuner_user_status {
+    int clientid;
+    int lock_status; /* bitmap */
+    int audmode;
+    int rxsubchans;
+    int capability;
+};
+
+struct tuner_daemon_user_cmd {
+    int clientid;
+    int tunerid;
+    int command;
+    void *data;
+    int retval;
+};
+
+struct tuner_user_mode {
+    int clientid;
+    enum tuner_user_type mode;
+};
+
+#define TUNER_GET_VERSION              _IOR  ('V', 0, int)
+#define TUNER_GET_CMD                   _IOR  ('V', 1, struct 
tuner_user_cmd)
+#define TUNER_SEND_I2C_CMD             _IOWR ('V', 2, struct tuner_i2c_msg)
+#define TUNER_COMPLETE_CMD             _IOR  ('V', 3, struct 
tuner_user_cmd)
+#define TUNER_SEND_CTRL                _IOWR ('V', 4, struct 
tuner_user_ctrl)
+#define TUNER_ADD_TUNER                _IOWR ('V', 5, struct 
tuner_user_info)
+#define TUNER_GET_TUNING_PARAM         _IOR  ('V', 6, struct 
tuner_user_params)
+#define TUNER_GET_CURRENT_TUNING_PARAM _IOR  ('V', 7, struct 
tuner_user_params)
+#define TUNER_SET_STATUS               _IOWR ('V', 8, struct 
tuner_user_status)
+#define TUNER_READ_I2C_CMD             _IOWR ('V', 9, struct tuner_i2c_msg)
+#define TUNER_ADD_DAEMON               _IO   ('V', 10)
+#define TUNER_DAEMON_GET_CMD           _IOR  ('V', 11, struct 
tuner_daemon_user_cmd)
+#define TUNER_SET_MODE                 _IOR  ('V', 12, struct 
tuner_user_mode)
+#define TUNER_DAEMON_COMPLETE_CMD      _IOR  ('V', 13, struct 
tuner_user_cmd)
+
+#ifdef __KERNEL__
+
+struct tuner_config {
+    int tunerid;   /* tuner id eg. TC_TUNER_XC3028 */
+    int clientid;  /* clientid for sharing tuners */
+    unsigned int vendorid;  /* passing to userspace */
+    unsigned int productid; /* passing to userspace */
+    unsigned int config;    /* passing to userspace */
+};
+    
+struct dvb_frontend;
+struct i2c_adapter;
+
+extern int tuner_register_client(struct i2c_adapter *adap, int 
(*tuner_callback)(void *priv, int cmd, int data), void *priv, struct 
tuner_config *config);
+extern int tuner_unregister_client(int id);
+extern int tuner_run_cmd(int id, int cmd, void *data);
+extern int tuner_v4l_cmd(int clientid, unsigned int cmd, void *arg);
+extern struct dvb_frontend * tuner_stub_attach(struct dvb_frontend *fe, 
struct i2c_adapter *i2c, int (*tuner_callback)(void *priv, int cmd, int 
data), void *priv, struct tuner_config *config);
+
+struct tuner_dev {
+    int users;
+    void *priv;
+    int tunerid;
+    int clientid;
+    unsigned int config; /* device configuration */
+    unsigned int vendorid;
+    unsigned int productid;
+    atomic_t pending;
+    struct i2c_adapter *adap;
+    struct list_head devlist;
+    struct mutex lock;
+    enum tuner_user_type mode;
+    /* current tuning params */
+    struct tuner_user_params tuning_params;
+    wait_queue_head_t waitqueue;
+    int (*callback)(void *priv, int command, int value);
+};
+
+/* in kernelspace we also store the wait queue and list of tuner 
commands */
+struct tuner_cmd {
+    int commandid;
+    int clientid;
+    int tunerid;
+    int command;
+    void *data;
+    struct tuner_dev *dev;
+    struct list_head list;
+};
+
+struct tuner_daemon_cmd {
+    int clientid;
+    int tunerid;
+    int command;
+    void *data;
+    int retval;
+    struct tuner_dev *dev;
+    struct list_head list;
+};
+
+struct tuner_calc_params {
+        struct tuner_user_params *params;
+        u8 *buf;
+        int buf_len;
+};
+
+struct tuner_info {
+    int tunerid;
+    int version;
+    char company[50];
+    char device[50];
+    char copyright[100];
+    int client_type;
+#define TUNER_UNDEF  0 /* default when allocating that struct */
+#define TUNER_DAEMON 1
+#define TUNER_CLIENT 2
+    int cap; /* capabilities  (DVB tuner, analog tuner, etc) */
+
+    unsigned int sclient;
+
+    /* DVB information */
+    __u32 frequency_min;
+    __u32 frequency_max;
+    __u32 frequency_step;
+
+    __u32 bandwidth_min;
+    __u32 bandwidth_max;
+    __u32 bandwidth_step;
+
+    wait_queue_head_t waitqueue;
+
+    struct mutex lock;
+    struct list_head list;
+    struct list_head tuner_commands;
+};
+
+struct tuner_int_params {
+        __u32 frequency;     /* (absolute) frequency in Hz for 
QAM/OFDM/ATSC */
+                             /* intermediate frequency in kHz for QPSK */
+        enum tuner_user_type    mode;
+        __u64             std;
+        fe_spectral_inversion_t inversion;
+        union {
+                struct dvb_qpsk_parameters qpsk;
+                struct dvb_qam_parameters  qam;
+                struct dvb_ofdm_parameters ofdm;
+                struct dvb_vsb_parameters vsb;
+        } u;
+};
+
+#define V4L_OPS(i) ({ \
+        struct tuner_user_params __o; \
+    __o.clientid  = priv->clientid; \
+        __o.frequency = i->frequency; \
+        __o.inversion = i->inversion; \
+        __o.std       = (__u64)0; \
+        switch(fe->ops.info.type) { \
+                case FE_OFDM: \
+                        __o.mode = TUNER_STUB_DVBT_TV; \
+                        break; \
+                case FE_QAM: \
+                        __o.mode = TUNER_STUB_DVBC_TV; \
+                        break; \
+                case FE_ATSC: \
+                        __o.mode = TUNER_STUB_ATSC_TV; \
+                        break; \
+                case FE_QPSK: \
+                        __o.mode = TUNER_STUB_DVBS_TV; \
+                        break; \
+        } \
+        memcpy(&__o.u, &i->u, sizeof(__o.u)); \
+        &__o; \
+})
+#endif
+
+#endif





More information about the linux-dvb mailing list