linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH 1/7] Adding empia base driver
       [not found] ` <20081024153509.0f51d676@pedra.chehab.org>
@ 2008-10-24 20:15   ` Markus Rechberger
  2008-10-24 20:20     ` Markus Rechberger
  0 siblings, 1 reply; 29+ messages in thread
From: Markus Rechberger @ 2008-10-24 20:15 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Linux Kernel Mailing List, em28xx

On Fri, Oct 24, 2008 at 7:35 PM, Mauro Carvalho Chehab
<mchehab@infradead.org> wrote:
> On Wed, 22 Oct 2008 22:59:00 +0200
> "Markus Rechberger" <mrechberger@gmail.com> wrote:
>
>>     em2880-dvb:
>>     * supporting the digital part of Empia based devices, which
>> includes ATSC, ISDB-T and DVB-T
>>
>>     em28xx-aad.c:
>>     * alternative audio driver, can be used instead of em28xx-audio if
>> alsa is not available
>>     or not compiled into the kernel, it provides a raw interface to
>> the PCM samples
>>
>>     em28xx-audio.c:
>>     * em28xx alsa driver and audio driver for FM radio
>>
>>     em28xx-audioep.c:
>>     * em28xx alsa driver for devices which are set to vendor specific
>> audio on interface 1,
>>     in that case snd-usb-audio will not attach to the interface and
>> em28xx-audioep will be needed
>>
>>     em28xx-cards.c:
>>     * card definition and initial setup of devices.
>>
>>     em28xx-core.c:
>>     * core videohandling and VBI frame slicing
>>
>>     em28xx-i2c.c:
>>     * i2c setup and GPIO setup handling of the devices (including
>> em2888 based ones)
>>
>>     em28xx-input.c:
>>     * currently mostly disabled since the linuxtv input handling is
>> broken by design and racy
>>
>>     em28xx-keymaps.c:
>>     * keymap references of some remotes (could be merged into
>> ir-common, although as mentioned
>>     this should be in userland done by lirc).
>>
>>     em28xx-video.c:
>>     * inode handling for analog TV, radio and VBI, also some device probing
>>
>>     em28xx-webcam.c:
>>     * videology webcam specific i2c commands
>
> NACK.
>
> There's already a driver for em28xx. Be welcome sending incremental patches to
> improve, like other developers do. But another driver for the same chip would
> just create a mess.
>

the description right above shows up what the current driver is
missing, excluding the bugfixes.
Technical reasons why the source is basically kept in the structure as
it is, is eg. higher backward
compatibility without having to update the whole system. See eeePC
packages which are available
from some vendors. The driver seamlessly works with the rest which is
installed there, without
any framework upgrade.

Most users are currently using the drivers from mcentral.de since it's
more stable and very
well tested over the last 3 years. It was basically your decision to
not merge it back then
http://mcentral.de/v4l-dvb/

I pulled out the source and moved it together then and worked on
additional device support.

http://mcentral.de/hg/~mrec/em28xx-new/shortlog
There are more than 200 changesets pointing out how it evolved, if
someone wants to have an indepth
view about it. Bugreports and patches have been posted to the em28xx
mailinglist where people worked
on it, including enduser applications.

The xc3028 as it is in the kernel is based on leaked and partly
reverse engineered information.
I know that because I was also CC'ed with the leaked driver information.

The Xceive drivers which I submitted are the latest versions from
Xceive addressing several bugs,
you might not have access to their changelog.

Before continuing any discussion the sourcecode and every statement I
made for those patchsets
should be commented, otherwise a discussion won't go anywhere.

br,
Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-24 20:15   ` [PATCH 1/7] Adding empia base driver Markus Rechberger
@ 2008-10-24 20:20     ` Markus Rechberger
  0 siblings, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-10-24 20:20 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Linux Kernel Mailing List, em28xx

On Fri, Oct 24, 2008 at 10:15 PM, Markus Rechberger
<mrechberger@gmail.com> wrote:
> On Fri, Oct 24, 2008 at 7:35 PM, Mauro Carvalho Chehab
> <mchehab@infradead.org> wrote:
>> On Wed, 22 Oct 2008 22:59:00 +0200
>> "Markus Rechberger" <mrechberger@gmail.com> wrote:
>>
>>>     em2880-dvb:
>>>     * supporting the digital part of Empia based devices, which
>>> includes ATSC, ISDB-T and DVB-T
>>>
>>>     em28xx-aad.c:
>>>     * alternative audio driver, can be used instead of em28xx-audio if
>>> alsa is not available
>>>     or not compiled into the kernel, it provides a raw interface to
>>> the PCM samples
>>>
>>>     em28xx-audio.c:
>>>     * em28xx alsa driver and audio driver for FM radio
>>>
>>>     em28xx-audioep.c:
>>>     * em28xx alsa driver for devices which are set to vendor specific
>>> audio on interface 1,
>>>     in that case snd-usb-audio will not attach to the interface and
>>> em28xx-audioep will be needed
>>>
>>>     em28xx-cards.c:
>>>     * card definition and initial setup of devices.
>>>
>>>     em28xx-core.c:
>>>     * core videohandling and VBI frame slicing
>>>
>>>     em28xx-i2c.c:
>>>     * i2c setup and GPIO setup handling of the devices (including
>>> em2888 based ones)
>>>
>>>     em28xx-input.c:
>>>     * currently mostly disabled since the linuxtv input handling is
>>> broken by design and racy
>>>
>>>     em28xx-keymaps.c:
>>>     * keymap references of some remotes (could be merged into
>>> ir-common, although as mentioned
>>>     this should be in userland done by lirc).
>>>
>>>     em28xx-video.c:
>>>     * inode handling for analog TV, radio and VBI, also some device probing
>>>
>>>     em28xx-webcam.c:
>>>     * videology webcam specific i2c commands
>>
>> NACK.
>>
>> There's already a driver for em28xx. Be welcome sending incremental patches to
>> improve, like other developers do. But another driver for the same chip would
>> just create a mess.
>>
>
> the description right above shows up what the current driver is
> missing, excluding the bugfixes.
> Technical reasons why the source is basically kept in the structure as
> it is, is eg. higher backward
> compatibility without having to update the whole system. See eeePC
> packages which are available
> from some vendors. The driver seamlessly works with the rest which is
> installed there, without
> any framework upgrade.
>
> Most users are currently using the drivers from mcentral.de since it's
> more stable and very
> well tested over the last 3 years. It was basically your decision to
> not merge it back then
> http://mcentral.de/v4l-dvb/
>
> I pulled out the source and moved it together then and worked on
> additional device support.
>
> http://mcentral.de/hg/~mrec/em28xx-new/shortlog
> There are more than 200 changesets pointing out how it evolved, if
> someone wants to have an indepth
> view about it. Bugreports and patches have been posted to the em28xx
> mailinglist where people worked
> on it, including enduser applications.
>
> The xc3028 as it is in the kernel is based on leaked and partly
> reverse engineered information.
> I know that because I was also CC'ed with the leaked driver information.
>
> The Xceive drivers which I submitted are the latest versions from
> Xceive addressing several bugs,
> you might not have access to their changelog.
>
> Before continuing any discussion the sourcecode and every statement I
> made for those patchsets
> should be commented, otherwise a discussion won't go anywhere.
>

*to avoid any misunderstanding here, this is not about using
chipdrivers from userspace*

> br,
> Markus
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-28 20:09           ` Greg KH
@ 2008-11-30  6:23             ` Markus Rechberger
  0 siblings, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-11-30  6:23 UTC (permalink / raw)
  To: Greg KH; +Cc: Pekka Enberg, Aidan Thornton, Linux Kernel Mailing List

On Fri, Nov 28, 2008 at 9:09 PM, Greg KH <greg@kroah.com> wrote:
> On Thu, Nov 27, 2008 at 11:33:03AM +0200, Pekka Enberg wrote:
>> On Thu, Nov 27, 2008 at 6:25 AM, Markus Rechberger
>> <mrechberger@gmail.com> wrote:
>> > I simply don't have the time for those games anymore, and those people here who have personal
>> > issues with me and/or my work don't pay my monthly bills, neither do they have any clue how that
>> > driver is used with some systems of certain customers. I constantly deliver full solutions, and not
>> > driver code only. Neither am I limited to Linux only.
>>
>> Again, it's not personal, it's the way we do things here. But you
>> should already know that by now so whatever.
>>
>> What puzzles me the most here is why isn't Empia simply providing
>> datasheets to other developers as well. I mean, even the GPU and
>> wireless vendors know how to play well with the community now, so
>> what's the problem? Being locked to just one developer who's unwilling
>> to cooperate doesn't sound like a winning strategy to me.
>>
>> Greg, is there any reason we don't have Markus' out-of-tree driver
>> listed here, btw:
>>
>>   http://www.linuxdriverproject.org/twiki/bin/view/Main/OutOfTreeDrivers
>
> It's a wiki, feel free to add it yourself :)
>
> thanks,
>
> greg k-h
>

hah I fully understand now. I've been blind. There's no better way to
go than the current one for
someone here who recently got hired at Redhat even if he doesn't
deserve it for real.

I hope some other people are able to convince me about OSS again. I
have alot respect of
ffmpeg people actually. Writing v4l drivers is damn easy actually
callbacks here callbacks there
learning some structs and dumping data 1:1 to userland and someone is
done already.

This is the second project which goes that way, the first one was
polite enough to just take my
code and remove the copyrights .. after noticing this - after a year-
I got a nice reply that the work
was inspired by even when containing the same comments...

this is my last email to LKML, even when continuing to support Linux I
won't loose focus at other
operating systems. Thanks for teaching me about GPL stuff, I now have
more faith into the BSD
license if at all in the OSS area.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-27  9:33         ` Pekka Enberg
  2008-11-28 15:48           ` Devin Heitmueller
@ 2008-11-28 20:09           ` Greg KH
  2008-11-30  6:23             ` Markus Rechberger
  1 sibling, 1 reply; 29+ messages in thread
From: Greg KH @ 2008-11-28 20:09 UTC (permalink / raw)
  To: Pekka Enberg; +Cc: Markus Rechberger, Aidan Thornton, Linux Kernel Mailing List

On Thu, Nov 27, 2008 at 11:33:03AM +0200, Pekka Enberg wrote:
> On Thu, Nov 27, 2008 at 6:25 AM, Markus Rechberger
> <mrechberger@gmail.com> wrote:
> > I simply don't have the time for those games anymore, and those people here who have personal
> > issues with me and/or my work don't pay my monthly bills, neither do they have any clue how that
> > driver is used with some systems of certain customers. I constantly deliver full solutions, and not
> > driver code only. Neither am I limited to Linux only.
> 
> Again, it's not personal, it's the way we do things here. But you
> should already know that by now so whatever.
> 
> What puzzles me the most here is why isn't Empia simply providing
> datasheets to other developers as well. I mean, even the GPU and
> wireless vendors know how to play well with the community now, so
> what's the problem? Being locked to just one developer who's unwilling
> to cooperate doesn't sound like a winning strategy to me.
> 
> Greg, is there any reason we don't have Markus' out-of-tree driver
> listed here, btw:
> 
>   http://www.linuxdriverproject.org/twiki/bin/view/Main/OutOfTreeDrivers

It's a wiki, feel free to add it yourself :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-27  9:33         ` Pekka Enberg
@ 2008-11-28 15:48           ` Devin Heitmueller
  2008-11-28 20:09           ` Greg KH
  1 sibling, 0 replies; 29+ messages in thread
From: Devin Heitmueller @ 2008-11-28 15:48 UTC (permalink / raw)
  To: Pekka Enberg
  Cc: Markus Rechberger, Aidan Thornton, Greg KH, Linux Kernel Mailing List

On Thu, Nov 27, 2008 at 4:33 AM, Pekka Enberg <penberg@cs.helsinki.fi> wrote:
> On Thu, Nov 27, 2008 at 6:25 AM, Markus Rechberger
> <mrechberger@gmail.com> wrote:
>> I simply don't have the time for those games anymore, and those people here who have personal
>> issues with me and/or my work don't pay my monthly bills, neither do they have any clue how that
>> driver is used with some systems of certain customers. I constantly deliver full solutions, and not
>> driver code only. Neither am I limited to Linux only.
>
> Again, it's not personal, it's the way we do things here. But you
> should already know that by now so whatever.
>
> What puzzles me the most here is why isn't Empia simply providing
> datasheets to other developers as well. I mean, even the GPU and
> wireless vendors know how to play well with the community now, so
> what's the problem? Being locked to just one developer who's unwilling
> to cooperate doesn't sound like a winning strategy to me.
>
> Greg, is there any reason we don't have Markus' out-of-tree driver
> listed here, btw:
>
>  http://www.linuxdriverproject.org/twiki/bin/view/Main/OutOfTreeDrivers
>
> Maybe there are people in the "Linux Driver Project" that could be
> interested in working on this but simply don't know about it?
>
>                               Pekka
> --

Hello Pekka,

In Empia's defense, they actually have recently provided the
datasheets to others under NDA, such as myself.  This is one of the
reasons the in-kernel em28xx driver has improved significantly in the
last month (including new support for the em2874 and fixes to
em2860/em2880 support).

The in-kernel driver will continue to improve, device support will
continue to increase as I work with the people who have devices that
people care about.

Markus has made it very clear that he does not want any of his code
merged into the existing in-kernel em28xx driver.  Depsite the fact
that the license specifically allows doing exactly that, I have made
an effort to respect his feelings in this manner.

Regards,

Devin

-- 
Devin J. Heitmueller
http://www.devinheitmueller.com
AIM: devinheitmueller

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-27  4:25       ` Markus Rechberger
@ 2008-11-27  9:33         ` Pekka Enberg
  2008-11-28 15:48           ` Devin Heitmueller
  2008-11-28 20:09           ` Greg KH
  0 siblings, 2 replies; 29+ messages in thread
From: Pekka Enberg @ 2008-11-27  9:33 UTC (permalink / raw)
  To: Markus Rechberger; +Cc: Aidan Thornton, Greg KH, Linux Kernel Mailing List

On Thu, Nov 27, 2008 at 6:25 AM, Markus Rechberger
<mrechberger@gmail.com> wrote:
> I simply don't have the time for those games anymore, and those people here who have personal
> issues with me and/or my work don't pay my monthly bills, neither do they have any clue how that
> driver is used with some systems of certain customers. I constantly deliver full solutions, and not
> driver code only. Neither am I limited to Linux only.

Again, it's not personal, it's the way we do things here. But you
should already know that by now so whatever.

What puzzles me the most here is why isn't Empia simply providing
datasheets to other developers as well. I mean, even the GPU and
wireless vendors know how to play well with the community now, so
what's the problem? Being locked to just one developer who's unwilling
to cooperate doesn't sound like a winning strategy to me.

Greg, is there any reason we don't have Markus' out-of-tree driver
listed here, btw:

  http://www.linuxdriverproject.org/twiki/bin/view/Main/OutOfTreeDrivers

Maybe there are people in the "Linux Driver Project" that could be
interested in working on this but simply don't know about it?

                               Pekka

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-26 19:12     ` Aidan Thornton
@ 2008-11-27  4:25       ` Markus Rechberger
  2008-11-27  9:33         ` Pekka Enberg
  0 siblings, 1 reply; 29+ messages in thread
From: Markus Rechberger @ 2008-11-27  4:25 UTC (permalink / raw)
  To: Aidan Thornton; +Cc: Greg KH, Linux Kernel Mailing List

On Wed, Nov 26, 2008 at 8:12 PM, Aidan Thornton <makosoft@googlemail.com> wrote:
> On 10/22/08, Markus Rechberger <mrechberger@gmail.com> wrote:
>> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
>>> On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>>>>     em2880-dvb:
>>>>     * supporting the digital part of Empia based devices, which
>>>> includes ATSC, ISDB-T and DVB-T
>>>
>>> <snip>
>>>
>>> Doesn't this driver duplicate some of the existing devices we already
>>> support with the current in-kernel driver?  If so, why not just add the
>>> new device support to the existing driver instead of duplicating
>>> everything?
>>>
>>> This is going to cause a big problem for distros as they will not know
>>> which to enable, so they will probably just disable this one, which is
>>> what I don't think you want to have happen :(
>>>
>>
>> the current driver doesn't support most devices which are in there,
>> also the alsa
>> audio driver can easily crash the whole system. (It's my code so I
>> know what was wrong there).
>>
>> The core video code is already too much off, the VBI code added alot
>> complexity
>> to it it does frame slicing on the fly.
>>
>> Those devices ship VBI+VIDEO within 1 datastream, VBI and Video aren't
>> that different
>> in the system. both interfaces provide framebuffers through a mmap'ed
>> interface.
>> If all the VBI buffers are filled the data has to be sliced off in any
>> case while providing
>> the same bottom data ot the Video interface
>
> Hi,
>
> Just to check, your driver does handle this correctly now, right? Last
> time I checked, it was a buggy mess with severely broken locking that
> caused a kernel panic half the time when recording whith MythTV (and
> indeed, had been for years). It looks, at a glance, like this may have
> been fixed, but since I never quite managed to pin down the exact
> cause, I can't say for sure.
>

the exact cause was that the vbi buffer handling was connected with
the video buffer handling
back then. It crashed at certain scenarios.
VBI has been reworked completely since then, also the offsets. It's a
hardcoded em28xx - videodecoder setup.

> (The code is, alas, still somewhat ugly compared to the existing
> videobuf-based code in the in-kernel driver - it should be possible to
> add VBI support to that fairly cleanly and easily. I actually
> attempted this a while back, but ran into issues due to not having
> access to the datasheets. Of course, the fact that Markus works there
> and is actively aggressive to any Linux driver development that
> doesn't go through him.)
>
>> http://mcentral.de/hg/~mrec/em28xx-new/shortlog (there are more than
>> 200 changelog entries
>> what happened in detail).
>>
>> The development has been split off (first due limitations in the
>> kernel, afterwards due ..., and finally
>> due the restriction that all that has to work without a framework
>> upgrade on the eeePC).
>
> This doesn't exactly inspire confidence; it seems to basically mean
> you forked or rewrote existing drivers rather than just using code
> that was already there.
>

well I initially worked on that driver since 2005(!) and at least
helped more than
thousand users to get their devices work.

>> diffing the 2 available drivers shows up that only the core is twice
>> as big as the one which is currently
>> in the driver (the result of 2-3 years asynchronous development).
>
> Actually, part of the reason for this is probably that there's a
> massive bunch of video buffer handling code in your driver that mostly
> repeats stuff handled by the videobuf module in the in-kernel driver.
> (Admittedly, your does slightly more in that it handles raw VBI, but
> as I've said it should be possible to do that cleanly and in a smaller
> amount of code using videobuf.)
>
>> The driver is currently also tested with signal generators (different
>> inputs, and different video standards).
>>
>> Very likely the best would be to replace the available driver with it
>> but I don't care, alot people use and have been using the driver from
>> mcentral.de for a long time, development has always been opensource
>> there too.
>
> Just not necessarily open source under a GPL compliant license, as I recall.
>

I pointed out a couple of times that the license is no issue with the
exported sources.

> As I've said before, it'd be really nice if you'd make an effort to
> actually integrate your changes, but this looks like more of the same
> old mess.
>

The driver has been exported so far, that's all I'll ever offer. In
future to avoid those clashes I'll
simply use the i2c-dev framework which is commonly available and
allows to control
those chips from userland without any extra module. We already have a
player which
is optimized for those devices and also properly supports that interface.

I simply don't have the time for those games anymore, and those people
here who have personal
issues with me and/or my work don't pay my monthly bills, neither do
they have any clue how that
driver is used with some systems of certain customers. I constantly
deliver full solutions, and not
driver code only. Neither am I limited to Linux only.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-01 13:59 ` Hans Verkuil
  2008-11-02  4:27   ` Mauro Carvalho Chehab
@ 2008-11-26 20:36   ` Aidan Thornton
  1 sibling, 0 replies; 29+ messages in thread
From: Aidan Thornton @ 2008-11-26 20:36 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: Markus Rechberger, Linux Kernel Mailing List, em28xx, acano,
	Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l,
	linux-dvb, Mauro Carvalho Chehab, greg, Alan Cox

Hi,

On 11/1/08, Hans Verkuil <hverkuil@xs4all.nl> wrote:
> Hi Markus,
>
> As promised I've done a review of your empia driver and looked at what
> needs to be done to get it into the kernel.
>
> First of all, I've no doubt that your empia driver is better and
> supports more devices than the current em28xx driver. I also have no
> problem adding your driver separate from the current driver. It's been
> done before (certain networking drivers spring to mind) and while
> obviously not ideal I expect that the older em28xx driver can probably
> be removed after a year or something like that.
>
> In my opinion it's pretty much hopeless trying to convert the current
> em28xx driver into what you have. It's a huge amount of work that no
> one wants to do and (in this case) with very little benefit. Of course,
> Mauro has the final say in this.

The main reason no-one wants to do it is partly because it's a large
amount of work, and partly because Markus is the only one able to test
a decent subset of the available hardware and he has objections to the
idea. To be honest, the diffs look somewhat worse than they actually
are - there's a lot of noise and a lot of changes that will just be
dumped if this approach is used.

Incidentally, the new code has some odd quirks. For example, why does
opening an ALSA device switch the card from digital to radio mode,
even when the card doesn't support radio? In particular, unless I'm
missing something, this would mean that any app that opened the ALSA
device before the video device would get stuck in radio mode. (Also,
why is there tuner-specific code in the ALSA driver?) Why the bizarre
indirection through em28xx_cmd, which only has one possible value for
cmd?

> While there is some cleaning up to do in your code (coding style, some
> copyright/license clarifications), that is not a big deal. The coding
> style cleanups are a fair amount of work, but I think we can probably
> spread that out over several people and get that done quickly.
>
> What I definitely would recommend is to use video_ioctl2 rather than
> video_usercopy. The latter function will disappear in the future. I
> think the policy for new drivers is to use video_ioctl2, so this is
> probably a required task before it can be merged. Doing this will
> improve maintenance of the code as well, so it's useful to do this
> anyway. I think it's better if you do it, but I guess some volunteer
> can probably be found if needed. It's not a difficult task.
>
> Now the real problems are with three duplicate i2c drivers: cx25843,
> xc3028 and xc5000.
>
> To start with the easiest one: cx25843. There already is a driver for
> this (cx25840) and the empia driver should use that one. Since I
> maintain cx25840 the easiest approach for you is to see if you can get
> me hardware (em28xx + cx25843) so that I can test and update cx25843 to
> provide proper empia support. The alternative is that we work together
> on this, but that's probably more work for both of us. I most
> definitely do *not* want to duplicate this driver. Windows drivers
> duplicate this stuff all the time, but we're not Windows and having one
> driver ensures that fixes or new functionality become available to all
> bridge drivers that use it.
>
> The same reasoning is true for xc3028 and xc5000. In addition, the
> licensing of these sources is very vague. Is it even allowed to
> distribute them under a GPL license? There is no GPL license in the
> sources, yet in the module you specify GPL. This needs to be clarified
> first.
>
> I see two ways forward when it comes to these drivers:
>
> 1) The empia driver switches to the current xceive drivers that are
> already in the kernel. No doubt this means that xceive driver bugs will
> surface with certain devices, but those are bugs that the xceive driver
> maintainer will have to fix. Obviously assistance would be appreciated,
> but the bottom line is that a) your driver is finally in the kernel,
> and b) there is a lot more pressure to fix bugs in the xceive drivers
> than is the case otherwise. Yes, some devices will not work initially
> with the empia driver, but I expect that to be a temporary situation.

The trouble is, it looks like the core empia code is pretty tightly
integrated with the xc3028 driver - in particular, there's lots of
xc3028-specific code in empia/empia-cards.c. (This probably isn't a
good thing in itself.) Ideally, Markus would've used the in-kernel
driver for at least xc3028 - it was developed and mostly working at
that point - but I suppose that was fairly unlikely to happen.
(There's also a bunch of tuner-specific code in empia-dvb.c that
wouldn't be needed if the proper framework was used.)

Plus, there are (as usual) some direct writes to demodulators by
empia-dvb.c that need to be cleaned up.

> 2) Your xceive drivers replace the current drivers. This will require
> that a) the license situation is clarified, b) the drivers are modified
> to fit the current v4l-dvb tuner architecture. This option will mean a
> lot of work for you since you are the maintainer of these drivers. In
> addition, I forsee a lot of flaming if we go this way.

Of course, the same issue applies to this option - the empia and
xc3028 code appear to be fairly closely entangled.

In fact, since most of the work is, I suspect, going to be dealing
with this issue, just merging the required changes into the existing
em28xx driver starts to look like quite a plausible option. It
basically boils down to:

(a) audio changes, some of which are undesirable, and the rest of
which can be done by a (hopefully relatively simple) patch to the
current driver - I can just about see most of the changes in the diff.
(b) VBI, which can't all be ported directly but should be fairly easy
to add - plus, doing it that way is cleaner.
(c) some modifications to the card initialisation functions and glue
code, which ought to be simple to port but will require someone with
access to the hardware to test and debug them. (This means Markus,
basically).
(b) a bunch of formatting changes, noise and, now-redundant code with
the occasional useful change to trap the unwary.

(Interestingly, aside from VBI, the now-redundant buffer management
code, duplicate drivers, init/glue code for new cards, and misplaced
tuner-specific stuff, most of the significant changes seem to be code
that's in the existing driver but not the new. This is probably not a
good sign. Unfortunately, there's a lot of diff noise, partly due to
some of the functions being reordered at some point, partly due to
changes in stuff like indentation and format of register #defines, and
partly due to diff sucking, so it's a bit hard to see what exactly
changed in places.)

Of course, since Markus probably won't go for this, it's not really an
option. That's a shame, because it'd probably work out best.

Sorry about the rather late mail; I don't generally pay attention to
em28xx development anymore. I used to be more involved, but my card
died and I moved to hardware with a less messy driver situation.

Aidan Thornton

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:24   ` Markus Rechberger
  2008-10-22 22:26     ` Markus Rechberger
  2008-10-22 22:27     ` Greg KH
@ 2008-11-26 19:12     ` Aidan Thornton
  2008-11-27  4:25       ` Markus Rechberger
  2 siblings, 1 reply; 29+ messages in thread
From: Aidan Thornton @ 2008-11-26 19:12 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Greg KH, Linux Kernel Mailing List, em28xx, acano,
	Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On 10/22/08, Markus Rechberger <mrechberger@gmail.com> wrote:
> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
>> On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>>>     em2880-dvb:
>>>     * supporting the digital part of Empia based devices, which
>>> includes ATSC, ISDB-T and DVB-T
>>
>> <snip>
>>
>> Doesn't this driver duplicate some of the existing devices we already
>> support with the current in-kernel driver?  If so, why not just add the
>> new device support to the existing driver instead of duplicating
>> everything?
>>
>> This is going to cause a big problem for distros as they will not know
>> which to enable, so they will probably just disable this one, which is
>> what I don't think you want to have happen :(
>>
>
> the current driver doesn't support most devices which are in there,
> also the alsa
> audio driver can easily crash the whole system. (It's my code so I
> know what was wrong there).
>
> The core video code is already too much off, the VBI code added alot
> complexity
> to it it does frame slicing on the fly.
>
> Those devices ship VBI+VIDEO within 1 datastream, VBI and Video aren't
> that different
> in the system. both interfaces provide framebuffers through a mmap'ed
> interface.
> If all the VBI buffers are filled the data has to be sliced off in any
> case while providing
> the same bottom data ot the Video interface

Hi,

Just to check, your driver does handle this correctly now, right? Last
time I checked, it was a buggy mess with severely broken locking that
caused a kernel panic half the time when recording whith MythTV (and
indeed, had been for years). It looks, at a glance, like this may have
been fixed, but since I never quite managed to pin down the exact
cause, I can't say for sure.

(The code is, alas, still somewhat ugly compared to the existing
videobuf-based code in the in-kernel driver - it should be possible to
add VBI support to that fairly cleanly and easily. I actually
attempted this a while back, but ran into issues due to not having
access to the datasheets. Of course, the fact that Markus works there
and is actively aggressive to any Linux driver development that
doesn't go through him.)

> http://mcentral.de/hg/~mrec/em28xx-new/shortlog (there are more than
> 200 changelog entries
> what happened in detail).
>
> The development has been split off (first due limitations in the
> kernel, afterwards due ..., and finally
> due the restriction that all that has to work without a framework
> upgrade on the eeePC).

This doesn't exactly inspire confidence; it seems to basically mean
you forked or rewrote existing drivers rather than just using code
that was already there.

> diffing the 2 available drivers shows up that only the core is twice
> as big as the one which is currently
> in the driver (the result of 2-3 years asynchronous development).

Actually, part of the reason for this is probably that there's a
massive bunch of video buffer handling code in your driver that mostly
repeats stuff handled by the videobuf module in the in-kernel driver.
(Admittedly, your does slightly more in that it handles raw VBI, but
as I've said it should be possible to do that cleanly and in a smaller
amount of code using videobuf.)

> The driver is currently also tested with signal generators (different
> inputs, and different video standards).
>
> Very likely the best would be to replace the available driver with it
> but I don't care, alot people use and have been using the driver from
> mcentral.de for a long time, development has always been opensource
> there too.

Just not necessarily open source under a GPL compliant license, as I recall.

As I've said before, it'd be really nice if you'd make an effort to
actually integrate your changes, but this looks like more of the same
old mess.

Aidan

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:56               ` Mauro Carvalho Chehab
@ 2008-11-08 11:02                 ` Markus Rechberger
  0 siblings, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-11-08 11:02 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, Nov 8, 2008 at 11:56 AM, Mauro Carvalho Chehab
<mchehab@infradead.org> wrote:
> On Sat, 8 Nov 2008, Markus Rechberger wrote:
>
>> Should I start picking patches from the linuxtv.org tree where patches
>> from my tree are taken
>> and where the Sign off is not provided?
>
> SOB's are just meant to testify that the code is GPL. It has nothing to do
> with authorship.
>
> On all cases I'm aware, when some code from your tree were merged upstream,
> your authorship were marked inside the patch's description. Also, your
> authorship are explicit at the em28xx files you've contributed.
>

really?
http://linuxtv.org/hg/v4l-dvb/rev/5f3c3af9c1f9

http://article.gmane.org/gmane.linux.drivers.dvb/44726

nevermind.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:42             ` Markus Rechberger
  2008-11-08 10:46               ` Markus Rechberger
@ 2008-11-08 10:56               ` Mauro Carvalho Chehab
  2008-11-08 11:02                 ` Markus Rechberger
  1 sibling, 1 reply; 29+ messages in thread
From: Mauro Carvalho Chehab @ 2008-11-08 10:56 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, 8 Nov 2008, Markus Rechberger wrote:

> Should I start picking patches from the linuxtv.org tree where patches
> from my tree are taken
> and where the Sign off is not provided?

SOB's are just meant to testify that the code is GPL. It has nothing to do 
with authorship.

On all cases I'm aware, when some code from your tree were merged 
upstream, your authorship were marked inside the patch's description. 
Also, your authorship are explicit at the em28xx files you've contributed.

-- 
Cheers,
Mauro Carvalho Chehab
http://linuxtv.org
mchehab@infradead.org

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:42             ` Markus Rechberger
@ 2008-11-08 10:46               ` Markus Rechberger
  2008-11-08 10:56               ` Mauro Carvalho Chehab
  1 sibling, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-11-08 10:46 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, Nov 8, 2008 at 11:42 AM, Markus Rechberger
<mrechberger@gmail.com> wrote:
> On Sat, Nov 8, 2008 at 11:37 AM, Mauro Carvalho Chehab
> <mchehab@infradead.org> wrote:
>> On Sat, 8 Nov 2008, Markus Rechberger wrote:
>>
>>> As written earlier already I don't think that I didn't follow any
>>> rules here since I provided single
>>> patches at the very first beginning
>>>
>>> http://mcentral.de/v4l-dvb/
>>> (this is all kernel code and only kernel code).
>>>
>>> That work didn't get attention and due a different decision of
>>> framework changes (which that codebase relied
>>> on) it all had to be rebased, I doubt that anyone
>>> would have reworked all that patch for patch. Instead it went into one
>>> repository and finally got modified to work again
>>> with the available framework rather than relying onto any such
>>> modifications.
>>
>> One thing is to rebase a tree, another is to merge all patches into a big
>> one, not preserving the original authorships.
>>
>> Development trees sometimes need rebase. This is done by popping all newer
>> patches from the tree, applying the upstream patches, and then pushing again
>> every individual patches, fixing the ones that don't apply well, but
>> preserving their authorships.
>>
>> The modified patches should receive a special tag before the maintainer's
>> SOB, like:
>>
>> [me@mymail: I did this to apply this patch]
>>
>> as stated at the kernel docs.
>>
>> This method will reduce a lot the risk of breaking improvements and other
>> fixes that happened upstream, and will properly preserve authorship of
>> individual patches.
>>
>> If you were doing a rebase, your patches would likely be accepted.
>>
>

let's link back to the only review done:
 http://linux.derkeiler.com/Mailing-Lists/Kernel/2008-11/msg00060.html

<snip>
In my opinion it's pretty much hopeless trying to convert the current
em28xx driver into what you have. It's a huge amount of work that no
one wants to do and (in this case) with very little benefit.
</snip>

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:37           ` Mauro Carvalho Chehab
@ 2008-11-08 10:42             ` Markus Rechberger
  2008-11-08 10:46               ` Markus Rechberger
  2008-11-08 10:56               ` Mauro Carvalho Chehab
  0 siblings, 2 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-11-08 10:42 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, Nov 8, 2008 at 11:37 AM, Mauro Carvalho Chehab
<mchehab@infradead.org> wrote:
> On Sat, 8 Nov 2008, Markus Rechberger wrote:
>
>> As written earlier already I don't think that I didn't follow any
>> rules here since I provided single
>> patches at the very first beginning
>>
>> http://mcentral.de/v4l-dvb/
>> (this is all kernel code and only kernel code).
>>
>> That work didn't get attention and due a different decision of
>> framework changes (which that codebase relied
>> on) it all had to be rebased, I doubt that anyone
>> would have reworked all that patch for patch. Instead it went into one
>> repository and finally got modified to work again
>> with the available framework rather than relying onto any such
>> modifications.
>
> One thing is to rebase a tree, another is to merge all patches into a big
> one, not preserving the original authorships.
>
> Development trees sometimes need rebase. This is done by popping all newer
> patches from the tree, applying the upstream patches, and then pushing again
> every individual patches, fixing the ones that don't apply well, but
> preserving their authorships.
>
> The modified patches should receive a special tag before the maintainer's
> SOB, like:
>
> [me@mymail: I did this to apply this patch]
>
> as stated at the kernel docs.
>
> This method will reduce a lot the risk of breaking improvements and other
> fixes that happened upstream, and will properly preserve authorship of
> individual patches.
>
> If you were doing a rebase, your patches would likely be accepted.
>

Should I start picking patches from the linuxtv.org tree where patches
from my tree are taken
and where the Sign off is not provided?

Just recently one patch went into not even stating that it comes from
my codebase, although not
worth the time making an elephant out of it.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:22         ` Markus Rechberger
@ 2008-11-08 10:37           ` Mauro Carvalho Chehab
  2008-11-08 10:42             ` Markus Rechberger
  0 siblings, 1 reply; 29+ messages in thread
From: Mauro Carvalho Chehab @ 2008-11-08 10:37 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, 8 Nov 2008, Markus Rechberger wrote:

> As written earlier already I don't think that I didn't follow any
> rules here since I provided single
> patches at the very first beginning
>
> http://mcentral.de/v4l-dvb/
> (this is all kernel code and only kernel code).
>
> That work didn't get attention and due a different decision of
> framework changes (which that codebase relied
> on) it all had to be rebased, I doubt that anyone
> would have reworked all that patch for patch. Instead it went into one
> repository and finally got modified to work again
> with the available framework rather than relying onto any such modifications.

One thing is to rebase a tree, another is to merge all patches into a big 
one, not preserving the original authorships.

Development trees sometimes need rebase. This is done by popping all newer 
patches from the tree, applying the upstream patches, and then pushing 
again every individual patches, fixing the ones that don't apply well, but 
preserving their authorships.

The modified patches should receive a special tag before the 
maintainer's SOB, like:

[me@mymail: I did this to apply this patch]

as stated at the kernel docs.

This method will reduce a lot the risk of breaking improvements and other 
fixes that happened upstream, and will properly preserve authorship of 
individual patches.

If you were doing a rebase, your patches would likely be accepted.

-- 
Cheers,
Mauro

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-08 10:15       ` Mauro Carvalho Chehab
@ 2008-11-08 10:22         ` Markus Rechberger
  2008-11-08 10:37           ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 29+ messages in thread
From: Markus Rechberger @ 2008-11-08 10:22 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Andre Kelmanson, Hans Verkuil, Linux Kernel Mailing List, em28xx,
	acano, Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb, greg,
	Alan Cox

On Sat, Nov 8, 2008 at 11:15 AM, Mauro Carvalho Chehab
<mchehab@infradead.org> wrote:
> On Sat, 8 Nov 2008 03:50:20 -0200
> "Andre Kelmanson" <akelmanson@gmail.com> wrote:
>
>> Dears,
>>
>> I'm using this version of em28xx for a long time and it's working fine. It
>> has three very important features for me. The first one is Kaiomy device,
>> the second one is the new em28xx-audoep module and the third one is PAL-M
>> support. Kaiomy and PAL-M support were developed based on my support on the
>> em28xx mailinglist.
>>
>> Now I can use my device (Kaiomy) outside Windows with sound (em28xx-audioep)
>> and colors (PAL-M)! I'm using this version everyday with no problems.
>>
>> Because of this, it will be nice if this work could be included in the
>> kernel code. What do you (other users) think about having that driver in
>> kernel?
>
> André,
>
> First of all, the big issue why we aren't merging em28xx improvements from
> Markus is that he is not following the rules.
>
> For example, you said that you've contributed with Markus tree.
>
> However, on Markus pull request, I see no patch from you on his series of patches.
>
> The correct procedure would be just to forward your patch as-is, adding with his SOB bellow yours.
>
> Not doing this, he would be considered as the author of your patch. IANAL, but
> this doesn't seem to be right, from GPL's perspective. Probably, there are
> other patches there not authored by Markus that are just merged inside his big patch.
>
> About PAL-M, this always worked at the upstream driver. I have myself lots of
> em28xx devices, all working in colors with PAL-M, and with audio. I live in
> Brazil, so I always check if PAL-M is ok ;)
>
> If you want to have your device supported, just send us a patch against the
> upstream driver.
>

As written earlier already I don't think that I didn't follow any
rules here since I provided single
patches at the very first beginning

http://mcentral.de/v4l-dvb/
(this is all kernel code and only kernel code).

That work didn't get attention and due a different decision of
framework changes (which that codebase relied
on) it all had to be rebased, I doubt that anyone
would have reworked all that patch for patch. Instead it went into one
repository and finally got modified to work again
with the available framework rather than relying onto any such modifications.

The modification which I received from André suggested to use the NTSC
VBI offset lines for PAL-M, and the
rest for audio comes from my side.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
       [not found]     ` <a2aa6e3a0811072150t535e802cge3375a7b88ee6287@mail.gmail.com>
@ 2008-11-08 10:15       ` Mauro Carvalho Chehab
  2008-11-08 10:22         ` Markus Rechberger
  0 siblings, 1 reply; 29+ messages in thread
From: Mauro Carvalho Chehab @ 2008-11-08 10:15 UTC (permalink / raw)
  To: Andre Kelmanson
  Cc: Hans Verkuil, Markus Rechberger, Linux Kernel Mailing List,
	em28xx, acano, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l,
	linux-dvb, greg, Alan Cox

On Sat, 8 Nov 2008 03:50:20 -0200
"Andre Kelmanson" <akelmanson@gmail.com> wrote:

> Dears,
> 
> I'm using this version of em28xx for a long time and it's working fine. It
> has three very important features for me. The first one is Kaiomy device,
> the second one is the new em28xx-audoep module and the third one is PAL-M
> support. Kaiomy and PAL-M support were developed based on my support on the
> em28xx mailinglist.
> 
> Now I can use my device (Kaiomy) outside Windows with sound (em28xx-audioep)
> and colors (PAL-M)! I'm using this version everyday with no problems.
> 
> Because of this, it will be nice if this work could be included in the
> kernel code. What do you (other users) think about having that driver in
> kernel?

André,

First of all, the big issue why we aren't merging em28xx improvements from
Markus is that he is not following the rules.

For example, you said that you've contributed with Markus tree.

However, on Markus pull request, I see no patch from you on his series of patches. 

The correct procedure would be just to forward your patch as-is, adding with his SOB bellow yours.

Not doing this, he would be considered as the author of your patch. IANAL, but
this doesn't seem to be right, from GPL's perspective. Probably, there are
other patches there not authored by Markus that are just merged inside his big patch.

About PAL-M, this always worked at the upstream driver. I have myself lots of
em28xx devices, all working in colors with PAL-M, and with audio. I live in
Brazil, so I always check if PAL-M is ok ;)

If you want to have your device supported, just send us a patch against the
upstream driver.

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-11-01 13:59 ` Hans Verkuil
@ 2008-11-02  4:27   ` Mauro Carvalho Chehab
       [not found]     ` <a2aa6e3a0811072150t535e802cge3375a7b88ee6287@mail.gmail.com>
  2008-11-26 20:36   ` Aidan Thornton
  1 sibling, 1 reply; 29+ messages in thread
From: Mauro Carvalho Chehab @ 2008-11-02  4:27 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: Markus Rechberger, Linux Kernel Mailing List, em28xx, acano,
	Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l,
	linux-dvb, greg, Alan Cox

On Sat, 1 Nov 2008 14:59:17 +0100
Hans Verkuil <hverkuil@xs4all.nl> wrote:

> Hi Markus,
> 
> As promised I've done a review of your empia driver and looked at what 
> needs to be done to get it into the kernel.
> 
> First of all, I've no doubt that your empia driver is better and 
> supports more devices than the current em28xx driver. I also have no 
> problem adding your driver separate from the current driver. It's been 
> done before (certain networking drivers spring to mind) and while 
> obviously not ideal I expect that the older em28xx driver can probably 
> be removed after a year or something like that.
> 
> In my opinion it's pretty much hopeless trying to convert the current 
> em28xx driver into what you have. It's a huge amount of work that no 
> one wants to do and (in this case) with very little benefit. Of course, 
> Mauro has the final say in this.
> 

Both upstream and the 4 duplicated drivers have similar functionality. Also,
the upstream driver is actively maintained. So, there's no sense on accepting
those duplicated drivers.

Also, just replacing one existing driver by a newer one will cause regressions
on some already fixed bugs and remove some improvements that the upstream driver
suffered.

If there's a bug or a lack of functionality on em28xx, cx25843, xc5000 or
tuner-xc2028, it is just a matter of submitting patches fixing those bugs or
adding newer features.

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
@ 2008-11-01 14:05 Hans Verkuil
  0 siblings, 0 replies; 29+ messages in thread
From: Hans Verkuil @ 2008-11-01 14:05 UTC (permalink / raw)
  To: Markus Rechberger; +Cc: Linux Kernel Mailing List, em28xx, v4l, linux-dvb

(Note: I'm reposting this since it bounced on several mailinglists due 
to "Too many recipients". I'm now only sending it to the mailinglists 
as I assume that most of the recipients are on at least one of these 
mailinglists. Apologies if this is not the case.)


Hi Markus,

As promised I've done a review of your empia driver and looked at what 
needs to be done to get it into the kernel.

First of all, I've no doubt that your empia driver is better and 
supports more devices than the current em28xx driver. I also have no 
problem adding your driver separate from the current driver. It's been 
done before (certain networking drivers spring to mind) and while 
obviously not ideal I expect that the older em28xx driver can probably 
be removed after a year or something like that.

In my opinion it's pretty much hopeless trying to convert the current 
em28xx driver into what you have. It's a huge amount of work that no 
one wants to do and (in this case) with very little benefit. Of course, 
Mauro has the final say in this.

While there is some cleaning up to do in your code (coding style, some 
copyright/license clarifications), that is not a big deal. The coding 
style cleanups are a fair amount of work, but I think we can probably 
spread that out over several people and get that done quickly.

What I definitely would recommend is to use video_ioctl2 rather than 
video_usercopy. The latter function will disappear in the future. I 
think the policy for new drivers is to use video_ioctl2, so this is 
probably a required task before it can be merged. Doing this will 
improve maintenance of the code as well, so it's useful to do this 
anyway. I think it's better if you do it, but I guess some volunteer 
can probably be found if needed. It's not a difficult task.

Now the real problems are with three duplicate i2c drivers: cx25843, 
xc3028 and xc5000.

To start with the easiest one: cx25843. There already is a driver for 
this (cx25840) and the empia driver should use that one. Since I 
maintain cx25840 the easiest approach for you is to see if you can get 
me hardware (em28xx + cx25843) so that I can test and update cx25843 to 
provide proper empia support. The alternative is that we work together 
on this, but that's probably more work for both of us. I most 
definitely do *not* want to duplicate this driver. Windows drivers 
duplicate this stuff all the time, but we're not Windows and having one 
driver ensures that fixes or new functionality become available to all 
bridge drivers that use it.

The same reasoning is true for xc3028 and xc5000. In addition, the 
licensing of these sources is very vague. Is it even allowed to 
distribute them under a GPL license? There is no GPL license in the 
sources, yet in the module you specify GPL. This needs to be clarified 
first.

I see two ways forward when it comes to these drivers:

1) The empia driver switches to the current xceive drivers that are 
already in the kernel. No doubt this means that xceive driver bugs will 
surface with certain devices, but those are bugs that the xceive driver 
maintainer will have to fix. Obviously assistance would be appreciated, 
but the bottom line is that a) your driver is finally in the kernel, 
and b) there is a lot more pressure to fix bugs in the xceive drivers 
than is the case otherwise. Yes, some devices will not work initially 
with the empia driver, but I expect that to be a temporary situation.

2) Your xceive drivers replace the current drivers. This will require 
that a) the license situation is clarified, b) the drivers are modified 
to fit the current v4l-dvb tuner architecture. This option will mean a 
lot of work for you since you are the maintainer of these drivers. In 
addition, I forsee a lot of flaming if we go this way.

BTW, I noticed that the current xc5000 driver is very similar to yours 
but with proper copyrights/license notices and coding style. So for 
this driver option 1 is definitely the way to go.

To be honest, I don't see option 2 as viable. I forsee too many 
inter-personal problems that will appear if we go that way. Option 1 
has the big advantage that all you need to do is to file a bug report 
if it doesn't work with a certain device. And in the meantime users can 
fallback to your stand-alone driver until it's fixed in the kernel. 
Obviously, if you can state in the bug report what the precise problem 
is, so much the better.

So my recommendation would be to:

1) Switch to using the current xceive drivers in your empia driver. This 
is something you have to do, I'm afraid. Unless someone would 
volunteer? I personally do not have enough experience with this to do 
it.

2) Switch to using the cx25840 driver. If I can get hardware, then I can 
do this, otherwise we have to do this together. Initially we probably 
have to disable devices using the cx25840 until the cx25840 driver has 
been fixed for the empia driver.

3) Switch to video_ioctl2 in the empia driver. You can do that, but we 
can probably find a volunteer as well.

4) Conform the code to the coding style. If several people can help with 
this we can get it done pretty quickly.

5) Merge the empia driver alongside the current em28xx driver.

There are no doubt some things I missed, but I don't think I missed 
anything major. I've put up a hg tree here:

http://linuxtv.org/hg/~hverkuil/v4l-dvb-em28xx/

This allows you to compile the empia module alongside the em28xx module. 
Note that I've removed the empia cx25843 module in the last changeset 
in order to test which dependencies the driver had on that module. So 
if you want to test this tree with an empia+cx25843 device, then don't 
get the latest changeset, but the one before that.

My tree does contain the empia xceive drivers, though. Perhaps someone 
more knowledgeable with DVB can take a look to see how much work it is 
to convert to the kernel xceive drivers? And to see if I overlooked any 
DVB-related major obstacles?

I think this is a reasonable roadmap to finally get this in. If everyone 
can pitch in then it really shouldn't take that much work to get it 
into v4l-dvb and from there to 2.6.29.

Regards,

	Hans Verkuil

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 21:14 Markus Rechberger
  2008-10-22 22:09 ` Greg KH
@ 2008-11-01 13:59 ` Hans Verkuil
  2008-11-02  4:27   ` Mauro Carvalho Chehab
  2008-11-26 20:36   ` Aidan Thornton
  1 sibling, 2 replies; 29+ messages in thread
From: Hans Verkuil @ 2008-11-01 13:59 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang, v4l, linux-dvb,
	Mauro Carvalho Chehab, greg, Alan Cox

Hi Markus,

As promised I've done a review of your empia driver and looked at what 
needs to be done to get it into the kernel.

First of all, I've no doubt that your empia driver is better and 
supports more devices than the current em28xx driver. I also have no 
problem adding your driver separate from the current driver. It's been 
done before (certain networking drivers spring to mind) and while 
obviously not ideal I expect that the older em28xx driver can probably 
be removed after a year or something like that.

In my opinion it's pretty much hopeless trying to convert the current 
em28xx driver into what you have. It's a huge amount of work that no 
one wants to do and (in this case) with very little benefit. Of course, 
Mauro has the final say in this.

While there is some cleaning up to do in your code (coding style, some 
copyright/license clarifications), that is not a big deal. The coding 
style cleanups are a fair amount of work, but I think we can probably 
spread that out over several people and get that done quickly.

What I definitely would recommend is to use video_ioctl2 rather than 
video_usercopy. The latter function will disappear in the future. I 
think the policy for new drivers is to use video_ioctl2, so this is 
probably a required task before it can be merged. Doing this will 
improve maintenance of the code as well, so it's useful to do this 
anyway. I think it's better if you do it, but I guess some volunteer 
can probably be found if needed. It's not a difficult task.

Now the real problems are with three duplicate i2c drivers: cx25843, 
xc3028 and xc5000.

To start with the easiest one: cx25843. There already is a driver for 
this (cx25840) and the empia driver should use that one. Since I 
maintain cx25840 the easiest approach for you is to see if you can get 
me hardware (em28xx + cx25843) so that I can test and update cx25843 to 
provide proper empia support. The alternative is that we work together 
on this, but that's probably more work for both of us. I most 
definitely do *not* want to duplicate this driver. Windows drivers 
duplicate this stuff all the time, but we're not Windows and having one 
driver ensures that fixes or new functionality become available to all 
bridge drivers that use it.

The same reasoning is true for xc3028 and xc5000. In addition, the 
licensing of these sources is very vague. Is it even allowed to 
distribute them under a GPL license? There is no GPL license in the 
sources, yet in the module you specify GPL. This needs to be clarified 
first.

I see two ways forward when it comes to these drivers:

1) The empia driver switches to the current xceive drivers that are 
already in the kernel. No doubt this means that xceive driver bugs will 
surface with certain devices, but those are bugs that the xceive driver 
maintainer will have to fix. Obviously assistance would be appreciated, 
but the bottom line is that a) your driver is finally in the kernel, 
and b) there is a lot more pressure to fix bugs in the xceive drivers 
than is the case otherwise. Yes, some devices will not work initially 
with the empia driver, but I expect that to be a temporary situation.

2) Your xceive drivers replace the current drivers. This will require 
that a) the license situation is clarified, b) the drivers are modified 
to fit the current v4l-dvb tuner architecture. This option will mean a 
lot of work for you since you are the maintainer of these drivers. In 
addition, I forsee a lot of flaming if we go this way.

BTW, I noticed that the current xc5000 driver is very similar to yours 
but with proper copyrights/license notices and coding style. So for 
this driver option 1 is definitely the way to go.

To be honest, I don't see option 2 as viable. I forsee too many 
inter-personal problems that will appear if we go that way. Option 1 
has the big advantage that all you need to do is to file a bug report 
if it doesn't work with a certain device. And in the meantime users can 
fallback to your stand-alone driver until it's fixed in the kernel. 
Obviously, if you can state in the bug report what the precise problem 
is, so much the better.

So my recommendation would be to:

1) Switch to using the current xceive drivers in your empia driver. This 
is something you have to do, I'm afraid. Unless someone would 
volunteer? I personally do not have enough experience with this to do 
it.

2) Switch to using the cx25840 driver. If I can get hardware, then I can 
do this, otherwise we have to do this together. Initially we probably 
have to disable devices using the cx25840 until the cx25840 driver has 
been fixed for the empia driver.

3) Switch to video_ioctl2 in the empia driver. You can do that, but we 
can probably find a volunteer as well.

4) Conform the code to the coding style. If several people can help with 
this we can get it done pretty quickly.

5) Merge the empia driver alongside the current em28xx driver.

There are no doubt some things I missed, but I don't think I missed 
anything major. I've put up a hg tree here:

http://linuxtv.org/hg/~hverkuil/v4l-dvb-em28xx/

This allows you to compile the empia module alongside the em28xx module. 
Note that I've removed the empia cx25843 module in the last changeset 
in order to test which dependencies the driver had on that module. So 
if you want to test this tree with an empia+cx25843 device, then don't 
get the latest changeset, but the one before that.

My tree does contain the empia xceive drivers, though. Perhaps someone 
more knowledgeable with DVB can take a look to see how much work it is 
to convert to the kernel xceive drivers? And to see if I overlooked any 
DVB-related major obstacles?

I think this is a reasonable roadmap to finally get this in. If everyone 
can pitch in then it really shouldn't take that much work to get it 
into v4l-dvb and from there to 2.6.29.

Regards,

	Hans Verkuil

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-23  9:29   ` Alan Cox
@ 2008-10-23 11:10     ` Markus Rechberger
  0 siblings, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-10-23 11:10 UTC (permalink / raw)
  To: Alan Cox
  Cc: Greg KH, Linux Kernel Mailing List, em28xx, acano,
	Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 5:29 AM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
>> This is going to cause a big problem for distros as they will not know
>> which to enable, so they will probably just disable this one, which is
>> what I don't think you want to have happen :(
>
> Before looking into any of this talk to the V4L and DVB maintainers,
> browse video4linux list and have a happy read of stuff like:
>
> http://thread.gmane.org/gmane.linux.drivers.dvb/35454
>

There's a much much longer history about that, this can safely be ignored.
The history goes back 3 years .. this one only goes back a couple of months.
It will take at least a week for a single person to understand the
whole issue from the beginning on, although I'd say it's not relevant.

In case of Userspace tuners, see cx88 freeBSD driver the project is
growing, netBSD
is also using parts of that em28xx driver - there was no complaint at
all about it.
Some people don't like that idea in linux so it's ok and it has been
redone, no big deal anymore.

Markus

>
> Now I wonder why Markus didn't cc Mauro on this despite Mauro being
> maintainer.
>
> Alan
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:09 ` Greg KH
  2008-10-22 22:24   ` Markus Rechberger
@ 2008-10-23  9:29   ` Alan Cox
  2008-10-23 11:10     ` Markus Rechberger
  1 sibling, 1 reply; 29+ messages in thread
From: Alan Cox @ 2008-10-23  9:29 UTC (permalink / raw)
  To: Greg KH
  Cc: Markus Rechberger, Linux Kernel Mailing List, em28xx, acano,
	Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang

> This is going to cause a big problem for distros as they will not know
> which to enable, so they will probably just disable this one, which is
> what I don't think you want to have happen :(

Before looking into any of this talk to the V4L and DVB maintainers,
browse video4linux list and have a happy read of stuff like:

http://thread.gmane.org/gmane.linux.drivers.dvb/35454


Now I wonder why Markus didn't cc Mauro on this despite Mauro being
maintainer.

Alan

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:49         ` Greg KH
@ 2008-10-23  8:53           ` el es
  0 siblings, 0 replies; 29+ messages in thread
From: el es @ 2008-10-23  8:53 UTC (permalink / raw)
  To: linux-kernel

Greg KH <greg <at> kroah.com> writes:


> Please send patches that evolve the current drivers into supporting
> these new devices so that our development model is followed, and no user
> ends up in trouble.
> 
> thanks,
> 
> greg k-h
> 

(just my £.2, user's perspective warning)
Just to say this, the current in-tree driver doesn't support the device I own,
Markus' driver does.

Greg : 
The code Markus is requesting to merge is a completely NEW driver comparing to
the one that is in the tree (has a long and painful history). I think you might
safely assume that it already is NOT based on the old driver, tough shares the
name, due to the changes (and history).

Markus : just an idea - maybe if you've renamed the driver, to reflect the fact,
that it is completely new code, and schedule the old one to be deprecated ? 

Lukasz


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:35       ` Markus Rechberger
@ 2008-10-22 22:49         ` Greg KH
  2008-10-23  8:53           ` el es
  0 siblings, 1 reply; 29+ messages in thread
From: Greg KH @ 2008-10-22 22:49 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 12:35:54AM +0200, Markus Rechberger wrote:
> On Thu, Oct 23, 2008 at 12:27 AM, Greg KH <greg@kroah.com> wrote:
> > On Thu, Oct 23, 2008 at 12:24:31AM +0200, Markus Rechberger wrote:
> >> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
> >> > On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
> >> >>     em2880-dvb:
> >> >>     * supporting the digital part of Empia based devices, which
> >> >> includes ATSC, ISDB-T and DVB-T
> >> >
> >> > <snip>
> >> >
> >> > Doesn't this driver duplicate some of the existing devices we already
> >> > support with the current in-kernel driver?  If so, why not just add the
> >> > new device support to the existing driver instead of duplicating
> >> > everything?
> >> >
> >> > This is going to cause a big problem for distros as they will not know
> >> > which to enable, so they will probably just disable this one, which is
> >> > what I don't think you want to have happen :(
> >> >
> >>
> >> the current driver doesn't support most devices which are in there,
> >
> > Then why not just add new device support to the existing one?
> >
> >> also the alsa audio driver can easily crash the whole system. (It's my
> >> code so I know what was wrong there).
> >
> > Why not send patches to fix it?
> >
> 
> that's why I'm sending that request at the moment, development still goes on
> on my side.

I don't understand, I don't see patches here to fix the existing
problems in the existing driver.  Am I missing something?

> >> Very likely the best would be to replace the available driver with it
> >> but I don't care, alot people use and have been using the driver from
> >> mcentral.de for a long time, development has always been opensource
> >> there too.
> >
> > Dropping existing code and replacing it entirely with a new base is not
> > how Linux kernel development works for the most part.
> >
> 
> the patch adds the driver from mcentral.de as alternative entry, not
> the best way but it currently supports almost all devices which have
> an entry in there.

What do you mean by "alternative" entry?  Our device/driver matching
system doesn't allow for "alternates" it is a "first one there wins"
type system, which, while isn't the nicest, is what it is.  This driver
is going to cause problems for distros, and for users, as if you build
them both as modules, it is pseudo-random which one will end up being
loaded first and binding to the device.

Which is not something we generally want to do to our users at all.

> > How about just sending patches in an incremental way, fixing problems in
> > the current driver that you know about, and adding support for all of
> > this goodness as well?  That's what all 2000+ other kernel developers
> > do when they want to make changes like this.
> >
> 
> I understand what you mean although too many things went from from the
> beginning on and people were in general not participating at
> discussions, it slightly changed now but the codebase evolved over
> time.
> 
> I'll be happy to do so for upcoming patches.

So we should drop our current rules for you, and trust that things from
here on out will be correct?  That doesn't sound like a good idea for us
to do, would you recommend doing something like that for someone else?

Especially given the above mentioned problems for users, this really
isn't going to work out.

Please send patches that evolve the current drivers into supporting
these new devices so that our development model is followed, and no user
ends up in trouble.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:27     ` Greg KH
@ 2008-10-22 22:35       ` Markus Rechberger
  2008-10-22 22:49         ` Greg KH
  0 siblings, 1 reply; 29+ messages in thread
From: Markus Rechberger @ 2008-10-22 22:35 UTC (permalink / raw)
  To: Greg KH
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 12:27 AM, Greg KH <greg@kroah.com> wrote:
> On Thu, Oct 23, 2008 at 12:24:31AM +0200, Markus Rechberger wrote:
>> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
>> > On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>> >>     em2880-dvb:
>> >>     * supporting the digital part of Empia based devices, which
>> >> includes ATSC, ISDB-T and DVB-T
>> >
>> > <snip>
>> >
>> > Doesn't this driver duplicate some of the existing devices we already
>> > support with the current in-kernel driver?  If so, why not just add the
>> > new device support to the existing driver instead of duplicating
>> > everything?
>> >
>> > This is going to cause a big problem for distros as they will not know
>> > which to enable, so they will probably just disable this one, which is
>> > what I don't think you want to have happen :(
>> >
>>
>> the current driver doesn't support most devices which are in there,
>
> Then why not just add new device support to the existing one?
>
>> also the alsa audio driver can easily crash the whole system. (It's my
>> code so I know what was wrong there).
>
> Why not send patches to fix it?
>

that's why I'm sending that request at the moment, development still goes on
on my side.

>> Very likely the best would be to replace the available driver with it
>> but I don't care, alot people use and have been using the driver from
>> mcentral.de for a long time, development has always been opensource
>> there too.
>
> Dropping existing code and replacing it entirely with a new base is not
> how Linux kernel development works for the most part.
>

the patch adds the driver from mcentral.de as alternative entry, not
the best way
but it currently supports almost all devices which have an entry in there.
2 or 3 entries definitely don't work yet due missing external drivers
which would
come in later.

> How about just sending patches in an incremental way, fixing problems in
> the current driver that you know about, and adding support for all of
> this goodness as well?  That's what all 2000+ other kernel developers
> do when they want to make changes like this.
>

I understand what you mean although too many things went from from the
beginning on
and people were in general not participating at discussions, it
slightly changed now but
the codebase evolved over time.

I'll be happy to do so for upcoming patches.

Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:24   ` Markus Rechberger
  2008-10-22 22:26     ` Markus Rechberger
@ 2008-10-22 22:27     ` Greg KH
  2008-10-22 22:35       ` Markus Rechberger
  2008-11-26 19:12     ` Aidan Thornton
  2 siblings, 1 reply; 29+ messages in thread
From: Greg KH @ 2008-10-22 22:27 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 12:24:31AM +0200, Markus Rechberger wrote:
> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
> > On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
> >>     em2880-dvb:
> >>     * supporting the digital part of Empia based devices, which
> >> includes ATSC, ISDB-T and DVB-T
> >
> > <snip>
> >
> > Doesn't this driver duplicate some of the existing devices we already
> > support with the current in-kernel driver?  If so, why not just add the
> > new device support to the existing driver instead of duplicating
> > everything?
> >
> > This is going to cause a big problem for distros as they will not know
> > which to enable, so they will probably just disable this one, which is
> > what I don't think you want to have happen :(
> >
> 
> the current driver doesn't support most devices which are in there,

Then why not just add new device support to the existing one?

> also the alsa audio driver can easily crash the whole system. (It's my
> code so I know what was wrong there).

Why not send patches to fix it?

> Very likely the best would be to replace the available driver with it
> but I don't care, alot people use and have been using the driver from
> mcentral.de for a long time, development has always been opensource
> there too.

Dropping existing code and replacing it entirely with a new base is not
how Linux kernel development works for the most part.

How about just sending patches in an incremental way, fixing problems in
the current driver that you know about, and adding support for all of
this goodness as well?  That's what all 2000+ other kernel developers
do when they want to make changes like this.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:24   ` Markus Rechberger
@ 2008-10-22 22:26     ` Markus Rechberger
  2008-10-22 22:27     ` Greg KH
  2008-11-26 19:12     ` Aidan Thornton
  2 siblings, 0 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-10-22 22:26 UTC (permalink / raw)
  To: Greg KH
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 12:24 AM, Markus Rechberger
<mrechberger@gmail.com> wrote:
> On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
>> On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>>>     em2880-dvb:
>>>     * supporting the digital part of Empia based devices, which
>>> includes ATSC, ISDB-T and DVB-T
>>
>> <snip>
>>
>> Doesn't this driver duplicate some of the existing devices we already
>> support with the current in-kernel driver?  If so, why not just add the
>> new device support to the existing driver instead of duplicating
>> everything?
>>
>> This is going to cause a big problem for distros as they will not know
>> which to enable, so they will probably just disable this one, which is
>> what I don't think you want to have happen :(
>>
>
> the current driver doesn't support most devices which are in there,
> also the alsa
> audio driver can easily crash the whole system. (It's my code so I
> know what was wrong there).
>

since the USB IDs were taken from the driver from mcentral.de without
testing them at all.

> The core video code is already too much off, the VBI code added alot complexity
> to it it does frame slicing on the fly.
>
> Those devices ship VBI+VIDEO within 1 datastream, VBI and Video aren't
> that different
> in the system. both interfaces provide framebuffers through a mmap'ed interface.
> If all the VBI buffers are filled the data has to be sliced off in any
> case while providing
> the same bottom data ot the Video interface
>
> http://mcentral.de/hg/~mrec/em28xx-new/shortlog (there are more than
> 200 changelog entries
> what happened in detail).
>
> The development has been split off (first due limitations in the
> kernel, afterwards due ..., and finally
> due the restriction that all that has to work without a framework
> upgrade on the eeePC).
>
> diffing the 2 available drivers shows up that only the core is twice
> as big as the one which is currently
> in the driver (the result of 2-3 years asynchronous development).
>
> The driver is currently also tested with signal generators (different
> inputs, and different video standards).
>
> Very likely the best would be to replace the available driver with it
> but I don't care, alot people use and have been using the driver from
> mcentral.de for a long time, development has always been opensource
> there too.
>
> regards,
> Markus
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 22:09 ` Greg KH
@ 2008-10-22 22:24   ` Markus Rechberger
  2008-10-22 22:26     ` Markus Rechberger
                       ` (2 more replies)
  2008-10-23  9:29   ` Alan Cox
  1 sibling, 3 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-10-22 22:24 UTC (permalink / raw)
  To: Greg KH
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Thu, Oct 23, 2008 at 12:09 AM, Greg KH <greg@kroah.com> wrote:
> On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>>     em2880-dvb:
>>     * supporting the digital part of Empia based devices, which
>> includes ATSC, ISDB-T and DVB-T
>
> <snip>
>
> Doesn't this driver duplicate some of the existing devices we already
> support with the current in-kernel driver?  If so, why not just add the
> new device support to the existing driver instead of duplicating
> everything?
>
> This is going to cause a big problem for distros as they will not know
> which to enable, so they will probably just disable this one, which is
> what I don't think you want to have happen :(
>

the current driver doesn't support most devices which are in there,
also the alsa
audio driver can easily crash the whole system. (It's my code so I
know what was wrong there).

The core video code is already too much off, the VBI code added alot complexity
to it it does frame slicing on the fly.

Those devices ship VBI+VIDEO within 1 datastream, VBI and Video aren't
that different
in the system. both interfaces provide framebuffers through a mmap'ed interface.
If all the VBI buffers are filled the data has to be sliced off in any
case while providing
the same bottom data ot the Video interface

http://mcentral.de/hg/~mrec/em28xx-new/shortlog (there are more than
200 changelog entries
what happened in detail).

The development has been split off (first due limitations in the
kernel, afterwards due ..., and finally
due the restriction that all that has to work without a framework
upgrade on the eeePC).

diffing the 2 available drivers shows up that only the core is twice
as big as the one which is currently
in the driver (the result of 2-3 years asynchronous development).

The driver is currently also tested with signal generators (different
inputs, and different video standards).

Very likely the best would be to replace the available driver with it
but I don't care, alot people use and have been using the driver from
mcentral.de for a long time, development has always been opensource
there too.

regards,
Markus

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 1/7] Adding empia base driver
  2008-10-22 21:14 Markus Rechberger
@ 2008-10-22 22:09 ` Greg KH
  2008-10-22 22:24   ` Markus Rechberger
  2008-10-23  9:29   ` Alan Cox
  2008-11-01 13:59 ` Hans Verkuil
  1 sibling, 2 replies; 29+ messages in thread
From: Greg KH @ 2008-10-22 22:09 UTC (permalink / raw)
  To: Markus Rechberger
  Cc: Linux Kernel Mailing List, em28xx, acano, Andre Kelmanson,
	Bouwsma Barry, Dan Kreiser, Frank Neuber, Jelle de Jong,
	John Stowers, Lukas Kuna, Stefan Vonolfen, Stephan Berberig,
	Thomas Giesecke, Vitaly Wool, Zhenyu Wang

On Wed, Oct 22, 2008 at 11:14:36PM +0200, Markus Rechberger wrote:
>     em2880-dvb:
>     * supporting the digital part of Empia based devices, which
> includes ATSC, ISDB-T and DVB-T

<snip>

Doesn't this driver duplicate some of the existing devices we already
support with the current in-kernel driver?  If so, why not just add the
new device support to the existing driver instead of duplicating
everything?

This is going to cause a big problem for distros as they will not know
which to enable, so they will probably just disable this one, which is
what I don't think you want to have happen :(

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 1/7] Adding empia base driver
@ 2008-10-22 21:14 Markus Rechberger
  2008-10-22 22:09 ` Greg KH
  2008-11-01 13:59 ` Hans Verkuil
  0 siblings, 2 replies; 29+ messages in thread
From: Markus Rechberger @ 2008-10-22 21:14 UTC (permalink / raw)
  To: Linux Kernel Mailing List, em28xx
  Cc: acano, Andre Kelmanson, Bouwsma Barry, Dan Kreiser, Frank Neuber,
	Jelle de Jong, John Stowers, Lukas Kuna, Stefan Vonolfen,
	Stephan Berberig, Thomas Giesecke, Vitaly Wool, Zhenyu Wang

[-- Attachment #1: Type: text/plain, Size: 1284 bytes --]

    em2880-dvb:
    * supporting the digital part of Empia based devices, which
includes ATSC, ISDB-T and DVB-T

    em28xx-aad.c:
    * alternative audio driver, can be used instead of em28xx-audio if
alsa is not available
    or not compiled into the kernel, it provides a raw interface to
the PCM samples

    em28xx-audio.c:
    * em28xx alsa driver and audio driver for FM radio

    em28xx-audioep.c:
    * em28xx alsa driver for devices which are set to vendor specific
audio on interface 1,
    in that case snd-usb-audio will not attach to the interface and
em28xx-audioep will be needed

    em28xx-cards.c:
    * card definition and initial setup of devices.

    em28xx-core.c:
    * core videohandling and VBI frame slicing

    em28xx-i2c.c:
    * i2c setup and GPIO setup handling of the devices (including
em2888 based ones)

    em28xx-input.c:
    * currently mostly disabled since the linuxtv input handling is
broken by design and racy

    em28xx-keymaps.c:
    * keymap references of some remotes (could be merged into
ir-common, although as mentioned
    this should be in userland done by lirc).

    em28xx-video.c:
    * inode handling for analog TV, radio and VBI, also some device probing

    em28xx-webcam.c:
    * videology webcam specific i2c commands

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: em28xx-01.diff --]
[-- Type: text/x-diff; name=em28xx-01.diff, Size: 428709 bytes --]

commit a18816f6b91629eb6038aed4815e4fb94ddc0a6b
Author: Markus Rechberger <mrechberger@sundtek.de>
Date:   Wed Oct 22 21:56:45 2008 +0200

    Adding empia base driver
    
    em2880-dvb:
    * supporting the digital part of Empia based devices, which includes ATSC, ISDB-T and DVB-T
    
    em28xx-aad.c:
    * alternative audio driver, can be used instead of em28xx-audio if alsa is not available
    or not compiled into the kernel, it provides a raw interface to the PCM samples
    
    em28xx-audio.c:
    * em28xx alsa driver and audio driver for FM radio
    
    em28xx-audioep.c:
    * em28xx alsa driver for devices which are set to vendor specific audio on interface 1,
    in that case snd-usb-audio will not attach to the interface and em28xx-audioep will be needed
    
    em28xx-cards.c:
    * card definition and initial setup of devices.
    
    em28xx-core.c:
    * core videohandling and VBI frame slicing
    
    em28xx-i2c.c:
    * i2c setup and GPIO setup handling of the devices (including em2888 based ones)
    
    em28xx-input.c:
    * currently mostly disabled since the linuxtv input handling is broken by design and racy
    
    em28xx-keymaps.c:
    * keymap references of some remotes (could be merged into ir-common, although as mentioned
    this should be in userland done by lirc).
    
    em28xx-video.c:
    * inode handling for analog TV, radio and VBI, also some device probing
    
    em28xx-webcam.c:
    * videology webcam specific i2c commands

Signed-off-by: Markus Rechberger <mrechberger@sundtek.de>

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 47102c2..a726c59 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -783,6 +783,8 @@ source "drivers/media/video/pvrusb2/Kconfig"
 
 source "drivers/media/video/em28xx/Kconfig"
 
+source "drivers/media/video/empia/Kconfig"
+
 source "drivers/media/video/usbvision/Kconfig"
 
 source "drivers/media/video/usbvideo/Kconfig"
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 16962f3..9357368 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_MEYE) += meye.o
 obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
 obj-$(CONFIG_VIDEO_CX88) += cx88/
 obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
+obj-$(CONFIG_VIDEO_EMPIA) += empia/
 obj-$(CONFIG_VIDEO_USBVISION) += usbvision/
 obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o
 obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
diff --git a/drivers/media/video/empia/Kconfig b/drivers/media/video/empia/Kconfig
new file mode 100644
index 0000000..6bd0ac3
--- /dev/null
+++ b/drivers/media/video/empia/Kconfig
@@ -0,0 +1,61 @@
+config VIDEO_EMPIA
+	tristate "Empia EM28xx USB video capture support"
+	depends on VIDEO_DEV && I2C
+	select VIDEO_BUF
+	select VIDEO_TUNER
+	select VIDEO_SAA711X if VIDEO_HELPER_CHIPS_AUTO
+	select VIDEO_TVP5150 if VIDEO_HELPER_CHIPS_AUTO
+	---help---
+	  This em28xx driver has been worked on since 2006 and constantly
+	  improved to support the latest available devices including
+	  different TV standards as well as VBI.
+
+	  the difference between this and the other em28xx driver is that
+	  it's well tested and supports more devices.
+
+config VIDEO_EMPIA_DTV
+	tristate "Empia EM288x/EM275x DVB-T support"
+	depends on VIDEO_EMPIA && DVB_CORE
+	select FW_LOADER
+	select DVB_TUNER_MT2060
+	select DVB_TUNER_QT1010
+	select DVB_LGDT3304
+	select DVB_MT352 if !DVB_FE_CUSTOMISE
+	select DVB_ZL10353 if !DVB_FE_CUSTOMISE
+	select DVB_LGDT330X if !DVB_FE_CUSTOMISE
+	select DVB_LGDT3304 if !DVB_FE_CUSTOMISE
+	---help---
+	  Support for Empia em288x/em2870 DVB-T/ATSC and ISDB-T extension
+
+	  If unsure say N.
+
+config VIDEO_EMPIA_AUDIO
+	tristate "Empia EM28xx USB Audio support"
+	depends on VIDEO_EMPIA
+	---help---
+	  This is an audio driver for Em28x[1/2] based usb video devices,
+	  this will be used by Hauppauge HVR 900 for analogue TV
+	  instead of snd-usb-audio.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called em28xx
+
+config VIDEO_EMPIA_AUDIOEP
+	tristate "Empia EM28xx USB Audio support (second configuration)"
+	depends on VIDEO_EMPIA
+	---help---
+	  This is another audiodriver for em28xx based devices which have
+	  a vendor specific audio interface on interface 1.
+
+	  choose yes if unsure, the driver will only attach if your
+	  Empia device has such a configuration
+
+config VIDEO_EMPIA_AAD_AUDIO
+	tristate "Empia EM28xx USB Audio support (not alsa compatible)"
+	depends on VIDEO_EMPIA
+	---help---
+	  This driver adds vendor specific audio support to em28xx based devices
+	  It can be used as an alternative if alsa is not available or enabled
+	  in the kernel (eg. to support audio with OSS).
+
+	  choose no if unsure
diff --git a/drivers/media/video/empia/Makefile b/drivers/media/video/empia/Makefile
new file mode 100644
index 0000000..9ee2376
--- /dev/null
+++ b/drivers/media/video/empia/Makefile
@@ -0,0 +1,19 @@
+em28xx-objs     := em28xx-video.o em28xx-i2c.o em28xx-cards.o em28xx-core.o \
+		   em28xx-input.o em28xx-webcam.o em28xx-keymaps.o
+
+em28xx-dvb-objs   := em2880-dvb.o 
+
+obj-$(CONFIG_VIDEO_EMPIA) += em28xx.o
+obj-$(CONFIG_VIDEO_EMPIA_AUDIO) += em28xx-audio.o
+obj-$(CONFIG_VIDEO_EMPIA_AUDIOEP) += em28xx-audioep.o
+obj-$(CONFIG_VIDEO_EMPIA_AAD_AUDIO) += em28xx-aad.o
+obj-$(CONFIG_VIDEO_EMPIA_DTV) += em28xx-dvb.o
+obj-$(CONFIG_VIDEO_EMPIA) += cx25843/
+obj-$(CONFIG_VIDEO_EMPIA_DTV) += lgdt3304/
+obj-$(CONFIG_VIDEO_EMPIA) += xc3028/
+obj-$(CONFIG_VIDEO_EMPIA) += xc5000/
+
+EXTRA_CFLAGS += -Idrivers/media/video
+EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
+EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
+EXTRA_CFLAGS += -Idrivers/media/common/tuners
diff --git a/drivers/media/video/empia/em2880-dvb.c b/drivers/media/video/empia/em2880-dvb.c
new file mode 100644
index 0000000..bd38018
--- /dev/null
+++ b/drivers/media/video/empia/em2880-dvb.c
@@ -0,0 +1,1139 @@
+/*
+ *  Empiatech em2880 DVB-T extension
+ *
+ *  Copyright (C) 2006/2007/2008 Markus Rechberger <mrechberger@sundtek.de>
+ *
+ *  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/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/suspend.h>
+#include <linux/dvb/frontend.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#include "em28xx.h"
+
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#include "dvb_frontend.h"
+
+#include "zl10353.h"
+#include "mt352.h"
+#include "xc5000/xc5000_control.h"
+#ifdef MICRONAS_DRX3975D
+#include "drx3973d/drx3973d_demod.h"
+#endif
+#include "lgdt3304/lgdt3304.h"
+#include "qt1010.h"
+#include "mt2060.h"
+#ifdef ADIMTV102
+#include "adimtv102/adimtv102.h"
+#endif
+
+#include "lgdt330x.h"
+#include "sharp/s921_module.h"
+
+#define EM2880_DVB_NUM_PACKETS 64
+#define EM2880_URB_COUNT 32
+
+#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+#endif
+
+
+#define xc3028_offset_8mhz 2750000;
+#define xc3028_offset_7mhz 2250000;
+#define xc3028_offset_6mhz 2750000;
+#define xc3028_offset_atsc 1750000;
+
+
+MODULE_DESCRIPTION("Empiatech em2880 DVB-T extension");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_LICENSE("GPL");
+
+#ifdef MICRONAS_DRX3975D
+DRX3973DData_t DRX3973DData_g = {
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	DRX_UIO_MODE_DISABLE,
+	DRX_UIO_MODE_DISABLE,
+	{
+		DRX3973D_AGC_CTRL_AUTO,
+		0,
+		0,
+		0,
+		0,
+		0
+	},
+	{
+		DRX3973D_AGC_CTRL_AUTO,
+		0,
+		0,
+		0,
+		0,
+		0
+	},
+	DRX3973D_IFFILTER_SAW,
+	FALSE,
+	DRX_BANDWIDTH_8MHZ,
+	FALSE,
+	DRX3973D_APPENV_PORTABLE,
+	TRUE,
+	FALSE,
+#ifndef _CH_
+	{
+		"01234567890",
+		"01234567890"
+	},
+	{
+		{
+			DRX_MODULE_UNKNOWN,
+			(char *)(NULL),
+			0,
+			0,
+			0,
+			(char *)(NULL)
+		},
+		{
+			DRX_MODULE_UNKNOWN,
+			(char *)(NULL),
+			0,
+			0,
+			0,
+			(char *)(NULL)
+		}
+	},
+	{
+		{
+			(pDRXVersion_t)(NULL),
+			(pDRXVersionList_t)(NULL)
+		},
+		{
+			(pDRXVersion_t)(NULL),
+			(pDRXVersionList_t)(NULL)
+		}
+	},
+	DRX3973D_SPIN_UNKNOWN,
+	{
+	},
+	FALSE,
+
+#if (DRXD_TYPE_B)
+	DRX3973D_I2C_INIT_ASEL
+#endif
+
+
+
+#endif
+};
+
+/**
+ * \var DRX3973DDefaultAddr_g
+ * \brief Default I2C address and device identifier.
+ */
+I2CDeviceAddr_t DRX3973DDefaultAddr_g = {
+#define DRX3973D_DEF_I2C_ADDR (0xe0)>>1
+	DRX3973D_DEF_I2C_ADDR,     /* i2c address */
+#define DRX3973D_DEF_DEMOD_DEV_ID   (1)
+	DRX3973D_DEF_DEMOD_DEV_ID,  /* device id */
+	NULL       /* private data structure */
+};
+
+/**
+ * \var DRX3973DDefaultCommAttr_g
+ * \brief Default common attributes of a drx3973d demodulator instance.
+ */
+DRXCommonAttr_t DRX3973DDefaultCommAttr_g = {
+	(pu8_t)NULL,
+	0,
+	TRUE,
+	4560,
+	48000L,
+	12000L,
+	0L,
+	FALSE,
+	TRUE,
+	TRUE,
+	TRUE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	32000000UL,
+	FALSE,
+	(pDRXScanParam_t)(NULL),
+	0,
+	0,
+	FALSE,
+	0L,
+	0L,
+	100,
+	DRX_LOCKED,
+	FALSE,
+	DRX_POWER_DOWN,
+	1,
+	0L,
+	0L
+};
+
+#endif
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "em2880-dvb debug level (default off)");
+
+#define dprintk(lvl, fmt, args...) if (debug >= lvl) do {\
+	printk(fmt, ##args); } while (0)
+
+
+static int em2880_set_alternate(struct em2880_dvb *dvb_dev);
+
+/* ------------------------------------------------------------------ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static void em2880_complete_irq(struct urb *urb, struct pt_regs *ptregs)
+{
+#else
+static void em2880_complete_irq(struct urb *urb)
+{
+#endif
+	struct em2880_dvb *dvb_dev = urb->context;
+	int i;
+	int status;
+
+	if (dvb_dev == NULL) {
+		dprintk(1, "em2880-dev.c: device not valid!\n");
+		return;
+	}
+
+	if (debug && urb->status < 0) {
+		printk(KERN_ERR"%s:%u:%s(): status = %i\n", __FILE__,
+				__LINE__, __func__, urb->status);
+	}
+
+	switch (urb->status) {
+		case -ESHUTDOWN:
+			return;
+			break;
+	}
+
+	if (urb->status == -ENOENT)
+		return;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		if (urb->iso_frame_desc[i].status != 0) {
+			dprintk(1, "em2880-dvb.c: status != 0\n");
+			continue;
+		} else if (urb->iso_frame_desc[i].actual_length > 0)
+			dvb_dmx_swfilter(&dvb_dev->demux,
+					urb->transfer_buffer +
+					urb->iso_frame_desc[i].offset,
+					urb->iso_frame_desc[i]
+					.actual_length);
+	}
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status)
+		dprintk(1, "resubmitting urb failed!\n");
+}
+
+static void em2880_stop_stream(struct em2880_dvb *dvb_dev)
+{
+	int i;
+	int arg;
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		if (dvb_dev->urb[i]) {
+			usb_kill_urb(dvb_dev->urb[i]);
+			if (dvb_dev->transfer_buffer[i]) {
+				usb_buffer_free(dvb_dev->udev,
+						(EM2880_DVB_NUM_PACKETS *
+						 dvb_dev->dtv_packetsize),
+						dvb_dev->transfer_buffer[i],
+						dvb_dev->urb[i]->transfer_dma);
+			}
+			usb_free_urb(dvb_dev->urb[i]);
+		}
+		dvb_dev->urb[i] = NULL;
+		dvb_dev->transfer_buffer[i] = NULL;
+	}
+	arg=EM28XX_REG_OFF;
+	dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg);
+
+}
+
+static int em2880_start_stream(struct em2880_dvb *dvb_dev)
+{
+	int i, errCode;
+	int arg;
+	const int sb_size = EM2880_DVB_NUM_PACKETS * dvb_dev->dtv_packetsize;
+
+	dprintk(1, "em2880-dvb.c: got start stream request %s\n", __func__);
+	arg=EM28XX_REG_ON;
+	dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg);
+
+	em2880_set_alternate(dvb_dev);
+
+	/* allocate urbs */
+
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		struct urb *urb;
+		int j, k;
+		/* allocate transfer buffer */
+		urb = usb_alloc_urb(EM2880_DVB_NUM_PACKETS, GFP_KERNEL);
+		if (!urb) {
+			dprintk(1, "cannot alloc urb %i\n", i);
+			return -ENOMEM;
+		}
+		dvb_dev->transfer_buffer[i] =
+			usb_buffer_alloc(dvb_dev->udev, sb_size,
+					GFP_KERNEL, &urb->transfer_dma);
+
+		if (!dvb_dev->transfer_buffer[i]) {
+			dprintk(1, "unable to allocate %i bytes for transfer"
+				   "buffer %i\n", sb_size, i);
+
+			return -ENOMEM;
+		}
+		memset(dvb_dev->transfer_buffer[i], 0, sb_size);
+		urb->dev = dvb_dev->udev;
+		urb->context = dvb_dev;
+		urb->pipe = usb_rcvisocpipe(dvb_dev->udev, 0x84);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->interval = 1;
+		urb->transfer_buffer = dvb_dev->transfer_buffer[i];
+		urb->complete = em2880_complete_irq;
+		urb->number_of_packets = EM2880_DVB_NUM_PACKETS;
+		urb->transfer_buffer_length = sb_size;
+		for (j = k = 0; j < EM2880_DVB_NUM_PACKETS;
+				j++, k += dvb_dev->dtv_packetsize) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+				dvb_dev->dtv_packetsize;
+		}
+		dvb_dev->urb[i] = urb;
+	}
+
+	/* submit urbs */
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		errCode = usb_submit_urb(dvb_dev->urb[i], GFP_KERNEL);
+		if (errCode) {
+			dprintk(1, "submit of urb %i failed (error=%i)\n",
+					i,
+					errCode);
+			return errCode;
+		}
+	}
+	return 0;
+}
+
+static int em2880_set_alternate(struct em2880_dvb *dvb_dev)
+{
+	int errCode;
+	errCode = usb_set_interface(dvb_dev->udev, dvb_dev->em28xx_dev->usb_interface, 1);
+
+	return errCode;
+}
+
+static int em2880_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct em2880_dvb *dvb_dev = demux->priv;
+	dprintk(1, "em2880-dvb.c: got start feed request %s\n", __func__);
+
+	if (mutex_lock_interruptible(&dvb_dev->sem))
+		return -ERESTARTSYS;
+
+	if (dvb_dev->streaming == 0)
+		em2880_start_stream(dvb_dev);
+
+	dvb_dev->streaming++;
+	mutex_unlock(&dvb_dev->sem);
+	return 0;
+}
+
+static int em2880_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct em2880_dvb *dvb_dev = demux->priv;
+
+	dprintk(1, "em2880-dvb.c: got stop feed request %s\n", __func__);
+
+	if (mutex_lock_interruptible(&dvb_dev->sem))
+		return -ERESTARTSYS;
+	if (0 == --dvb_dev->streaming)
+		em2880_stop_stream(dvb_dev);
+	mutex_unlock(&dvb_dev->sem);
+	return 0;
+}
+
+static int em28xx_ts_bus_ctrl_int(struct em28xx *dev, int acquire)
+{
+	u8 gpio;
+	gpio = dev->em28xx_read_reg(dev, 04);
+	gpio &= ~((u8)0x3);
+
+	if (acquire == 1)
+		gpio |= 1;
+
+	return dev->em28xx_write_regs(dev, 0x04, &gpio, 1);
+}
+
+static int em28xx_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	return dev->em28xx_acquire(dev, EM28XX_DVBT, acquire);
+}
+
+static struct zl10353_config em2880_zl10353_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+	.if2 = 45600
+};
+
+struct bcode {
+	int reg;
+	char *txt;
+	int len;
+	int delay;
+};
+
+
+static int mt352_pinnacle_init(struct dvb_frontend *fe)
+{
+	int i;
+	struct bcode zlconf[]={
+			{0x1e,"\x8a\x2c",2,0},
+			{0x1e,"\x89\x38",2,0},
+			{0x1e,"\x50\x80",2,0},
+			{0x1e,"\x8e\x40",2,0},
+			{0x1e,"\x69\x00",2,0},
+			{0x1e,"\x6a\xff",2,0},
+			{0x1e,"\x6b\xff",2,0},
+			{0x1e,"\x6c\x00",2,0},
+			{0x1e,"\x6d\xff",2,0},
+			{0x1e,"\x6e\x00",2,0},
+			{0x1e,"\x6f\x40",2,0},
+			{0x1e,"\x70\x40",2,0},
+			{0x1e,"\x68\xa0",2,0},
+
+			{0x1e,"\x56\x31",2,0}, // set input frequency
+			{0x1e,"\x57\xb8",2,0},
+			{0x1e,"\x75\x33",2,0},
+
+			{0x1e,"\x7c\x00",2,0},
+			{0x1e,"\x7d\x4d",2,0},
+			{0x1e,"\xb5\x7a",2,0},
+			{0x1e,"\x51\x40",2,0},
+			{0x1e,"\x52\x80",2,0},
+			{0x1e,"\x53\x50",2,0},
+			{0x1e,"\x5d\x01",2,0},
+			{}
+	};
+	for(i=0;zlconf[i].txt;i++)
+		fe->ops.write(fe, zlconf[i].txt,zlconf[i].len);
+	return 0;
+}
+
+
+static struct mt352_config em2880_mt352_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	//.parallel_ts = 1,
+	.if2 = 45600,
+	.demod_init = mt352_pinnacle_init,
+};
+
+
+static struct zl10353_config em2880_zl10353_pinnacle = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct zl10353_config em2880_kworld_355u_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct lgdt330x_config em2880_lgdt3303_dev = {
+	.demod_address = 0x0e,
+	.demod_chip = LGDT3303
+};
+
+static struct mt2060_config em2870_mt2060_config = {
+	.i2c_address = 0x60
+};
+
+static struct qt1010_config em2870_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static int kworld355u_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct zl10353_state {
+		struct i2c_adapter *i2c;
+		struct dvb_frontend frontend;
+
+		struct zl10353_config config;
+
+		enum fe_bandwidth bandwidth;
+	};
+	struct zl10353_state *state = fe->demodulator_priv;
+
+	struct em28xx *dev = state->i2c->algo_data;
+
+	return em28xx_ts_bus_ctrl_int(dev, enable);
+}
+
+static struct lgdt3304_config lgdt3304_atsc_config = {
+	.i2c_address = 0x0e
+};
+
+static int em28xx_set_params(struct dvb_frontend *fe,
+		struct dvb_frontend_parameters *params)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	int i;
+	unsigned long frequency = params->frequency;
+	struct dvb_ofdm_parameters *op = &params->u.ofdm;
+
+	if (!dev->tuner)
+		return -EINVAL;
+
+	switch (fe->ops.info.type) {
+	case FE_OFDM:
+		if (dev->dvbnorm->bandwidth !=
+				op->bandwidth) {
+			for (i = 0; dev->board->dvbnorms[i].tv_mode || dev->board->dvbnorms[i].index; i++) {
+				if (dev->board->dvbnorms[i].bandwidth == op->bandwidth) {
+					switch (dev->tuner_type) {
+					case TUNER_XCEIVE_XC3028:
+					{
+						struct xc3028_init_cmd cmd;
+						dvbdev->bw_index = i;
+						cmd.new_tv_mode_ptr = dev->board->dvbnorms[i].tv_mode;
+						cmd.new_channel_map_ptr = dev->board->dvbnorms[i].channelmap;
+						dev->dvbnorm = &dev->board->dvbnorms[i];
+						if (dev->tuner->tuner_cmd)
+							dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+						break;
+					}
+					case TUNER_XCEIVE_XC5000:
+					{
+						struct xc_std_conf cmd;
+						dvbdev->bw_index = i;
+						cmd.index = dev->board->dvbnorms[i].index;
+						dev->dvbnorm = &dev->board->dvbnorms[i];
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+						break;
+					}
+					}
+					break;
+				}
+			}
+		}
+
+		if (dev->tuner->set_frequency) {
+			switch (params->u.ofdm.bandwidth) {
+			case BANDWIDTH_AUTO:
+			case BANDWIDTH_8_MHZ:
+				frequency -= xc3028_offset_8mhz;
+				break;
+			case BANDWIDTH_7_MHZ:
+				frequency -= xc3028_offset_7mhz;
+				break;
+			case BANDWIDTH_6_MHZ:
+				frequency -= xc3028_offset_6mhz;
+				break;
+			default:
+				return -EINVAL;
+			}
+			dev->tuner->set_frequency(dev->tuner, frequency);
+			dev->dctl_freq = frequency;
+		}
+		break;
+	case FE_QAM:
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_init_cmd cmd;
+			dvbdev->bw_index = 0;
+			cmd.new_tv_mode_ptr = dev->board->qamnorms[0].tv_mode;
+			cmd.new_channel_map_ptr = dev->board->qamnorms[0].channelmap;
+			dev->qamnorm = &dev->board->qamnorms[0];
+			if (dev->tuner->tuner_cmd)
+				dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc_std_conf cmd;
+			dvbdev->bw_index = 0;
+			cmd.index = dev->board->atscnorms[0].index;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+			break;
+		}
+		}
+		frequency -= xc3028_offset_atsc;
+
+		dev->tuner->set_frequency(dev->tuner, frequency);
+		dev->dctl_freq = frequency;
+		break;
+	case FE_ATSC:
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_init_cmd cmd;
+			dvbdev->bw_index = 0;
+			cmd.new_tv_mode_ptr = dev->board->atscnorms[0].tv_mode;
+			cmd.new_channel_map_ptr = dev->board->atscnorms[0].channelmap;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			if (dev->tuner->tuner_cmd)
+				dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc_std_conf cmd;
+			dvbdev->bw_index = 0;
+			cmd.index = dev->board->atscnorms[0].index;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+			break;
+		}
+		}
+
+		frequency -= xc3028_offset_atsc;
+		dev->tuner->set_frequency(dev->tuner, frequency);
+		dev->dctl_freq = frequency;
+		break;
+	case FE_QPSK:
+		printk(KERN_INFO"FE_QPSK currently not supported\n");
+		break;
+	}
+
+	return 0;
+}
+
+static int em28xx_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	*frequency = dev->dctl_freq;
+	return 0;
+}
+
+static int em28xx_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	*bandwidth = (u32)dev->board->dvbnorms[dvbdev->bw_index].bandwidth;
+	return 0;
+}
+
+static int em28xx_dvb_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	int gpio_arg;
+
+	printk(KERN_INFO"em28xx_dvb_init\n");
+
+	if (dev->mode != V4L2_TUNER_DIGITAL_TV) {
+		printk(KERN_INFO"switching over from %d\n", dev->mode);
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+
+		gpio_arg = EM28XX_REG_ON;
+		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+		mdelay(100);
+	}
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+	msleep(100);
+	switch (dev->tuner_type) {
+	case TUNER_XCEIVE_XC3028:
+	{
+		struct xc3028_init_cmd cmd;
+		if (dev->dev_modes & EM28XX_DVBT) {
+			cmd.new_tv_mode_ptr = dev->dvbnorm->tv_mode;
+			cmd.new_channel_map_ptr = dev->dvbnorm->channelmap;
+		} else if (dev->dev_modes & EM28XX_ATSC) {
+			cmd.new_tv_mode_ptr = dev->atscnorm->tv_mode;
+			cmd.new_channel_map_ptr = dev->atscnorm->channelmap;
+		}
+			
+		if (dev->tuner && dev->tuner->tuner_cmd)
+			dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+		break;
+	}
+	case TUNER_XCEIVE_XC5000:
+	{
+		struct xc_std_conf cmd;
+		if (dev->dev_modes & EM28XX_DVBT) {
+			cmd.index = dev->dvbnorm->index;
+		} else if (dev->dev_modes & EM28XX_ATSC) {
+			cmd.index = dev->atscnorm->index;
+		}
+
+		printk(KERN_INFO"initializing: %d\n", dev->dvbnorm->index);
+		dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL);
+
+		if (dev->tuner && dev->tuner->tuner_cmd)
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+	}
+	}
+
+	return 0;
+}
+
+static int em28xx_s921_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	switch (dev->em_type) {
+	case EM2875:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		break;
+	default:
+		break;
+	}
+	if (dvbdev->init_override)
+		dvbdev->init_override(fe);
+
+	return 0;
+}
+
+static int em28xx_zl10353_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+
+	int gpio_arg;
+
+//	if (dev->mode != V4L2_TUNER_DIGITAL_TV) {
+		dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
+		dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1);
+
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+		gpio_arg = EM28XX_REG_ON;
+		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+		mdelay(100);
+//	}
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+	msleep(100);
+
+	if (dvbdev->init_override)
+		dvbdev->init_override(fe);
+
+	switch (dev->em_type) {
+	case EM2888:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		/* TODO move this to zl10353, though keep backward compatibility */
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2);
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x64", 2);
+		break;
+	case EM2875:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		break;
+	default:
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2);
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x44", 2);
+		break;
+	}
+	return 0;
+}
+
+
+static int em28xx_zl10353_sleep(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+//	int gpio_arg = EM28XX_REG_OFF;
+	if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) {
+//		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		printk(KERN_INFO"powered down zl10353\n");
+	}
+	return 0;
+}
+
+
+static int em28xx_dvb_sleep(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+#if 0
+	int gpio_arg;
+#endif
+	if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) {
+//		dev->tuner->shutdown(dev->tuner);
+#if 0
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		printk(KERN_INFO"powered down xc3028\n");
+#endif
+	}
+	return 0;
+}
+
+#ifdef ADIMTV102
+struct adimtv102_config dmbt_adim_config = {
+	.i2c_address = 0xc2>>1
+};
+
+#endif
+
+struct s921_config sharp_isdbt_config = {
+	.i2c_address = 0x30>>1
+};
+
+static int em2880_dvb_init(struct em28xx *dev)
+{
+	struct em2880_dvb *dvb_dev;
+	int err;
+	u16 if1 = 1220;
+	int gpio_arg;
+	int i;
+	struct dvb_demux *dvbdemux;
+
+	printk(KERN_INFO"em2880-dvb.c: DVB Init\n");
+	if (NULL == dev) {
+		dprintk(1, "em2880-dvb.c: no device attached?\n");
+		return -ENOMEM;
+	}
+
+	dvb_dev = kzalloc(sizeof(struct em2880_dvb), GFP_KERNEL);
+	if (!dvb_dev) {
+		dprintk(1, "out of memory!\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&dvb_dev->sem);
+
+	dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
+	dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1);
+
+
+	if (dev->dvbnorm == NULL) {
+		kfree(dvb_dev);
+		return -EINVAL;
+	}
+
+	gpio_arg = EM28XX_REG_OFF;
+	dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	gpio_arg = EM28XX_REG_ON;
+	dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+	mdelay(100);
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+
+
+	switch (dev->model) {
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO:
+#ifdef MICRONAS_DRX3975D
+		dvb_dev->frontend = dvb_attach(drx3973d_attach,
+				&DRX3973DDefaultCommAttr_g,
+				&DRX3973DDefaultAddr_g,
+				&DRX3973DData_g,
+				&dev->i2c_adap);
+#endif
+		break;
+	case EM2883_BOARD_TERRATEC_HYBRID_XS_FM:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H:	
+	case EM2870_BOARD_TERRATEC_XS:
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+	case EM2880_BOARD_MSI_DIGIVOX_AD:
+	case EM2880_BOARD_MSI_DIGIVOX_AD_II:
+	case EM2870_BOARD_KWORLD_350U:
+	case EM2883_BOARD_KWORLD_HYBRID_E323:
+	case EM2888_BOARD_KWORLD_HYBRID_E329:
+	case EM2888_BOARD_EMPIA_HYBRID:
+	case EM2880_BOARD_TERRATEC_PRODIGY_XS:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_zl10353_dev, &dev->i2c_adap);
+		if (dvb_dev->frontend == NULL)
+		dvb_dev->frontend = dvb_attach(mt352_attach,
+				&em2880_mt352_dev, &dev->i2c_adap);
+ 		
+		break;
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_zl10353_pinnacle, &dev->i2c_adap);
+		break;
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2883_BOARD_PINNACLE_PCTV_HD_PRO:
+	case EM2883_BOARD_KWORLD_HYBRID_A316:
+	case EM2883_BOARD_ATI_TVWONDER600:
+		dvb_dev->frontend = dvb_attach(lgdt330x_attach,
+				&em2880_lgdt3303_dev, &dev->i2c_adap);
+		break;
+	case EM2870_BOARD_KWORLD_355U:
+	case EM2870_BOARD_COMPRO_VIDEOMATE:
+		em28xx_ts_bus_ctrl_int(dev, 0);
+		/* fall through */
+	case EM2870_BOARD_PINNACLE_PCTV_DVB:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_kworld_355u_dev,
+				&dev->i2c_adap);
+		break;
+	case EM2883_BOARD_EMPIA_HYBRID_ATSC:
+	case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC:
+		dvb_dev->frontend = dvb_attach(lgdt3304_attach,
+				&lgdt3304_atsc_config, &dev->i2c_adap);
+		break;
+	case EM2875_BOARD_SAMPLE_ISDBT:
+		
+#if 1
+		dvb_dev->frontend = dvb_attach(s921_attach,
+				&sharp_isdbt_config, &dev->i2c_adap);
+#endif
+		if (dvb_dev->frontend) {
+			dvb_dev->frontend->tuner_priv = dev;
+			dvb_dev->init_override = dvb_dev->frontend->ops.init;
+			dvb_dev->frontend->ops.init = em28xx_s921_init;
+		} else {
+			printk("failed to initialize s921\n");
+		}
+		break;
+	case EM2879_BOARD_SAMPLE_DMB:
+#if 0
+		dvb_dev->frontend = dvb_attach(dmb_attach,
+				&sharp_dmb_config, &dev->i2c_adap);
+#endif
+		break;
+	default:
+		printk(KERN_INFO"em2880-dvb.c: unsupported device\n");
+		kfree(dvb_dev);
+		return -EINVAL;
+	}
+
+	if (dvb_dev->frontend && dev->model != EM2875_BOARD_SAMPLE_ISDBT) {
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		case TUNER_XCEIVE_XC5000:
+			dvb_dev->frontend->tuner_priv = dev;
+			dvb_dev->frontend->ops.tuner_ops.set_params =
+				em28xx_set_params;
+			dvb_dev->frontend->ops.tuner_ops.get_frequency =
+				em28xx_get_frequency;
+			dvb_dev->frontend->ops.tuner_ops.get_bandwidth =
+				em28xx_get_bandwidth;
+
+			/* xc3028 powersaving mode */
+			dvb_dev->frontend->ops.tuner_ops.init =
+				em28xx_dvb_init;
+			dvb_dev->frontend->ops.tuner_ops.sleep =
+				em28xx_dvb_sleep;
+			break;
+		case TUNER_MT2060:
+			dvb_attach(mt2060_attach, dvb_dev->frontend,
+					&dev->i2c_adap, &em2870_mt2060_config,
+					if1);
+			break;
+		case TUNER_QT1010:
+			dvb_dev->frontend->ops.i2c_gate_ctrl =
+				kworld355u_i2c_gate_ctrl;
+			dvb_attach(qt1010_attach, dvb_dev->frontend,
+					&dev->i2c_adap, &em2870_qt1010_config);
+			break;
+		case TUNER_ADIMTV102:
+#ifdef ADIMTV102
+			dvb_attach(adimtv102_attach, dvb_dev->frontend, &dev->i2c_adap, &dmbt_adim_config);
+#endif
+			break;
+		default:
+			printk(KERN_INFO"unsupported tuner (%d)\n",
+					dev->tuner_type);
+		}
+
+		/* zl10353 powersaving */
+		dvb_dev->init_override = dvb_dev->frontend->ops.init;
+
+		dvb_dev->frontend->ops.sleep = em28xx_zl10353_sleep;
+		dvb_dev->frontend->ops.init = em28xx_zl10353_init;
+	}
+
+	if (NULL == dvb_dev->frontend) {
+		printk(KERN_INFO"em2880-dvb.c: failed initializing zl10353"
+		       "DVB-T demodulator\n");
+		printk(KERN_INFO"em2880-dvb.c: retrying with mt352 DVB-T"
+				"demodulator\n");
+		if (NULL == dvb_dev->frontend) {
+			printk(KERN_INFO"em2880-dvb.c: no luck with mt352"
+			       "demodulator, not attaching em2880-dvb\n");
+			kfree(dvb_dev);
+			return -ENODEV;
+		}
+	}
+
+	/* this is not backward compatible */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+	dvb_dev->frontend->ops.ts_bus_ctrl = em28xx_ts_bus_ctrl;
+#endif
+
+	dvb_register_adapter(&dvb_dev->adapter, "em2880 DVB-T", THIS_MODULE,
+			&dev->udev->dev
+#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR
+			,adapter_nr
+#endif
+	);
+
+	
+	for (i=0; i<dev->uif->altsetting[1].desc.bNumEndpoints; i++) {
+		if (dev->uif->altsetting[1].endpoint[i].desc.bEndpointAddress == 0x84) {
+			dvb_dev->dtv_packetsize = dev->uif->altsetting[1].
+					endpoint[i].desc.wMaxPacketSize;
+			break;
+		}
+	}
+
+	dvb_dev->adapter.priv = dev;
+
+	dvb_register_frontend(&dvb_dev->adapter, dvb_dev->frontend);
+
+	dvb_dev->demux.priv = dvb_dev;
+	dvb_dev->demux.filternum = 256;
+	dvb_dev->demux.feednum = 256;
+	dvb_dev->demux.start_feed = em2880_start_feed;
+	dvb_dev->demux.stop_feed = em2880_stop_feed;
+	dvb_dev->demux.dmx.capabilities = DMX_TS_FILTERING |
+					DMX_SECTION_FILTERING |
+					DMX_MEMORY_BASED_FILTERING;
+
+	err = dvb_dmx_init(&dvb_dev->demux);
+	if (err < 0) {
+		dprintk(1, "dvb_dmx_init failed!\n");
+		kfree(dvb_dev);
+		return -1;
+	}
+
+	dvb_dev->dmxdev.filternum = dvb_dev->demux.filternum;
+	dvb_dev->dmxdev.demux = &dvb_dev->demux.dmx;
+	dvb_dev->dmxdev.capabilities = 0;
+
+	err = dvb_dmxdev_init(&dvb_dev->dmxdev, &dvb_dev->adapter);
+
+	if (err < 0) {
+		dprintk(1, "dvb_dmxdev failed!\n");
+		dvb_dmxdev_release(&dvb_dev->dmxdev);
+		kfree(dvb_dev);
+		return -1;
+	}
+
+	dvb_dev->udev = dev->udev;
+	dvb_dev->em28xx_dev = dev; /* FIXME get rid of this */
+
+	dev->dvb_dev = dvb_dev;
+
+	dvbdemux = &dvb_dev->demux;
+	dvb_net_init(&dvb_dev->adapter, &dvb_dev->dvbnet, &dvbdemux->dmx);
+
+	return 0;
+}
+
+static int em2880_dvb_fini(struct em28xx *dev)
+{
+	struct em2880_dvb *dvb_dev = dev->dvb_dev;
+	if (!dev) {
+		dprintk(1, "fini already called!\n");
+		return -1;
+	}
+	if (!dev->dvb_dev) {
+		dprintk(1, "dvb_dev not initialized!\n");
+		return -1;
+	}
+	dvb_dev = dev->dvb_dev;
+
+	dprintk(1, "releasing dvb device!\n");
+
+	dvb_net_release(&dvb_dev->dvbnet);
+	dvb_unregister_frontend(dvb_dev->frontend);
+	dvb_frontend_detach(dvb_dev->frontend);
+
+	dvb_dev->demux.dmx.close(&dvb_dev->demux.dmx);
+	dvb_dmxdev_release(&dvb_dev->dmxdev);
+	dvb_dmx_release(&dvb_dev->demux);
+
+	dvb_unregister_adapter(&dvb_dev->adapter);
+
+	dprintk(1, "dvb_fini\n");
+	if (dev->dvb_dev) {
+		dprintk(1, "freeing dvb_dev\n");
+		kfree(dev->dvb_dev);
+		dev->dvb_dev = NULL;
+	}
+	return 0;
+}
+
+static struct em28xx_ops dvb_ops = {
+	.id	= EM28XX_DVBT | EM28XX_ATSC | EM28XX_ISDB | EM28XX_DMB,
+	.name	= "Em2880 DVB Extension",
+	.init	= em2880_dvb_init,
+	.fini	= em2880_dvb_fini,
+};
+
+static int __init em2880_dvb_register(void)
+{
+	return em28xx_register_extension(&dvb_ops);
+}
+
+static void __exit em2880_dvb_unregister(void)
+{
+	em28xx_unregister_extension(&dvb_ops);
+}
+
+module_init(em2880_dvb_register);
+module_exit(em2880_dvb_unregister);
+
+/* ------------------------------------------------------------------ */
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/empia/em28xx-aad.c b/drivers/media/video/empia/em28xx-aad.c
new file mode 100644
index 0000000..7c6dcb6
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-aad.c
@@ -0,0 +1,404 @@
+/* 
+ * Em28xx-aad Empia Alternative Audio Driver
+ *
+ * This is a vendor specific driver which provides a vendor specific
+ * audio interface for Empia based devices.
+ *
+ * Copyright (C) 2008 Empia Technology Inc.
+ * Copyright (C) 2008 Sundtek Ltd.
+ *
+ */
+
+#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/usb.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <asm/uaccess.h>
+
+#include "em28xx-aad.h"
+#include "em28xx.h"
+
+static unsigned long em28xx_aad_devices; /* map */
+static int em28xx_aad_register(struct em28xx *dev);
+
+void em28xx_aad_unregister(struct em28xx_aad_info **int_aad);
+
+static int em28xx_maxdevs;
+
+static LIST_HEAD(em28xx_aad_devlist);
+
+/* ringbuffer implementation, taken from empia radio application */
+
+#define AAD_BUFFER_SIZE 20000
+
+static struct aad_rb_info *aad_rb_init(void);
+static struct aad_rb_info *aad_rb_init(void) {
+	struct aad_rb_info *rb = kzalloc(sizeof(struct aad_rb_info), GFP_KERNEL);
+        rb->left = rb->right = rb->pos = 0;
+	rb->bufsize = AAD_BUFFER_SIZE;
+        rb->buffer = kzalloc(AAD_BUFFER_SIZE*sizeof(u8), GFP_KERNEL);
+        if (rb->buffer == NULL)
+            return 0;
+	spin_lock_init(&rb->__aad_lock);
+	return rb;
+};
+
+void aad_rb_free(struct aad_rb_info **rb) {
+	kfree((*rb)->buffer);
+	kfree((*rb));
+}
+
+static inline u32 aad_rb_get_buffer_size(struct aad_rb_info *rb) {
+	if (rb->left > rb->right)
+		return (rb->bufsize - rb->left + rb->right);
+	else if(rb->left < rb->right)
+		return (rb->right - rb->left);
+	else if(rb->left == rb->right)
+		return 0;
+	return 0;
+}
+
+static void aad_rb_reset_buffer(struct aad_rb_info *rb) {
+        unsigned long flags;
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+        rb->left = rb->right = rb->pos = 0;
+        spin_unlock_irqrestore(&rb->__aad_lock, flags);
+}
+
+static inline u32 aad_rb_free_buffer_size(struct aad_rb_info *rb) {
+	u32 cur_buffsize = aad_rb_get_buffer_size(rb);
+	if (rb->bufsize - cur_buffsize > 0)
+		return rb->bufsize - cur_buffsize - 1;
+	return rb->bufsize - 1;
+}
+
+static int aad_rb_write_data(struct aad_rb_info *rb, u8 *data, ssize_t size) {
+	int len = size;
+	//int len2;
+	int free;
+	unsigned long flags;
+	/* lock */
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+	free = aad_rb_free_buffer_size(rb);
+
+	if (size > free)
+		size = free - free % sizeof(u32); /* alignment */
+
+	if (free == 0) {
+                if(printk_ratelimit()) 
+                    printk("dropping data!\n");
+		/* unlock */
+                spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		return 0;
+	}
+	if (rb->right + size >= rb->bufsize - 1) {
+		len = rb->bufsize - rb->right;
+		memcpy(&rb->buffer[rb->right], data, len);
+		memcpy(rb->buffer, data+len, size - len);
+		rb->right = size - len;
+	} else {
+		memcpy(&rb->buffer[rb->right], data, size);
+		rb->right += size;
+	}
+	/* unlock */
+        spin_unlock_irqrestore(&rb->__aad_lock, flags);
+	return 0;
+}
+
+/* this is a bit complicated since we cannot do memcpy/copy_to_user within the
+ * spinlocked block, it might work fine with most systems although the target
+ * systems just blew up */
+
+static int aad_rb_read_data(struct em28xx_aad_info *aad, u8 *buffer, ssize_t size) {
+	int len;
+	int bufsize;
+	unsigned int left;
+        unsigned long flags;
+        struct aad_rb_info *rb = aad->__aad_rb[aad->__curr_rb];
+	/* lock */
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+
+
+	if (aad_rb_get_buffer_size(rb) == 0) {
+		/* unlock */
+                spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		return 0;
+	}
+
+	bufsize = aad_rb_get_buffer_size(rb);
+	if (size > bufsize)
+		size = bufsize;
+
+	size -= size % 8; /* alignment */
+
+        aad->__curr_rb ^= 1; /* switch rb buffer so the irq can write to the other buffer during
+                                the copy routine */
+
+        aad->__write_rb = aad->__aad_rb[aad->__curr_rb];
+
+	if (rb->left + size >= rb->bufsize -1) {
+		len = rb->bufsize - rb->left;
+		left = rb->left;
+		rb->left = size - len;
+        	spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		copy_to_user(buffer, &rb->buffer[left], len);
+		copy_to_user(&buffer[len], rb->buffer, size - len);
+	} else {
+		left = rb->left;
+		rb->left += size;
+        	spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		copy_to_user(buffer, &rb->buffer[left], size);
+	}
+
+        /* reset our backed up ringbuffer */
+	
+        aad_rb_reset_buffer(rb);
+	/* unlock */
+	return size;
+}
+
+/* end ringbuffer implementation */
+
+/* usb code */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_aad_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_aad_isocirq(struct urb *urb)
+#endif
+{
+    struct em28xx_aad_info *aad = urb->context;
+    u8 *cp;
+    int i;
+    for(i=0;i<urb->number_of_packets;i++){
+        int length=urb->iso_frame_desc[i].actual_length;
+        cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+        if(!length)
+            continue;
+
+
+        if (urb->iso_frame_desc[i].status)
+            continue;
+
+        aad_rb_write_data(aad->__write_rb, cp, length);
+
+    }
+    urb->status = 0;
+
+    if(usb_submit_urb(urb, GFP_ATOMIC))
+        printk("unable to submit urb\n");
+}
+
+static int aad_audio_isoc_deinit(struct em28xx_aad_info *aad)
+{
+    int i;
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        usb_kill_urb(aad->urb[i]);
+        usb_free_urb(aad->urb[i]);
+        aad->urb[i]=NULL;
+    }
+    return 0;
+}
+
+static int aad_audio_isoc_init(struct em28xx_aad_info *aad)
+{
+    int i;
+    int errCode;
+    const int sb_size=AAD_NUM_AUDIO_PACKETS * AAD_AUDIO_MAX_PACKET_SIZE;
+
+    printk("aad_isoc audio init\n");
+
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        struct urb *urb;
+        int j,k;
+        aad->transfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC);
+
+        if(!aad->transfer_buffer[i])
+            return -ENOMEM;
+
+        memset(aad->transfer_buffer[i],0x80,sb_size);
+        urb = usb_alloc_urb(AAD_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+        if(urb){
+            urb->dev=aad->__aad_udev;
+            urb->context=aad;
+            urb->pipe=usb_rcvisocpipe(aad->__aad_udev, 0x83);
+            urb->transfer_flags = URB_ISO_ASAP;
+            urb->transfer_buffer = aad->transfer_buffer[i];
+            urb->interval=1;
+            urb->complete = em28xx_aad_isocirq;
+            urb->number_of_packets = AAD_NUM_AUDIO_PACKETS;
+            urb->transfer_buffer_length = sb_size;
+            for(j=k=0; j<AAD_NUM_AUDIO_PACKETS;j++,k+=AAD_AUDIO_MAX_PACKET_SIZE){
+                urb->iso_frame_desc[j].offset = k;
+                urb->iso_frame_desc[j].length=AAD_AUDIO_MAX_PACKET_SIZE;
+            }
+            aad->urb[i]=urb;
+        } else
+            return -ENOMEM;
+
+    }
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        errCode = usb_submit_urb(aad->urb[i], GFP_ATOMIC);
+        if (errCode){
+            aad_audio_isoc_deinit(aad);
+            return errCode;
+        }
+    }
+    return 0;
+}
+
+/* cheap usercheck */
+static int aad_users;
+
+static int aad_open(struct inode *inode, struct file *file) {
+	struct em28xx_aad_info *aad = NULL;
+	struct list_head *list;
+
+	if (aad_users != 0) 
+		return -EBUSY;
+	aad_users++;
+        /* TODO: support multiple devices here */
+	list_for_each(list, &em28xx_aad_devlist) {
+		aad = list_entry(list, struct em28xx_aad_info, __aad_devlist);
+		break;
+	}
+
+	if (aad == NULL)
+		return -EINVAL;
+
+	printk("found device entry!\n");
+
+	file->private_data = aad;
+
+        aad_rb_reset_buffer(aad->__write_rb);
+        aad->__aad_callback(aad->__cb_priv, EM28XX_ENABLE_AUDIO, NULL);
+        aad_audio_isoc_init(aad);
+	return 0;
+}
+
+static ssize_t aad_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
+	struct em28xx_aad_info *aad = NULL;
+	ssize_t retval;
+	aad = filp->private_data;
+	retval = aad_rb_read_data(aad, buf, count);
+	return retval;
+}
+
+static int aad_release(struct inode *inode, struct file *file) {
+        struct em28xx_aad_info *aad;
+        aad = file->private_data;
+	aad_users--;
+        aad_audio_isoc_deinit(aad);
+	return 0;
+}
+
+static struct file_operations em28xx_aad_fops = {
+        .owner   = THIS_MODULE,
+        .open    = aad_open,
+        .read    = aad_read,
+        .release = aad_release,
+};
+
+static int em28xx_aad_register(struct em28xx *dev) {
+	struct em28xx_aad_info *aad;
+	int id;
+	aad = kzalloc(sizeof(struct em28xx_aad_info), GFP_KERNEL);
+	id = find_first_zero_bit(&em28xx_aad_devices, sizeof(u32));
+	em28xx_aad_devices |= (1<<id);
+	aad->__id = id;
+        aad->__aad_udev = dev->udev;
+        aad->__aad_callback = dev->em28xx_aad_control;
+        aad->__cb_priv = dev;
+        dev->aad = aad;
+	snprintf(aad->__aad_devname, 50, "aad%d", id);
+	printk("em28xx-aad: registered /dev/%s\n",aad->__aad_devname);
+
+	aad->__aad_major = register_chrdev(0, aad->__aad_devname, &em28xx_aad_fops);
+	aad->__aad_class = class_create(THIS_MODULE, aad->__aad_devname);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+	aad->__aad_cdev  = class_device_create(aad->__aad_class, NULL, MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname);
+#else
+/* XXX now in 2.6.27 device_create == device_create_drvdata ... */
+/* dvb-core/dvbdev.c uses d_c_drvdata already, imitate that since I don't
+ * know any better...  XXX  */
+        aad->__aad_cdev  = device_create_drvdata(aad->__aad_class, NULL,
+                MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname);
+#endif
+
+	INIT_LIST_HEAD(&aad->__aad_devlist);
+	list_add_tail(&aad->__aad_devlist, &em28xx_aad_devlist);
+
+        /* we cannot copy out from the ringpuffer while it is spinlocked with 2.6.17 */
+	aad->__aad_rb[0] = aad_rb_init();
+	aad->__aad_rb[1] = aad_rb_init();
+        aad->__write_rb = aad->__aad_rb[0];
+        aad->__curr_rb = 0;
+	return 0;
+}
+
+void em28xx_aad_unregister(struct em28xx_aad_info **aad_int) {
+	struct em28xx_aad_info *aad = (*aad_int);
+	em28xx_aad_devices &= ~ (1<<aad->__id);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+	class_device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0));
+#else
+        device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0));
+#endif
+	unregister_chrdev(0, aad->__aad_devname);
+	class_destroy(aad->__aad_class);
+	aad_rb_free(&aad->__aad_rb[0]);
+	aad_rb_free(&aad->__aad_rb[1]);
+        list_del(&aad->__aad_devlist);
+
+	kfree(aad);
+}
+
+static int em28xx_aad_fini(struct em28xx *dev) {
+    em28xx_aad_unregister(&dev->aad);
+    dev->aad = NULL;
+    return 0;
+}
+
+static struct em28xx_ops audio_ops = {
+	.id	= EM28XX_AUDIO,
+	.name	= "Em28xx Alternative Audio Driver",
+	.init	= em28xx_aad_register,
+	.fini	= em28xx_aad_fini,
+};
+
+static int __init  em28xx_aad_init(void) {
+	printk("initializing Empia Audio Driver\n");
+	printk("Copyright (C) 2008 Empia Technology Inc\n");
+	printk("Copyright (C) 2008 Sundtek Ltd.\n");
+
+	em28xx_maxdevs = sizeof(int);
+	return em28xx_register_extension(&audio_ops);
+}
+
+static void __exit em28xx_aad_exit(void) {
+	printk("releasing Empia Audio Driver\n");
+	em28xx_unregister_extension(&audio_ops);
+}
+
+MODULE_AUTHOR("Empia Technology Inc./Sundtek Ltd.");
+MODULE_DESCRIPTION("Alternative Audio Driver for Empia based devices");
+MODULE_LICENSE("GPL");
+
+module_init(em28xx_aad_init);
+module_exit(em28xx_aad_exit);
diff --git a/drivers/media/video/empia/em28xx-aad.h b/drivers/media/video/empia/em28xx-aad.h
new file mode 100644
index 0000000..fd39ed1
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-aad.h
@@ -0,0 +1,46 @@
+#ifndef __EM28XX_AAD_H
+#define __EM28XX_AAD_H
+
+#include <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/compiler.h> /* need __user */
+
+#define MAX_AAD_DEVS 10
+
+struct aad_rb_info {
+    spinlock_t __aad_lock;
+    u32 left;
+    u32 right;
+    u32 pos;
+    u32 bufsize;
+    u8 *buffer;
+};
+
+#define AAD_AUDIO_BUFS 3
+#define AAD_NUM_AUDIO_PACKETS 64
+#define AAD_AUDIO_MAX_PACKET_SIZE 196 /* static value */
+
+struct em28xx_aad_info {
+    u32 __id;
+    u8 __aad_devname[50];
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+    struct class_device *__aad_cdev;
+#else
+    struct device *__aad_cdev;
+#endif
+    int __aad_major;
+    struct class *__aad_class;
+    struct list_head __aad_devlist;
+    struct aad_rb_info *__aad_rb[2];
+    struct aad_rb_info *__write_rb;
+    u8 __curr_rb;
+    struct usb_device *__aad_udev;
+    u8 *transfer_buffer[AAD_AUDIO_BUFS];
+    struct urb *urb[AAD_AUDIO_BUFS];
+    int (*__aad_callback)(void *, unsigned int cmd, void *arg);
+    void *__cb_priv;
+};
+
+#endif
diff --git a/drivers/media/video/empia/em28xx-audio.c b/drivers/media/video/empia/em28xx-audio.c
new file mode 100644
index 0000000..a1d06a7
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-audio.c
@@ -0,0 +1,630 @@
+/*
+ *  Empiatech em28x1 audio extension
+ *
+ *  Copyright (C) 2006 Markus Rechberger <mrechberger@sundtek.de>
+ *
+ *  This driver is based on my previous au600 usb pstn audio driver
+ *  and inherits all the copyrights
+ *
+ *  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/kernel.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/sound.h>
+#include <linux/spinlock.h>
+#include <linux/soundcard.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/moduleparam.h>
+#include <linux/wait.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <media/v4l2-common.h>
+#include "em28xx.h"
+#include "xc5000/xc5000_control.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static int em28xx_cmd(struct em28xx *dev, int cmd,int arg);
+
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size)
+#else
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size)
+#endif
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_runtime_t *runtime = subs->runtime;
+#else
+	struct snd_pcm_runtime *runtime = subs->runtime;
+#endif
+	if(runtime->dma_area){
+		if(runtime->dma_bytes > size)
+			return 0;
+		vfree(runtime->dma_area);
+	}
+	runtime->dma_area = vmalloc(size);
+	if(!runtime ->dma_area)
+		return -ENOMEM;
+	runtime->dma_bytes = size;
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_hardware_t snd_em28xx_hw_capture = {
+#else
+static struct snd_pcm_hardware snd_em28xx_hw_capture = {
+#endif
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
+		SNDRV_PCM_INFO_MMAP | 
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED  | 
+		SNDRV_PCM_INFO_MMAP_VALID,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 1024*1024, 
+	.period_bytes_min = 64, 
+	.period_bytes_max = 512*1024,
+	.periods_min = 2,
+	.periods_max = 1024,
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_open(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
+#endif
+{
+	int ret = 0;
+	int mode;
+	int arg;
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_runtime_t *runtime = substream->runtime;
+#else
+	struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+	printk("opening radio device and trying to acquire exclusive lock\n");
+        switch(dev->mode){
+	case V4L2_TUNER_DIGITAL_TV:
+		/* digital has no support for analog audio */
+		ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); 
+		if (ret != 0 ) {
+			printk("device is already in use by DVB-T\n");
+			return -EINVAL;
+		} else {
+			struct v4l2_tuner tuner;
+			struct v4l2_routing arouting;
+			printk("switching device to FM mode\n");
+
+			mode = V4L2_TUNER_RADIO;
+			memset(&tuner, 0x0, sizeof(struct v4l2_tuner));
+			tuner.type = V4L2_TUNER_RADIO;
+
+			dev->mode = mode;
+		
+			arg = EM28XX_REG_ON;
+			dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg);
+			arg = EM28XX_REG_OFF;
+			dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg);
+			mdelay(100);
+
+			/* upload firmware */
+			switch(dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+				{
+					struct xc3028_init_cmd cmd;
+
+					/* this is moreover to switch the decoder to FM */
+					cmd.new_tv_mode_ptr = dev->fmnorm->tv_mode;
+					cmd.new_channel_map_ptr = dev->fmnorm->channelmap;
+					if (dev->tuner)
+						dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+					break;
+				} 
+			case TUNER_XCEIVE_XC5000:
+				{
+					struct xc_std_conf cmd;
+					/* this is moreover to switch the decoder to FM */
+					if (dev->tuner) {
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL);
+						cmd.index=dev->fmnorm->index;
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+					}
+					break;
+				}
+			default:
+				break;
+			}
+
+			dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0);
+			
+			arouting.input = CX25843_RADIO;
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting);
+
+			printk("retrieved mode from tuner: %d\n",mode);
+		}
+		break;
+
+	case V4L2_TUNER_ANALOG_TV:
+		printk("em28xx-audio: device is currently in analog TV mode\n");
+		/* unmute by default */
+		ret = dev->em28xx_acquire(dev, EM28XX_VIDEO, 1); 
+		if (ret!=0)
+			return -EBUSY;
+
+		dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+
+		break;
+	case V4L2_TUNER_RADIO:
+	{
+		struct v4l2_routing arouting;
+		/* check current mode and put a hard lock onto it */
+		printk("em28xx-audio: device is currently in analogue FM mode\n");
+		/* unmute by default here */
+		ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); 
+
+		dev->mode = V4L2_TUNER_RADIO;
+
+		if ( ret == 0 ) 
+			printk("device is locked in fmradio mode now\n");
+
+		dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+		arouting.input = CX25843_RADIO;
+		em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting);
+
+		break;
+	}
+	default:
+		printk("em28xx-audio: unhandled mode %d\n", dev->mode);
+		return -EINVAL;
+	}
+	runtime->hw = snd_em28xx_hw_capture;
+	if(dev->alt == 0 && dev->adev->users == 0 ) {
+		int errCode;
+		dev->alt = 7;
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, 7);
+		printk("changing alternate number to 7\n");
+	}
+	dev->adev->users++;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	dev->adev->capture_pcm_substream = substream;
+	runtime->private_data = dev;
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	int amode = 0;
+	dev->adev->users--;
+
+	/* decrease audio reference */
+	switch(dev->mode) {
+		case V4L2_TUNER_ANALOG_TV:
+			amode = EM28XX_VIDEO;
+			break;
+		case V4L2_TUNER_RADIO:
+			amode = EM28XX_RADIO;
+			break;
+		default:
+			printk("invalid mode: %d\n",dev->mode);
+			return -EINVAL;
+	}
+
+	dev->em28xx_acquire(dev, amode, 0); 
+
+	if(dev->adev->users == 0 && dev->adev->shutdown == 1) {
+		dev->adev->shutdown = 0;
+		em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0);
+	}
+	wake_up(&dev->adev->open);
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
+#else
+static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
+#endif
+{
+	unsigned int channels, rate, format;
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	int ret;
+	ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params));
+	dev->adev->hwptr_done_capture = 0;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+	/* TODO: set up em28xx audio chip to deliver the correct audio format, 
+	   xc5000/cx25843 supports stereo
+	   xc3028/em202 supports mono only
+	   */
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+
+	if(dev->adev->capture_stream==STREAM_ON)
+		em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0);
+
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_prepare(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
+#endif
+{
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd)
+#else
+static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	switch(cmd){
+		case SNDRV_PCM_TRIGGER_START:
+			em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,1);
+			return 0;
+		case SNDRV_PCM_TRIGGER_STOP:
+			dev->adev->shutdown=1;
+			return 0;
+		default:
+			return -EINVAL;
+	}
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_audio_isocirq(struct urb *urb)
+#endif
+{
+	struct em28xx *dev = urb->context;
+	struct em28xx_audio *adev = dev->adev;
+	int i;
+	unsigned int oldptr;
+	unsigned long flags;
+	int period_elapsed = 0;
+	int status;
+	unsigned char *cp;
+	unsigned int stride;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+#else
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+#endif
+        if (adev->capture_stream == STREAM_INTERRUPT) {
+                adev->capture_stream = STREAM_OFF;
+                wake_up_interruptible(&adev->audio_wait_stream);
+        }
+
+	if ((dev->state & DEV_DISCONNECTED) || 
+			(dev->state & DEV_MISCONFIGURED))                
+		return;
+
+
+	if(dev->adev->capture_pcm_substream){
+		substream=dev->adev->capture_pcm_substream;
+		runtime=substream->runtime;
+
+		stride = runtime->frame_bits >> 3;
+		for(i=0;i<urb->number_of_packets;i++){
+
+			int length=urb->iso_frame_desc[i].actual_length/stride;
+			cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+			if(!length)
+				continue;
+
+
+			if (urb->iso_frame_desc[i].status) 
+				continue;
+
+			spin_lock_irqsave(&dev->adev->slock, flags);
+			oldptr = dev->adev->hwptr_done_capture;
+			dev->adev->hwptr_done_capture +=length;
+			if(dev->adev->hwptr_done_capture >= runtime->buffer_size)
+				dev->adev->hwptr_done_capture -= runtime->buffer_size;
+
+			dev->adev->capture_transfer_done += length;
+			if(dev->adev->capture_transfer_done >= runtime->period_size){
+				dev->adev->capture_transfer_done -= runtime->period_size;
+				period_elapsed=1;
+			}
+			spin_unlock_irqrestore(&dev->adev->slock, flags);
+
+			if(oldptr + length > runtime->buffer_size){
+				unsigned int cnt = runtime->buffer_size-oldptr;
+				memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride);
+				memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride);
+			} else {
+				memcpy(runtime->dma_area+oldptr*stride, cp, length*stride);
+			}
+		}
+		if(period_elapsed){
+			snd_pcm_period_elapsed(substream);
+		}
+	}
+	urb->status = 0;
+	
+	if(dev->adev->shutdown)
+		return;
+
+	if((status = usb_submit_urb(urb, GFP_ATOMIC)))
+		em28xx_errdev("resubmit of audio urb failed (error=%i)\n", status);
+	return;
+}
+
+static int em28xx_isoc_audio_deinit(struct em28xx *dev)
+{
+	int i;
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		usb_kill_urb(dev->adev->urb[i]);
+		usb_free_urb(dev->adev->urb[i]);
+		dev->adev->urb[i]=NULL;
+	}
+	return 0;
+}
+
+static int em28xx_init_audio_isoc(struct em28xx *dev)
+{
+	int i;
+	int errCode;
+	const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE;
+
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		struct urb *urb;
+		int j,k;
+		dev->adev->transfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC);
+
+		if(!dev->adev->transfer_buffer[i])
+			return -ENOMEM;
+
+		memset(dev->adev->transfer_buffer[i],0x80,sb_size);
+		urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+		if(urb){
+			urb->dev=dev->udev;
+			urb->context=dev;
+			urb->pipe=usb_rcvisocpipe(dev->udev,0x83);
+			urb->transfer_flags = URB_ISO_ASAP;
+			urb->transfer_buffer = dev->adev->transfer_buffer[i];
+			urb->interval=1;
+			urb->complete = em28xx_audio_isocirq;
+			urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS;
+			urb->transfer_buffer_length = sb_size;
+			for(j=k=0; j<EM28XX_NUM_AUDIO_PACKETS;j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+				urb->iso_frame_desc[j].offset = k;
+				urb->iso_frame_desc[j].length=EM28XX_AUDIO_MAX_PACKET_SIZE;
+			}
+			dev->adev->urb[i]=urb;
+		} else 
+			return -ENOMEM;
+
+	}
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		errCode = usb_submit_urb(dev->adev->urb[i], GFP_ATOMIC);
+		if (errCode){
+			em28xx_isoc_audio_deinit(dev);
+			return errCode;
+		}
+	}
+	return 0;
+}
+
+
+static int em28xx_cmd(struct em28xx *dev, int cmd,int arg)
+{
+	switch(cmd){
+		case EM28XX_CAPTURE_STREAM_EN:
+			if(dev->adev->capture_stream == STREAM_OFF && arg==1){
+				dev->adev->capture_stream=STREAM_ON;
+				em28xx_init_audio_isoc(dev);
+			} else if (dev->adev->capture_stream==STREAM_ON && arg==0){
+				dev->adev->capture_stream=STREAM_OFF;
+				em28xx_isoc_audio_deinit(dev);
+			} 
+			return 0;
+		default:
+			return -EINVAL;
+	}
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream)
+#else
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev;
+	snd_pcm_uframes_t hwptr_done;
+	dev = snd_pcm_substream_chip(substream);
+	hwptr_done = dev->adev->hwptr_done_capture;
+	return hwptr_done;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs,
+					     unsigned long offset)
+#else
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+					     unsigned long offset)
+#endif
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+	return vmalloc_to_page(pageptr);
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_ops_t snd_em28xx_pcm_capture = {
+#else
+static struct snd_pcm_ops snd_em28xx_pcm_capture = {
+#endif
+	.open = snd_em28xx_capture_open,
+	.close = snd_em28xx_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_em28xx_hw_capture_params,
+	.hw_free = snd_em28xx_hw_capture_free,
+	.prepare = snd_em28xx_prepare,
+	.trigger = snd_em28xx_capture_trigger,
+	.pointer = snd_em28xx_capture_pointer,
+	.page = snd_pcm_get_vmalloc_page,
+};
+
+
+static int em28xx_audio_init(struct em28xx *dev)
+{
+	struct em28xx_audio *adev;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_t *pcm;
+	snd_card_t *card;
+#else
+	struct snd_pcm *pcm;
+	struct snd_card *card;
+#endif
+	static int devnr;
+	int ret;
+	int err;
+	printk("em28xx-audio.c: probing for em28x1 non standard usbaudio\n");
+	printk("em28xx-audio.c: Copyright (C) 2006 Markus Rechberger\n");
+	adev=kzalloc(sizeof(*adev),GFP_KERNEL);
+	if(!adev){
+		printk("em28xx-audio.c: out of memory\n");
+		return -1;
+	}
+	card = snd_card_new(index[devnr], "Em28xx Audio", THIS_MODULE,0);
+	if(card==NULL){
+		kfree(adev);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&adev->slock);
+	init_waitqueue_head(&adev->audio_wait_stream);
+	init_waitqueue_head(&adev->open);
+	ret=snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
+	pcm->info_flags = 0;
+	pcm->private_data = dev;
+	strcpy(pcm->name,"Empia 28xx Capture");
+	strcpy(card->driver, "Empia Em28xx Audio");
+	strcpy(card->shortname, "Em28xx Audio");
+	strcpy(card->longname,"Empia Em28xx Audio");
+
+	if((err = snd_card_register(card))<0){
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+	adev->sndcard=card;
+	adev->udev=dev->udev;
+	dev->adev=adev;
+	return 0;
+}
+
+static int em28xx_audio_fini(struct em28xx *dev)
+{
+	struct em28xx_audio *adev = dev->adev;
+	int ret;
+	printk("audio: deinitializing audio device\n");
+
+	if(dev==NULL) {
+		printk("audio: dev is already null\n");
+		return 0;
+	}
+
+	if(adev){
+		printk("audio: before waitstream\n");
+		if (adev->capture_stream == STREAM_ON) {
+			adev->capture_stream = STREAM_INTERRUPT;
+			ret = wait_event_timeout(adev->audio_wait_stream,
+					(adev->capture_stream == STREAM_OFF) ||
+					(dev->state & DEV_DISCONNECTED),
+					EM28XX_URB_TIMEOUT);
+		}
+		printk("audio: after waitstream\n");
+		printk("audio: waiting for audioclients to release audio node\n");
+
+		if (adev->users>0) 
+			/* we have to strictly wait for this... */
+
+			wait_event(adev->open, (adev->users == 0));
+
+		snd_card_free(dev->adev->sndcard);
+		kfree(dev->adev);
+		dev->adev=NULL;
+	}
+	return 0;
+}
+
+static struct em28xx_ops audio_ops = {
+	.id	= EM28XX_AUDIO,
+	.name	= "Em28xx Audio Extension",
+	.init	= em28xx_audio_init,
+	.fini	= em28xx_audio_fini,
+};
+
+static int __init em28xx_alsa_register(void)
+{
+	return em28xx_register_extension(&audio_ops);
+}
+
+static void __exit em28xx_alsa_unregister(void)
+{
+	em28xx_unregister_extension(&audio_ops);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_DESCRIPTION("Em28xx Audio driver");
+
+module_init(em28xx_alsa_register);
+module_exit(em28xx_alsa_unregister);
diff --git a/drivers/media/video/empia/em28xx-audioep.c b/drivers/media/video/empia/em28xx-audioep.c
new file mode 100644
index 0000000..868e488
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-audioep.c
@@ -0,0 +1,522 @@
+/* Copyright (C) 2008 Empia Technology Inc.
+ * Copyright (C) 2008 Markus Rechberger <mrechberger@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <linux/vmalloc.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+
+#include "em28xx.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_LICENSE("GPL"); // my appropriate code is dual licensed also BSD
+
+static struct usb_driver em28xx_audio_drv;
+static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg);
+
+static struct usb_device_id em28xx_audio_id_table[]={
+        { USB_DEVICE(0x0ccd, 0x0042) }, /* just for testing it will not attach to the real device */
+	{ USB_DEVICE(0xeb1a, 0xe300) },
+	{ USB_DEVICE(0xeb1a, 0xe301) },
+	{ USB_DEVICE(0xeb1a, 0xe305) },
+	{ USB_DEVICE(0xeb1a, 0xe310) },
+	{ USB_DEVICE(0xeb1a, 0xe320) },
+	{ USB_DEVICE(0xeb1a, 0x2861) },
+        { },
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_hardware_t snd_em28xx_hw_capture = {
+#else
+static struct snd_pcm_hardware snd_em28xx_hw_capture = {
+#endif
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
+		SNDRV_PCM_INFO_MMAP | 
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED  | 
+		SNDRV_PCM_INFO_MMAP_VALID,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 1024*1024, 
+	.period_bytes_min = 64, 
+	.period_bytes_max = 512*1024,
+	.periods_min = 2,
+	.periods_max = 1024,
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size)
+#else
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size)
+#endif
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_runtime_t *runtime = subs->runtime;
+#else
+        struct snd_pcm_runtime *runtime = subs->runtime;
+#endif
+        if(runtime->dma_area){
+                if(runtime->dma_bytes > size)
+                        return 0;
+                vfree(runtime->dma_area);
+        }
+        runtime->dma_area = vmalloc(size);
+        if(!runtime ->dma_area)
+                return -ENOMEM;
+        runtime->dma_bytes = size;
+        return 0;
+}
+
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
+#else
+static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
+#endif
+{
+        unsigned int channels, rate, format;
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        int ret;
+        ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params));
+        adev->hwptr_done_capture = 0;
+
+        format = params_format(hw_params);
+        rate = params_rate(hw_params);
+        channels = params_channels(hw_params);
+        /* TODO: set up em28xx audio chip to deliver the correct audio format, 
+           xc5000/cx25843 supports stereo
+           xc3028/em202 supports mono only
+           */
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+
+        if(adev->capture_stream == STREAM_ON)
+                em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0);
+
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_prepare(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
+#endif
+{
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd)
+#else
+static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        switch(cmd){
+                case SNDRV_PCM_TRIGGER_START:
+                        em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 1);
+                        return 0;
+                case SNDRV_PCM_TRIGGER_STOP:
+                        adev->shutdown = 1;
+                        return 0;
+                default:
+                        return -EINVAL;
+        }
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_audio_isocirq(struct urb *urb)
+#endif
+{
+        struct em28xx_audio *adev = urb->context;
+        int i;
+        unsigned int oldptr;
+        unsigned long flags;
+        int period_elapsed = 0;
+        unsigned char *cp;
+        unsigned int stride;
+
+	int status;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_substream_t *substream;
+        snd_pcm_runtime_t *runtime;
+#else
+        struct snd_pcm_substream *substream;
+        struct snd_pcm_runtime *runtime;
+#endif
+        if (adev->capture_stream == STREAM_INTERRUPT) {
+                adev->capture_stream = STREAM_OFF;
+                wake_up_interruptible(&adev->audio_wait_stream);
+        }
+        if ((adev->state & DEV_DISCONNECTED) ||
+                        (adev->state & DEV_MISCONFIGURED))
+                return;
+
+
+        if(adev->capture_pcm_substream){
+                substream=adev->capture_pcm_substream;
+                runtime=substream->runtime;
+
+                stride = runtime->frame_bits >> 3;
+                for(i=0;i<urb->number_of_packets;i++){
+
+                        int length=urb->iso_frame_desc[i].actual_length/stride;
+                        cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+
+                        if(!length)
+                                continue;
+
+
+                        if (urb->iso_frame_desc[i].status)
+                                continue;
+
+                        spin_lock_irqsave(&adev->slock, flags);
+                        oldptr = adev->hwptr_done_capture;
+                        adev->hwptr_done_capture +=length;
+                        if(adev->hwptr_done_capture >= runtime->buffer_size)
+                                adev->hwptr_done_capture -= runtime->buffer_size;
+
+                        adev->capture_transfer_done += length;
+                        if(adev->capture_transfer_done >= runtime->period_size){
+                                adev->capture_transfer_done -= runtime->period_size;
+                                period_elapsed=1;
+                        }
+                        spin_unlock_irqrestore(&adev->slock, flags);
+
+                        if(oldptr + length > runtime->buffer_size){
+                                unsigned int cnt = runtime->buffer_size-oldptr;
+                                memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride);
+                                memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride);
+                        } else {
+                                memcpy(runtime->dma_area+oldptr*stride, cp, length*stride);
+                        }
+                }
+                if(period_elapsed){
+                        snd_pcm_period_elapsed(substream);
+                }
+        }
+        urb->status = 0;
+
+        if(adev->shutdown)
+                return;
+
+        if((status = usb_submit_urb(urb, GFP_ATOMIC)))
+                printk("resubmit of audio urb failed (error=%i)\n", status);
+        return;
+}
+
+
+static int em28xx_isoc_audio_deinit(struct em28xx_audio *adev)
+{
+        int i;
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                usb_kill_urb(adev->urb[i]);
+                usb_free_urb(adev->urb[i]);
+                adev->urb[i] = NULL;
+        }
+        return 0;
+}
+
+static int em28xx_init_audio_isoc(struct em28xx_audio *adev)
+{
+        int i;
+        int errCode;
+        const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE;
+
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                struct urb *urb;
+                int j,k;
+                adev->transfer_buffer[i]=kzalloc(sb_size,GFP_ATOMIC);
+
+                if(!adev->transfer_buffer[i])
+                        return -ENOMEM;
+
+                urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+                if(urb){
+                        urb->dev = adev->udev;
+                        urb->context = adev;
+                        urb->pipe = usb_rcvisocpipe(adev->udev,0x83);
+                        urb->transfer_flags = URB_ISO_ASAP;
+                        urb->transfer_buffer = adev->transfer_buffer[i];
+                        urb->interval = 1;
+                        urb->complete = em28xx_audio_isocirq;
+                        urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS;
+                        urb->transfer_buffer_length = sb_size;
+                        for(j=k=0; j<EM28XX_NUM_AUDIO_PACKETS; j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+                                urb->iso_frame_desc[j].offset = k;
+                                urb->iso_frame_desc[j].length = EM28XX_AUDIO_MAX_PACKET_SIZE;
+                        }
+                        adev->urb[i] = urb;
+                } else
+                        return -ENOMEM;
+
+        }
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                errCode = usb_submit_urb(adev->urb[i], GFP_ATOMIC);
+                if (errCode){
+                        em28xx_isoc_audio_deinit(adev);
+                        return errCode;
+                }
+        }
+        return 0;
+}
+
+static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg)
+{
+        switch(cmd){
+	case EM28XX_CAPTURE_STREAM_EN:
+		if(adev->capture_stream == STREAM_OFF && arg==1){
+			adev->capture_stream = STREAM_ON;
+			em28xx_init_audio_isoc(adev);
+		} else if (adev->capture_stream == STREAM_ON && arg==0){
+			adev->capture_stream = STREAM_OFF;
+			em28xx_isoc_audio_deinit(adev);
+		}
+		return 0;
+	default:
+		return -EINVAL;
+        }
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream)
+#else
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev;
+        snd_pcm_uframes_t hwptr_done;
+        adev = snd_pcm_substream_chip(substream);
+        hwptr_done = adev->hwptr_done_capture;
+        return hwptr_done;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs,
+                                             unsigned long offset)
+#else
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+                                             unsigned long offset)
+#endif
+{
+        void *pageptr = subs->runtime->dma_area + offset;
+        return vmalloc_to_page(pageptr);
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_open(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+	int errCode;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_runtime_t *runtime = substream->runtime;
+#else
+        struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+
+	if(adev->alt == 0 && adev->users == 0 ) 
+                errCode = usb_set_interface(adev->udev, 1, adev->alt_max); /* Todo */
+
+	runtime->hw = snd_em28xx_hw_capture;
+
+        adev->users++;
+        runtime->private_data = adev;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	adev->capture_pcm_substream = substream;
+	return 0;
+
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        adev->users--;
+        if(adev->users == 0 && adev->shutdown == 1) {
+                adev->shutdown = 0;
+                em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0);
+        }
+        wake_up(&adev->open);
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_ops_t snd_em28xx_pcm_capture = {
+#else
+static struct snd_pcm_ops snd_em28xx_pcm_capture = {
+#endif
+        .open = snd_em28xx_capture_open,
+        .close = snd_em28xx_pcm_close,
+        .ioctl = snd_pcm_lib_ioctl,
+        .hw_params = snd_em28xx_hw_capture_params,
+        .hw_free = snd_em28xx_hw_capture_free,
+        .prepare = snd_em28xx_prepare,
+        .trigger = snd_em28xx_capture_trigger,
+        .pointer = snd_em28xx_capture_pointer,
+        .page = snd_pcm_get_vmalloc_page,
+};
+
+
+static int em28xx_audio_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+	int err;
+	int i;
+	u8 max_alt;
+	u8 if_num;
+	u16 max_pck = 0, cur_pck = 0;
+	struct usb_device *udev;
+	struct em28xx_audio *adev = NULL;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_t *pcm;
+        snd_card_t *card;
+#else
+        struct snd_pcm *pcm;
+        struct snd_card *card;
+#endif
+
+
+	int retval = -ENODEV;
+	u8 i_max = 0;
+        static int devnr;
+        int ret;
+
+
+	struct usb_endpoint_descriptor *endpoint;
+	udev = usb_get_dev(interface_to_usbdev(interface));
+	endpoint = &interface->cur_altsetting->endpoint[0].desc;
+
+	if_num = interface->altsetting[0].desc.bInterfaceNumber;
+	max_alt = interface->num_altsetting;
+
+	if (max_alt == 6 && if_num > 0) {
+		printk("registering em28xx-audioep\n");
+		for (i=0; i<max_alt; i++) {
+			cur_pck=le16_to_cpu(interface->altsetting[i].endpoint[0].desc.wMaxPacketSize);
+			if (cur_pck > max_pck) {
+				i_max = i;
+				max_pck = cur_pck;
+			}
+		}
+		adev = kzalloc(sizeof(struct em28xx_audio), GFP_KERNEL);
+		retval = 0;
+		adev->max_pck = max_pck;
+		adev->alt_max = i_max;
+		adev->udev = udev;
+		
+		printk("em28xx-audioep.c: detected nonstandard usbaudio\n");
+		printk("em28xx-audioep.c: Copyright (C) 2008 Empia Technology Inc.\n");
+		printk("em28xx-audioep.c: Copyright (C) 2008 Markus Rechberger\n");
+		
+
+		card = snd_card_new(index[devnr], "Em28xx Audio2", THIS_MODULE, 0);
+
+		// todo increase devnr .. or better make a bitmap out of it
+
+		if(card == NULL) {
+			kfree(adev);
+			return -ENOMEM;
+		}
+
+		spin_lock_init(&adev->slock);
+		init_waitqueue_head(&adev->audio_wait_stream);
+		init_waitqueue_head(&adev->open);
+		ret = snd_pcm_new(card, "Em28xx Audio2", 0, 0, 1, &pcm);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
+		pcm->info_flags = 0;
+		pcm->private_data = adev;
+		strcpy(pcm->name, "Empia em28xx Capture");
+		strcpy(card->driver, "Empia Em28xx Audio 2");
+		strcpy(card->shortname, "Em28xx Audio 2");
+		strcpy(card->longname, "Empia Em28xx Audio 2");
+
+		if ((err = snd_card_register(card)) < 0) {
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+		adev->sndcard = card;
+
+		usb_set_intfdata(interface, adev);
+	}
+	return retval;
+}
+
+static void em28xx_audio_disconnect(struct usb_interface *interface)
+{
+	struct em28xx_audio *adev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+	if (!adev)
+		return;
+
+	if (adev->users > 0)
+		wait_event(adev->open, (adev->users == 0));
+
+	snd_card_free(adev->sndcard);
+	kfree(adev);
+}
+
+static struct usb_driver em28xx_audio_drv = {
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 15)
+	.owner = THIS_MODULE,
+#endif
+	.name = "em28xx-audioep",
+	.probe = em28xx_audio_probe,
+	.disconnect = em28xx_audio_disconnect,
+	.id_table = em28xx_audio_id_table,
+};
+
+static int __init em28xx_audio_init(void)
+{
+	int result;
+	result = usb_register(&em28xx_audio_drv);
+	if (result)
+		printk("usb_register failed. Error number %d.\n", result);
+	return result;
+}
+
+static void __exit em28xx_audio_exit(void)
+{
+	usb_deregister(&em28xx_audio_drv);
+}
+
+module_init(em28xx_audio_init);
+module_exit(em28xx_audio_exit);
diff --git a/drivers/media/video/empia/em28xx-cards.c b/drivers/media/video/empia/em28xx-cards.c
new file mode 100644
index 0000000..00bea04
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-cards.c
@@ -0,0 +1,3387 @@
+/*
+   em28xx-cards.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 - 2008 Markus Rechberger <mrechberger@gmail.com>
+		 2005       Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005       Sascha Sommer <saschasommer@freenet.de>
+		 2005       Ludovico Cavedon <cavedon@sssup.it>
+
+   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/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <media/tuner.h>
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 26)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#include "msp3400-driver.h"
+#include <media/saa7115.h>
+#include <media/tvp5150.h>
+
+#include "xc3028/xc3028_firmwares.h"
+#include "xc3028/xc3028l_firmwares.h"
+#include "xc3028/xc3028_channelmaps.h"
+#define XCEIVE_EXTERNAL_FIRMWARE
+#ifdef XCEIVE_EXTERNAL_FIRMWARE
+#include "xc3028/xc3028fw_helper.h"
+#endif
+
+#include "em28xx.h"
+#include "em28xx-keymaps.h"
+
+extern unsigned char *XC5000_firmware_SEQUENCE;
+
+
+
+static const u8 em28xx_terratec_cinergy_200_usb_i2c_devs[]     = {0x4a, 0x60, 0x62, 0x64, 0x86, 0xc0, 0xc2, 0};
+static const u8 em28xx_vgear_pockettv_i2c_devs[]               = {0x4a, 0x60, 0xc6, 0};
+
+/* keep this map private */
+#define EETI_XC3028_DEFAULT_ANALOG { 					\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028_tv_mode_d_k_pal_nicam_mono, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028_tv_mode_i_pal_nicam_mono, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono,        \
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV tested ok */			\
+		.id = V4L2_STD_SECAM_L, 				\
+                .tv_mode = &XC3028_tv_mode_l_secam_nicam_am_only,       \
+		.channelmap = &XC3028_channel_map_france_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}								\
+}
+
+/* keep this map private */
+#define EETI_XC3028_DEFAULT_ANALOG_STEREO {				\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028_tv_mode_d_k_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028_tv_mode_i_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_if,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_L, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_ANALOG {					\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028L_tv_mode_b_g_pal_nicam_b,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028L_tv_mode_d_k_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028L_tv_mode_i_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028L_tv_mode_m_n_ntsc_pal_a2_if,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_L, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_DVBT { 					\
+	{								\
+		/* DVB-T */						\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_QAM { 					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv6_zarlink_qam_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_FM { 					\
+	{								\
+		.tv_mode = &XC3028L_tv_mode_fm_radio_input1, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_ATSC {					\
+	{								\
+		/* ATSC */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv6_atsc_lg_6_0mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_QAM { 					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_zarlink_qam_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_DVBT { 					\
+	{								\
+		/* DVB-T */						\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+
+#define EETI_XC3028_DEFAULT_ATSC {					\
+	{								\
+		/* ATSC */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_atsc_lg_6_0mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_QAM64 {					\
+	{	/* QAM64 */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_qam_6_0mhz, 		\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_FM { 					\
+	{								\
+		.tv_mode = &XC3028_tv_mode_fm_radio_input1, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_ANALOG {					\
+	{								\
+		.name = "PAL-BG", 					\
+		.id = V4L2_STD_PAL_BG, 					\
+		.index = 4, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-DK", 					\
+		.id = V4L2_STD_PAL_DK, 					\
+		.index = 9, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-I", 					\
+		.id = V4L2_STD_PAL_I, 					\
+		.index = 7, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "NTSC M", 					\
+		.id = V4L2_STD_NTSC_M, 					\
+		.index = 0, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", 					\
+		.id = V4L2_STD_SECAM_L, 				\
+		.index = 15, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "SECAM LC", 					\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.index = 16, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "SECAM K1", 					\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.index = 15, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-M", /* todo, this one needs testing */	\
+		.id = V4L2_STD_PAL_M, 					\
+		.index = 1, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_FM {					\
+	{								\
+		.name = "FM Radio", 					\
+		.index = 22, 						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_DVBT {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 18						\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.index = 20						\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.index = 17						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_ATSC {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 0 /* TODO */					\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_QAM {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 0 /* TODO */					\
+	}								\
+}
+
+#define EETI_DEFAULT_GPIO {						\
+	.ts1_on     = _BIT_VAL(EM28XX_GPIO0,  0, 0), 			\
+	.a_on       = _BIT_VAL(EM28XX_GPIO1,  0, 0), 			\
+	.xc3028_sec = _BIT_VAL(EM28XX_GPIO2,  1, 0), 			\
+	/* reserved */							\
+	.t1_reset   = _BIT_VAL(EM28XX_GPIO4,  0, 1), 			\
+	/* reserved */							\
+	.t1_on      = _BIT_VAL(EM28XX_GPIO6,  0, 0), 			\
+	.t2_on      = _BIT_VAL(EM28XX_GPIO7,  1, 0), 			\
+									\
+	.l1_on      = _BIT_VAL(EM28XX_GOP2,   1, 0), 			\
+	.d1_reset   = _BIT_VAL(EM28XX_GOP3,   0, 1), 			\
+}
+
+#define EETI_DRX3975D_GPIO {						\
+	.ts1_on     = _BIT_VAL(EM28XX_GPIO0,  0, 0), 			\
+	.a_on       = _BIT_VAL(EM28XX_GPIO1,  0, 0), 			\
+	.xc3028_sec = _BIT_VAL(EM28XX_GPIO2,  1, 0), 			\
+	/* reserved */							\
+	.t1_reset   = _BIT_VAL(EM28XX_GPIO4,  0, 1), 			\
+	/* reserved */							\
+	.t1_on      = _BIT_VAL(EM28XX_GPIO6,  0, 0), 			\
+	.t2_on      = _BIT_VAL(EM28XX_GPIO7,  1, 0), 			\
+									\
+	.l1_on      = _BIT_VAL(EM28XX_GOP2,   1, 0), 			\
+}
+
+struct em28xx_board em28xx_boards[] = {
+	/* FIXME: please verify the supported video standards */
+	[EM2800_BOARD_GENERIC] = {
+		.name         = "Generic EM2800 video grabber",
+	},
+	[EM2820_BOARD_GENERIC] = {
+		.name         = "Generic EM2820 video grabber",
+	},
+	[EM2821_BOARD_GENERIC] = {
+		.name         = "Generic EM2821 video grabber",
+	},
+	[EM2750_BOARD_GENERIC] = {
+		.name         = "Generic EM2750 video grabber",
+	},
+	[EM2860_BOARD_GENERIC] = {
+		.name         = "Generic EM2860 video grabber",
+	},
+	[EM2861_BOARD_GENERIC] = {
+		.name         = "Generic EM2861 video grabber",
+	},
+	[EM2870_BOARD_GENERIC] = {
+		.name         = "Generic EM2870 video grabber",
+	},
+	[EM2881_BOARD_GENERIC] = {
+		.name         = "Generic EM2881 video grabber",
+	},
+	[EM2883_BOARD_GENERIC] = {
+		.name         = "Generic EM2883 video grabber",
+	},
+	[EM2820_BOARD_KWORLD_PVRTV2800RF] = {
+		.name         = "KWorld PVR TV 2800 RF",
+		.vchannels    = 2,
+		.norm         = V4L2_STD_PAL_BG,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input           = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_TERRATEC_CINERGY_250] = {
+		.name         = "Terratec Cinergy 250 USB",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.ir_i2c       = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_DLINK_USB_TV] = {
+		.name         = "D-Link DUB-T210 TV Tuner",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2821_BOARD_PROLINK_PLAYTV_USB2] = {
+		.name         = "SIIG AVTuner-PVR/Prolink PlayTV USB 2.0",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC, /* unknown? */
+		.tda9887_conf = TDA9887_PRESENT, /* unknown? */
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_NOTOUCH,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 2,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}},
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_HERCULES_SMART_TV_USB2] = {
+		.name         = "Hercules Smart TV USB 2.0",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2] = {
+		.name         = "Pinnacle PCTV USB 2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2_FM1216ME] = {
+		.name         = "Pinnacle PCTV USB 2 (Philips FM1216ME)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms        = {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+			}, {
+				.name = "SECAM-DK",
+				.id = V4L2_STD_SECAM_DK,
+			} },
+	},
+	[EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = {
+		.name         = "Hauppauge WinTV USB 2 (R2)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_ABSENT,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.dev_modes    = EM28XX_VIDEO,
+		/*FIXME: S-Video not tested */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2820_BOARD_HAUPPAUGE_WINTV_USB_2] = {
+		.name         = "Hauppauge WinTV USB 2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC,
+		.tuner_type   = TUNER_ABSENT,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.dev_modes    = EM28XX_VIDEO,
+		/*FIXME: S-Video not tested */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = {
+		.name         = "Hauppauge WinTV USB 2 R2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2821_BOARD_SUPERCOMP_USB_2] = {
+		.name         = "Supercomp USB 2.0 TV",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900] = {
+		.name         = "Hauppauge WinTV HVR 900",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT
+	},
+	[EM2883_BOARD_PINNACLE_PCTV_HD_PRO] = {
+		.name         = "Pinnacle PCTV HD Pro",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028_DEFAULT_QAM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_F306] = {
+		.name         = "KWorld PlusTV F306",
+		.em_type      = EM2883,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2888_BOARD_KWORLD_HYBRID_E329] = {
+		.name	      = "KWorld 329u",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.ir_keytab    = ir_codes_em_kworld,
+		.ir_getkey    = em2888_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028L_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2888_BOARD_LINCOLN_TV_FM] = {
+		.name	      = "Lincoln TV FM",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_E323] = {
+		.name	      = "KWorld E323",
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input        = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028_DEFAULT_FM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_A316] = {
+		.name         = "KWorld PlusTV HD Hybrid 330",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950] = {
+		.name         = "Hauppauge WinTV HVR 950",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028_DEFAULT_QAM,
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2] = {
+		.name         = "Hauppauge WinTV HVR (B2C0)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_hauppauge_new_u,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DRX3975D_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2860_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Cinergy A Hybrid XS",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2880_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Hybrid XS",
+		.vchannels    = 3,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2861_BOARD_KWORLD_PVRTV_300U] = {
+		.name	      = "KWorld PVRTV 300U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2880_BOARD_KWORLD_DVB_310U] = {
+		.name	      = "KWorld DVB-T 310U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_KWORLD_DVB_305U] = {
+		.name	      = "KWorld DVB-T 305U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_TERRATEC_HYBRID_XS_FR] = {
+		.name         = "Terratec Hybrid XS Secam",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_SECAM_L,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.has_msp34xx  = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2882_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Empia Hybrid ATSC (em2882)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec, 
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_TERRATEC_HYBRID_XS_FM] = {
+		.name         = "Terratec Hybrid XS FM (em2883)",
+		.em_type      = EM2883,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_radio    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.decoder      = EM28XX_CX25843,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC5000_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+	},
+	[EM2881_BOARD_DNT_DA2_HYBRID] = {
+		.name         = "DNT DA2 Hybrid",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_TERRATEC_XS] = {
+		.name         = "Terratec Cinergy T XS",
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.dev_modes    = EM28XX_DVBT,
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_TERRATEC_XS_MT2060] = {
+		.name         = "Terratec Cinergy T XS (MT2060)",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+#if 0
+		.manual_gpio  = 1,
+		.gpio_regs    = EETI_DEFAULT_GPIO,
+#endif
+	},
+	[EM2870_BOARD_PINNACLE_PCTV_DVB] = {
+		.name         = "Pinnacle PCTV DVB-T",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+		.ir_keytab    = ir_codes_pinnacle2,
+		.ir_getkey    = em2880_get_key_empia,
+#if 0
+		.manual_gpio  = 1,
+		.gpio_regs    = EETI_DEFAULT_GPIO,
+#endif
+	},
+	[EM2870_BOARD_KWORLD_350U] = {
+		.name         = "KWorld 350 U DVB-T",
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.dev_modes    = EM28XX_DVBT,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_KWORLD_355U] = {
+		.name         = "KWorld 355 U DVB-T",
+		.tuner_type   = TUNER_QT1010,
+		.dev_modes    = EM28XX_DVBT,
+		.manual_gpio  = 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+	},
+	[EM2870_BOARD_COMPRO_VIDEOMATE] = {
+		.name         = "Compro, VideoMate U3",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+	},
+	[EM2881_BOARD_PINNACLE_HYBRID_PRO] = {
+		.name         = "Pinnacle Hybrid Pro",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_pinnacle2,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2882_BOARD_PINNACLE_HYBRID_PRO] = {
+		.name         = "Pinnacle Hybrid Pro (em2882)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_pinnacle2_usb,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DRX3975D_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	/* maybe there's a reason behind it why Terratec sells the Hybrid XS as Prodigy XS with a
+	 * different PID, let's keep it separated for now maybe we'll need it lateron */
+	[EM2880_BOARD_TERRATEC_PRODIGY_XS] = {
+		.name         = "Terratec Prodigy XS",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_MSI_DIGIVOX_AD] = {
+		.name         = "MSI DigiVox A/D",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_MSI_DIGIVOX_AD_II] = {
+		.name         = "MSI DigiVox A/D II",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_ATI_TVWONDER600] = {
+		.name         = "ATI TV Wonder HD 600",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_ATSC | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028L_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028L_DEFAULT_QAM,
+	},
+	[EM2820_BOARD_MSI_VOX_USB_2] = {
+		.name		= "MSI VOX USB 2.0",
+		.vchannels	= 3,
+		.norm		= V4L2_STD_PAL_BG,
+		.tuner_type	= TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf	= TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner	= 1,
+		.decoder        = EM28XX_SAA7114,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE4,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2800_BOARD_TERRATEC_CINERGY_200] = {
+		.name         = "Terratec Cinergy 200 USB",
+		.em_type      = EM2800,
+		.i2c_devs     = em28xx_terratec_cinergy_200_usb_i2c_devs,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.ir_i2c       = 1,
+
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE] = {
+		.name         = "Leadtek Winfast USB II Deluxe",
+		.em_type      = EM2820,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7114,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = 2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = 9,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2800_BOARD_LEADTEK_WINFAST_USBII] = {
+		.name         = "Leadtek Winfast USB II",
+		.em_type      = EM2800,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2800_BOARD_KWORLD_USB2800] = {
+		.name         = "KWorld USB2800",
+		.em_type      = EM2800,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+#if defined TUNER_PHILIPS_FCV1236D
+		.tuner_type   = TUNER_PHILIPS_FCV1236D,
+#else
+		.tuner_type   = TUNER_PHILIPS_ATSC,
+#endif
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}
+		},
+	},
+	[EM2820_BOARD_PINNACLE_DVC_90] = {
+		.name         = "Pinnacle Dazzle DVC 90",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 0,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_DVC_100] = {
+		.name         = "Pinnacle Dazzle DVC 100",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 0,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "SECAM L",
+				.id = V4L2_STD_SECAM_L,
+			}, {
+				.name = "SECAM LC",
+				.id = V4L2_STD_SECAM_LC,
+			}, {
+				.name = "SECAM K1",
+				.id = V4L2_STD_SECAM_K1,
+		} },
+	},
+	[EM2820_BOARD_VIDEOLOGY_20K14XUSB] = {
+		.name          = "Videology 20K14XUSB USB2.0",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.has_tuner     = 0,
+		.ctrl          = em28xx_vy_cctrl,
+		.gctrl         = em28xx_vy_gctrl,
+		.qctrl         = em28xx_vy_qctrl,
+		.dev_modes      = EM28XX_VIDEO,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2821_BOARD_USBGEAR_VD204] = {
+		.name          = "Usbgear VD204v9",
+		.vchannels     = 2,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type  = EM28XX_VMUX_COMPOSITE1,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = 1,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2860_BOARD_TYPHOON_DVD_MAKER] = {
+		.name          = "Typhoon DVD Maker",
+		.vchannels     = 2,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type  = EM28XX_VMUX_COMPOSITE1,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = 1,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2860_BOARD_GADMEI_UTV330] = {
+		.name         = "Gadmei UTV330",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.ir_keytab    = ir_codes_em_gadmei_usb,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+		} },
+	},
+	[EM2820_BOARD_GADMEI_UTV310] = {
+		.name         = "Gadmei UTV310",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2800_BOARD_VGEAR_POCKETTV] = {
+		.name         = "V-Gear PocketTV",
+		.em_type      = EM2800,
+		.i2c_devs     = em28xx_vgear_pockettv_i2c_devs,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2861_BOARD_YAKUMO_MOVIE_MIXER] = {
+		.name          = "Yakumo MovieMixer",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_TVP5150,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2860_BOARD_NETGMBH_CAM] = {
+		.name          = "NetGMBH Cam", /* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2750_BOARD_DLCW_130] = {
+		.name          = "Huaqi DLCW-130", /* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.dev_modes      = EM28XX_VIDEO,
+		.em_type	= EM2751,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2751_BOARD_EMPIA_SAMPLE] = {
+		.name          = "EM2751 Webcam + Audio",
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.dev_modes      = EM28XX_VIDEO,
+		.em_type	= EM2751,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2861_BOARD_PLEXTOR_PX_TV100U] = {
+		.name         = "Plextor ConvertX PX-TV100U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.has_msp34xx  = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,  /* EM28XX_AUDIO ? */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms        = {{
+			.name = "NTSC",
+
+			.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2863_BOARD_EMPIA_GENERIC] = {
+		.name         = "Empia generic",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = 1,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 3,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = 2,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+#if 0
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+#endif
+
+		.manual_gpio  = 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+#if 0
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+#endif
+	},
+	[EM2883_BOARD_KWORLD_A340] = {
+		.name         = "KWorld ATSC 340",
+		.tuner_type   = TUNER_PHILIPS_TDA18271,
+		.dev_modes    = EM28XX_ATSC,
+	},
+	[EM2875_BOARD_SAMPLE_ISDBT] = {
+		.name         = "Empia ISDB",
+		.em_type      = EM2875,
+		.dev_modes    = EM28XX_ISDB,
+	},
+	[EM2879_BOARD_SAMPLE_DMB] = {
+		.name         = "Empia DMB-T",
+		.tuner_type   = TUNER_ADIMTV102,
+		.dev_modes    = EM28XX_DMB,
+	},
+	[EM2883_BOARD_EMPIA_HYBRID_ATSC] = {
+		.name         = "Empia Hybrid XS ATSC",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+		.atscnorms	= EETI_XC5000_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC5000_DEFAULT_QAM,
+	},
+	[EM2883_BOARD_EQUINUX_TUBESTICK_ATSC] = {
+		.name         = "Equinux TubeStick Hybrid ATSC",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+		.atscnorms	= EETI_XC5000_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC5000_DEFAULT_QAM,
+	},
+	[EM2888_BOARD_DVB_TC_HYBRID] = {
+		.name	      = "Pinnacle 510e",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+	},
+	[EM2888_BOARD_EMPIA_HYBRID] = {
+		.name	      = "Empia Hybrid PCTV",
+		.em_type      = EM2888, /* TODO read this from the chip */
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028L_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2860_BOARD_KAIOMY_TVNPC_U2] = {
+		/* TODO radio support */
+		.name	      = "Kaiomy TVnPC U2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_kworld,
+		.ir_getkey    = em2860_get_key_kaiomy,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2861_BOARD_POLLIN_USB_R1] = {
+		.name          = "Pollin USB-R1",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+		.input           = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}},
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			},{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+		}},
+	},
+	[EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H] = {
+		/* TODO: FM radio support for this Leadtek device */
+		.name         = "Leadtek PalmTop DTV 200H",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2820_BOARD_COMPRO_VIDEO_MATE] = {
+		.name         = "Compro VideoMate ForYou/Stereo",
+		.vchannels    = 2,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+	},
+};
+
+const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
+
+/*
+ * seems like it's possible to flash the eeprom, somehow one of my HVR 900 devices suddenly
+ * had the content of a WinTV USB 2 eeprom and thus identified itself as a Wintv USB 2
+ * device which of course didn't work...
+ *
+ */
+
+/* table of devices that work with this driver */
+struct usb_device_id em28xx_id_table [] = {
+	{ USB_DEVICE(0xeb1a, 0x2800), .driver_info = EM2800_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2820), .driver_info = EM2820_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2821), .driver_info = EM2821_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2750), .driver_info = EM2750_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2860), .driver_info = EM2860_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2861), .driver_info = EM2861_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2881), .driver_info = EM2881_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2870), .driver_info = EM2870_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0xe310), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD },
+	{ USB_DEVICE(0xeb1a, 0xe320), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD_II },
+	{ USB_DEVICE(0xeb1a, 0xe300), .driver_info = EM2861_BOARD_KWORLD_PVRTV_300U },
+	{ USB_DEVICE(0xeb1a, 0xe350), .driver_info = EM2870_BOARD_KWORLD_350U },
+	{ USB_DEVICE(0xeb1a, 0xe355), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0xeb1a, 0xe357), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0x1ae7, 0x0380), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0x0ccd, 0x0036), .driver_info = EM2820_BOARD_TERRATEC_CINERGY_250 },
+	{ USB_DEVICE(0x2304, 0x0208), .driver_info = EM2820_BOARD_PINNACLE_USB_2 },
+	{ USB_DEVICE(0x2040, 0x4200), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+	{ USB_DEVICE(0x2040, 0x4201), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2 },
+	{ USB_DEVICE(0x2040, 0x650a), .driver_info = EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2 },
+	{ USB_DEVICE(0x2304, 0x0207), .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x2304, 0x021a), .driver_info = EM2820_BOARD_PINNACLE_DVC_100 },
+	{ USB_DEVICE(0x2040, 0x6500), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 },
+	{ USB_DEVICE(0x2040, 0x6502), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 },
+	{ USB_DEVICE(0x0ccd, 0x0042), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x004f), .driver_info = EM2860_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x004c), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS_FR },
+	{ USB_DEVICE(0x0ccd, 0x005e), .driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x0043), .driver_info = EM2870_BOARD_TERRATEC_XS },
+	{ USB_DEVICE(0x0ccd, 0x0047), .driver_info = EM2880_BOARD_TERRATEC_PRODIGY_XS },
+	{ USB_DEVICE(0x185b, 0x2870), .driver_info = EM2870_BOARD_COMPRO_VIDEOMATE },
+	{ USB_DEVICE(0x0413, 0x6023), .driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII },
+	{ USB_DEVICE(0x2001, 0xf112), .driver_info = EM2820_BOARD_DLINK_USB_TV },
+	{ USB_DEVICE(0xeb1a, 0x2883), .driver_info = EM2883_BOARD_GENERIC },
+	{ USB_DEVICE(0x2040, 0x6513), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x6517), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651b), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651f), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2304, 0x0227), .driver_info = EM2883_BOARD_PINNACLE_PCTV_HD_PRO },
+	{ USB_DEVICE(0x2304, 0x0226), .driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO },
+	{ USB_DEVICE(0xeb1a, 0x2751), .driver_info = EM2751_BOARD_EMPIA_SAMPLE },
+	{ USB_DEVICE(0xeb1a, 0xe305), .driver_info = EM2880_BOARD_KWORLD_DVB_305U },
+	{ USB_DEVICE(0xeb1a, 0xa316), .driver_info = EM2883_BOARD_KWORLD_HYBRID_A316 },
+	{ USB_DEVICE(0x093b, 0xa005), .driver_info = EM2861_BOARD_PLEXTOR_PX_TV100U },
+	{ USB_DEVICE(0xeb1a, 0x50a6), .driver_info = EM2860_BOARD_GADMEI_UTV330 },
+	{ USB_DEVICE(0x0ccd, 0x0072), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM },
+	{ USB_DEVICE(0x0ccd, 0x0092), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM },
+	{ USB_DEVICE(0xeb1a, 0x2863), .driver_info = EM2863_BOARD_EMPIA_GENERIC },
+
+	{ USB_DEVICE(0xeb1a, 0xf306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 },
+	{ USB_DEVICE(0xeb1a, 0xe306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 }, /* development only */
+	{ USB_DEVICE(0x1b80, 0xe329), .driver_info = EM2888_BOARD_KWORLD_HYBRID_E329 },
+	{ USB_DEVICE(0xeb1a, 0x2889), .driver_info = EM2888_BOARD_EMPIA_HYBRID },
+	{ USB_DEVICE(0xeb1a, 0xe323), .driver_info = EM2883_BOARD_KWORLD_HYBRID_E323 },
+	{ USB_DEVICE(0xeb1a, 0xa139), .driver_info = EM2888_BOARD_LINCOLN_TV_FM },
+#if 0
+	{ USB_DEVICE(0x2304, 0x0242), .driver_info = EM2888_BOARD_DVB_TC_HYBRID },
+#endif
+	{ USB_DEVICE(0x0438, 0xb002), .driver_info = EM2883_BOARD_ATI_TVWONDER600 },
+	{ USB_DEVICE(0xeb1a, 0x2875), .driver_info = EM2875_BOARD_SAMPLE_ISDBT },
+	{ USB_DEVICE(0xeb1a, 0x2879), .driver_info = EM2879_BOARD_SAMPLE_DMB   },
+	{ USB_DEVICE(0xeb1a, 0xe301), .driver_info = EM2860_BOARD_KAIOMY_TVNPC_U2 },
+	{ USB_DEVICE(0x0413, 0x6f02), .driver_info = EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H },
+	{ USB_DEVICE(0x185b, 0x2041), .driver_info = EM2820_BOARD_COMPRO_VIDEO_MATE },
+	
+	{ },
+};
+EXPORT_SYMBOL(em28xx_id_table);
+
+/* TODO clean this up */
+int em28xx_card_setup(struct em28xx *dev)
+{
+	int ret = 0;
+
+#ifdef XCEIVE_EXTERNAL_FIRMWARE
+	switch(dev->tuner_type) {
+	case TUNER_XCEIVE_XC3028:
+	{
+		const struct firmware *fw;
+		static unsigned char *xc3028_firmware;
+
+		if (xc3028_firmware==NULL) {
+			ret = request_firmware(&fw, "empia_xc3028.fw", &dev->udev->dev);
+
+			if (ret) {
+				printk(KERN_INFO"Couldn't find empia_xc3028.fw, please run:\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+			if (fw->size != 120273) {
+				printk(KERN_INFO"Firmware size doesn't match, please be sure\n");
+				printk(KERN_INFO"that you picked the correct firmware\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			} else 
+				printk(KERN_INFO"xc3028 firmware successfully loaded to RAM\n");
+
+
+			xc3028_firmware = kzalloc(fw->size, GFP_KERNEL);
+			memcpy(xc3028_firmware, fw->data, fw->size);
+			release_firmware(fw);
+
+			xc3028_set_firmware(xc3028_firmware, 120273);
+		}
+		break;
+	}
+	case TUNER_XCEIVE_XC5000:
+	{
+		const struct firmware *fw;
+
+		if (XC5000_firmware_SEQUENCE == NULL) {
+			ret = request_firmware(&fw, "empia_xc5000.fw", &dev->udev->dev);
+
+			if (ret) {
+				printk(KERN_INFO"Couldn't find empia_xc5000.fw, please run:\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+			if (fw->size != 12376) {
+				printk(KERN_INFO"Firmware size doesn't match, please be sure\n");
+				printk(KERN_INFO"that you picked the correct firmware\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+
+			XC5000_firmware_SEQUENCE = kzalloc(fw->size, GFP_KERNEL);
+			memcpy(XC5000_firmware_SEQUENCE, &fw->data[6], fw->size-6);
+			release_firmware(fw);
+
+		}
+		break;
+	}
+	default:
+		break;
+	}
+#endif
+
+	switch (dev->model) {
+	case EM2750_BOARD_DLCW_130:
+	case EM2751_BOARD_EMPIA_SAMPLE:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+	case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+#ifdef EM28XX_TVEEPROM
+		struct tveeprom tv;
+		request_module("tveeprom");
+#endif
+#ifdef CONFIG_MODULES
+		request_module("ir-kbd-i2c");
+		request_module("msp3400");
+#endif
+		/* Call first TVeeprom */
+
+#ifdef EM28XX_TVEEPROM
+		dev->i2c_client.addr = 0xa0 >> 1;
+		tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata);
+
+		dev->tuner_type = tv.tuner_type;
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 26)
+		if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
+#else
+		if (tv.audio_processor == V4L2_IDENT_MSPX4XX) {
+#endif
+			dev->i2s_speed = 2048000;
+			dev->has_msp34xx = 1;
+		} else
+			dev->has_msp34xx = 0;
+
+		if (dev->has_msp34xx) {
+			/* Send a reset to other chips via gpio */
+			ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf7", 1);
+			if (ret < 0)
+				return ret;
+			udelay(2500);
+			ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xff", 1);
+			if (ret < 0)
+				return ret;
+			udelay(2500);
+		}
+#endif
+
+		break;
+	}
+	case EM2820_BOARD_KWORLD_PVRTV2800RF:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* GPIO enables sound on KWORLD PVR TV 2800RF */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf9", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#ifdef CONFIG_MODULES
+		request_module("tveeprom");
+#endif
+		break;
+	}
+	case EM2820_BOARD_MSI_VOX_USB_2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* enables audio for that device */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		break;
+	}
+
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2860_BOARD_TERRATEC_HYBRID_XS:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+	case EM2861_BOARD_KWORLD_PVRTV_300U:
+	case EM2880_BOARD_KWORLD_DVB_310U:
+	case EM2880_BOARD_KWORLD_DVB_305U:
+	case EM2880_BOARD_MSI_DIGIVOX_AD:
+	case EM2880_BOARD_MSI_DIGIVOX_AD_II:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+/*		case EM2870_BOARD_COMPRO_VIDEOMATE: TODO */
+	case EM2870_BOARD_PINNACLE_PCTV_DVB:
+	{
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		/* this device needs some gpio writes to get the DVB-T demod work */
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x22", 1); /* switch em2880 rc protocol */
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* this device needs some gpio writes to get the DVB-T demod work */
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = dev->em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		break;
+	}
+	case EM2870_BOARD_KWORLD_350U:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xdf", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6e", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7e", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2870_BOARD_KWORLD_355U:
+	case EM2870_BOARD_COMPRO_VIDEOMATE:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* TODO: someone can do some cleanup here... not everything's needed */
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x04, "\x01", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xdc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		break;
+	}
+
+	case EM2883_BOARD_PINNACLE_PCTV_HD_PRO:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2883_BOARD_KWORLD_HYBRID_E323:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x3d", 1);
+		if (ret < 0)
+			return ret;
+
+		/* enable audio 12 mhz i2s*/
+		ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2883_BOARD_ATI_TVWONDER600:
+	case EM2883_BOARD_KWORLD_HYBRID_A316:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x3d", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1); /* enable audio 12 mhz i2s*/
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	{
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(5);
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); /* switch em2880 rc protocol */
+		if (ret < 0)
+			return ret;
+#if 0
+
+		ret = dev->em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = dev->em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#endif
+#if 0
+		em2880_ir_attach(dev, ir_codes_pinnacle2, ARRAY_SIZE(ir_codes_pinnacle2), em2880_get_key_empia);
+#endif
+		break;
+	}
+	case EM2820_BOARD_GADMEI_UTV310:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* Turn on analog audio output */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2860_BOARD_GADMEI_UTV330:
+	{
+		/* Turn on IR */
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2861_BOARD_PLEXTOR_PX_TV100U:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* Turn on analog audio output */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2888_BOARD_KWORLD_HYBRID_E329:
+	case EM2888_BOARD_EMPIA_HYBRID:
+	case EM2888_BOARD_LINCOLN_TV_FM:
+	{
+		ret = em28xx_write_regs(dev, 0x80, "\xf0", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf4", 1);
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x80, "\xf6", 1);
+		ret = em28xx_write_regs(dev, 0x80, "\xb6", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf6", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf5", 1);
+		if (ret < 0)
+			return ret;
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x45", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x02", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x50, "\x01", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2883_BOARD_EMPIA_HYBRID_ATSC:
+	case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC:
+	case EM2883_BOARD_TERRATEC_HYBRID_XS_FM:
+	case EM2863_BOARD_EMPIA_GENERIC:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#if 0
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#endif
+		break;
+	}
+	case EM2860_BOARD_KAIOMY_TVNPC_U2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x0f, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2875_BOARD_SAMPLE_ISDBT:
+	{
+		//            em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); // EEPROM access
+		em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x44", 1);
+		em28xx_write_regs(dev, 0x0f, "\x02", 1);
+		em28xx_write_regs(dev, 0x50, "\x0e", 1);
+		em28xx_write_regs(dev, 0x80, "\xf0", 1);
+		break;
+	}
+
+	default:
+		ret = dev->em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		if (dev->em_type != EM2800) {
+			ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+			if (ret < 0)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+void em28xx_card_disconnect(struct em28xx *dev)
+{
+	if (dev->ir_em2880)
+		em2880_ir_detach(dev);
+}
+
+EXPORT_SYMBOL(em28xx_boards);
+EXPORT_SYMBOL(em28xx_bcount);
+
+MODULE_DEVICE_TABLE (usb, em28xx_id_table);
diff --git a/drivers/media/video/empia/em28xx-core.c b/drivers/media/video/empia/em28xx-core.c
new file mode 100644
index 0000000..48b4b8c
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-core.c
@@ -0,0 +1,1366 @@
+/*
+   em28xx-core.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+		      Markus Rechberger <mrechberger@gmail.com>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+		      Sascha Sommer <saschasommer@freenet.de>
+
+   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/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+
+#include "em28xx.h"
+
+/* #define ENABLE_DEBUG_ISOC_FRAMES */
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+#define em28xx_coredbg(fmt, arg...) do {\
+	if (core_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+static unsigned int reg_debug;
+module_param(reg_debug, int, 0644);
+MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]");
+
+#define em28xx_regdbg(fmt, arg...) do {\
+	if (reg_debug) \
+		printk(KERN_INFO "%s: "fmt, \
+			 dev->name, ##arg); } while (0)
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+#define em28xx_isocdbg(fmt, arg...) do {\
+	if (isoc_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+static int alt = EM28XX_PINOUT;
+module_param(alt, int, 0644);
+MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+static void *rvmalloc(size_t size)
+{
+	void *mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+
+	mem = vmalloc_32((unsigned long)size);
+	if (!mem)
+		return NULL;
+
+	adr = (unsigned long)mem;
+	while (size > 0) {
+		SetPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return mem;
+}
+
+static void rvfree(void *mem, size_t size)
+{
+	unsigned long adr;
+
+	if (!mem)
+		return;
+
+	size = PAGE_ALIGN(size);
+
+	adr = (unsigned long)mem;
+	while (size > 0) {
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vfree(mem);
+}
+#endif
+
+/*
+ * em28xx_request_buffers()
+ * allocate a number of buffers
+ */
+u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type)
+{
+	size_t size;
+
+	void *buff = NULL;
+	u32 i;
+
+
+	size = PAGE_ALIGN((type == EM28XX_VBI) ?
+			dev->vbi_frame_size : dev->frame_size);
+
+	switch (type) {
+	case EM28XX_VIDEO:
+		if (count > EM28XX_NUM_FRAMES)
+			count = EM28XX_NUM_FRAMES;
+
+		if (dev->num_frames > 0)
+			return -EINVAL;
+
+		dev->num_frames = count;
+		size = PAGE_ALIGN(dev->frame_size);
+		break;
+	case EM28XX_VBI:
+		if (count > EM28XX_NUM_FRAMES) /* FIXME VBI_NUM_FRAMES */
+			count = EM28XX_NUM_FRAMES;
+
+		if (dev->vbi_num_frames > 0)
+			return -EINVAL;
+
+		dev->vbi_num_frames = count;
+		size = PAGE_ALIGN(dev->vbi_frame_size);
+		break;
+	default:
+		printk(KERN_INFO"em28xx-core.c: error wrong buffer request!\n");
+		return -EINVAL;
+	}
+
+	em28xx_coredbg("requested %i buffers with size %zi", count, size);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+	buff = rvmalloc(count * size);
+#else
+	buff = vmalloc_32(count * size);
+#endif
+	if (buff)
+		memset(buff, 0, count * size);
+	else
+		return -ENOMEM;
+
+	for (i = 0; i < count; i++) {
+		switch (type) {
+		case EM28XX_VBI:
+			dev->vbi_frame[i].bufmem = buff + i * size;
+			dev->vbi_frame[i].buf.index = i;
+			dev->vbi_frame[i].buf.m.offset = i * size;
+			dev->vbi_frame[i].buf.length = dev->vbi_frame_size;
+			dev->vbi_frame[i].buf.type = V4L2_BUF_TYPE_VBI_CAPTURE;
+			dev->vbi_frame[i].buf.sequence = 0;
+
+/* Indicates the field order of the image in the buffer, see Table 3-8>. This
+   field is not used when the buffer contains VBI data. Drivers must set it 
+   when type refers to an input stream, applications when an output stream. 
+ 
+   this probably should be changed since we can have an interlaced field here 
+*/
+			if (dev->vbi_interlaced)
+				dev->vbi_frame[i].buf.field = V4L2_VBI_INTERLACED;
+			else
+				dev->vbi_frame[i].buf.field = V4L2_FIELD_NONE;
+			dev->vbi_frame[i].buf.memory = V4L2_MEMORY_MMAP;
+			dev->vbi_frame[i].buf.flags = 0;
+			break;
+		case EM28XX_VIDEO:
+			dev->frame[i].bufmem = buff + i * size;
+			dev->frame[i].buf.index = i;
+			dev->frame[i].buf.m.offset = i * size;
+			dev->frame[i].buf.length = dev->frame_size;
+			dev->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+			dev->frame[i].buf.sequence = 0;
+			dev->frame[i].buf.field = V4L2_FIELD_INTERLACED; /*_TB;  V4L2_FIELD_NONE; */
+			dev->frame[i].buf.memory = V4L2_MEMORY_MMAP;
+			dev->frame[i].buf.flags = 0;
+			break;
+		default:
+			printk(KERN_INFO"error wrong buffer request!\n");
+			return -EINVAL;
+		}
+
+	}
+	return count;
+}
+
+/*
+ * em28xx_queue_unusedframes()
+ * add all frames that are not currently in use to the inbuffer queue
+ */
+void em28xx_queue_unusedframes(struct em28xx *dev, int type)
+{
+	unsigned long lock_flags;
+	u32 i;
+	switch (type) {
+	case EM28XX_VIDEO:
+		for (i = 0; i < dev->num_frames; i++) {
+			if (dev->frame[i].state == F_UNUSED) {
+				spin_lock_irqsave(&dev->queue_lock, lock_flags);
+				dev->frame[i].state = F_QUEUED;
+				list_add_tail(&dev->frame[i].frame,
+						&dev->inqueue);
+				spin_unlock_irqrestore(&dev->queue_lock,
+						lock_flags);
+			}
+		}
+		break;
+	case EM28XX_VBI:
+		for (i = 0; i < dev->vbi_num_frames; i++) {
+			if (dev->vbi_frame[i].state == F_UNUSED) {
+				spin_lock_irqsave(&dev->vbi_queue_lock,
+						lock_flags);
+				dev->vbi_frame[i].state = F_QUEUED;
+				list_add_tail(&dev->vbi_frame[i].frame,
+						&dev->vbi_inqueue);
+				spin_unlock_irqrestore(&dev->vbi_queue_lock,
+						lock_flags);
+			}
+		}
+		break;
+	}
+}
+
+/*
+ * em28xx_release_buffers()
+ * free frame buffers
+ */
+void em28xx_release_buffers(struct em28xx *dev, int type)
+{
+	if (type == EM28XX_VIDEO && dev->num_frames) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		rvfree(dev->frame[0].bufmem,
+				dev->num_frames *
+				PAGE_ALIGN(dev->frame[0].buf.length));
+#else
+		vfree(dev->frame[0].bufmem);
+		dev->num_frames = 0;
+#endif
+	} else if (type == EM28XX_VBI && dev->vbi_num_frames) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		rvfree(dev->vbi_frame[0].bufmem,
+				dev->vbi_num_frames *
+				PAGE_ALIGN(dev->vbi_frame[0].buf.length));
+#else
+		vfree(dev->vbi_frame[0].bufmem);
+		dev->vbi_num_frames = 0;
+#endif
+	}
+}
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+				   char *buf, int len)
+{
+	int ret, byte;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, buf, len, HZ);
+
+	if (reg_debug) {
+		em28xx_regdbg("%s c0 %02x 00 00 %02x 00 %02x 00 <<<",
+				(ret < 0) ? "[NOK]" : "[ OK]", req, reg, len);
+
+		for (byte = 0; byte < len; byte++)
+			printk(" %02x", (unsigned char)buf[byte]);
+		printk("\n");
+	}
+	return ret;
+}
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg)
+{
+	u8 val;
+	int ret;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return(-ENODEV);
+
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, &val, 1, HZ);
+
+	if (reg_debug) {
+		if (ret < 0)
+			em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<<\n",
+					(ret < 0) ? "[NOK]" : "[ OK]", req,
+					reg);
+		else
+			em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<< "
+				      "%02x\n", (ret < 0) ? "[NOK]" : "[ OK]",
+				      req, reg, (unsigned char)val);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+int em28xx_read_reg(struct em28xx *dev, u16 reg)
+{
+	return em28xx_read_reg_req(dev, USB_REQ_GET_STATUS, reg);
+}
+
+/*
+ * em28xx_write_regs_req()
+ * sends data to the usb device, specifying bRequest
+ */
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+				 int len)
+{
+	int ret;
+
+	/*usb_control_msg seems to expect a kmalloced buffer */
+	unsigned char *bufs;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return(-ENODEV);
+
+	bufs = kmalloc(len, GFP_KERNEL);
+
+	if (!bufs)
+		return -ENOMEM;
+	memcpy(bufs, buf, len);
+
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), req,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, bufs, len, HZ);
+	if (reg_debug) {
+		int i;
+		em28xx_regdbg("%s 40 %02x 00 00 %02x 00 %02x 00 >>>",
+				(ret < 0) ? "[NOK]" : "[ OK]", req, reg, len);
+		for (i = 0; i < len; ++i)
+			printk(" %02x", (unsigned char)buf[i]);
+		printk("\n");
+	}
+	kfree(bufs);
+	return ret;
+}
+
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len)
+{
+	return em28xx_write_regs_req(dev, USB_REQ_GET_STATUS, reg, buf, len);
+}
+
+/*
+ * em28xx_write_reg_bits()
+ * sets only some bits (specified by bitmask) of a register, by first reading
+ * the actual value
+ */
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+				 u8 bitmask)
+{
+	int oldval;
+	u8 newval;
+	oldval = em28xx_read_reg(dev, reg);
+	if (oldval < 0)
+		return oldval;
+	newval = (((u8) oldval) & ~bitmask) | (val & bitmask);
+	return em28xx_write_regs(dev, reg, &newval, 1);
+}
+
+/*
+ * em28xx_write_ac97()
+ * write a 16 bit value to the specified AC97 address (LSB first!)
+ */
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val)
+{
+	int ret;
+	u8 addr;
+	switch (dev->model) {
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+		addr = 0x1a;
+		break;
+	default:
+		addr = reg & 0x7f;
+	}
+	
+	ret = em28xx_read_reg(dev, R43_AC97BUSY_REG);
+	if (ret < 0)
+		return ret;
+	else if (((u8) ret) & 0x01) 
+		return 0;
+	
+	ret = em28xx_write_regs(dev, R40_AC97LSB_REG, val, 2);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R42_AC97ADDR_REG, &addr, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_audio_analog_set(struct em28xx *dev)
+{
+	char s[2] = { 0x00, 0x00 };
+
+	switch (dev->model) {
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+		s[0] = 0x05;
+		s[1] = 0x05;
+		break;
+	case EM2861_BOARD_PLEXTOR_PX_TV100U:
+	case EM2860_BOARD_GADMEI_UTV330:
+		switch (dev->ctl_ainput) {
+		case 0:
+			s[0] = 0xfd;
+			break;
+		case 1:
+			s[0] = 0xfc;
+			break;
+		default :
+			s[0] = 0xfe;
+			break;
+		}
+		if (dev->mute)
+			s[0] = 0xfe;
+		return em28xx_write_regs(dev, 0x08, s, 1);
+	default:
+		s[0] |= 0x1f - dev->volume;
+		s[1] |= 0x1f - dev->volume;
+	}
+
+	if (dev->mute)
+		s[1] |= 0x80;
+	return em28xx_write_ac97(dev, R02_MASTER_AC97, s);
+}
+
+int em28xx_colorlevels_set_default(struct em28xx *dev)
+{
+	int ret;
+
+	/* contrast */
+	ret = em28xx_write_regs(dev, R20_YGAIN_REG, "\x10", 1);
+	if (ret < 0)
+		return ret;
+
+	/* brightness */
+	ret = em28xx_write_regs(dev, R21_YOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	/* saturation */
+	ret = em28xx_write_regs(dev, R22_UVGAIN_REG, "\x10", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R23_UOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R24_VOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R25_SHARPNESS_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R14_GAMMA_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R15_RGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R16_GGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R17_BGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R18_ROFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R19_GOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1A_BOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_capture_start(struct em28xx *dev, int start)
+{
+	int ret;
+	/* FIXME: which is the best order? */
+	/* video registers are sampled by VREF */
+	if ((ret = em28xx_write_reg_bits(dev, R0C_USBSUSP_REG, start ?
+					0x10 : 0x00,
+					  0x10)) < 0)
+		return ret;
+	/* enable video capture */
+	return em28xx_write_regs(dev, R12_VINENABLE_REG, start ?
+			"\x67" : "\x27", 1);
+}
+
+int em28xx_outfmt_set_yuv422(struct em28xx *dev)
+{
+	int ret;
+
+	/* maybe put this configuration into the card configstruct,
+	   it's not entirely clear what values are needed for em2750
+	   boards so this is a hacking place at the moment */
+
+	if (dev->em_type == EM2750 || dev->em_type == EM2751) {
+		ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x00", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x0a", 1);
+		if (ret < 0)
+			return ret;
+	} else {
+		u8 fmt_val;
+
+		fmt_val = ((u8)em28xx_read_reg(dev, R27_OUTFMT_REG) & ~0x1f) | dev->outfmt->config;
+
+		ret = em28xx_write_regs(dev, R27_OUTFMT_REG, &fmt_val, 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x10", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x11", 1);
+		if (ret < 0)
+			return ret;
+	}
+	if (dev->dev_modes & EM28XX_VBI)
+		em28xx_set_vbi(dev, 1);
+	return 0;
+}
+
+int em28xx_set_vbi(struct em28xx *dev, int enable)
+{
+	u8 vic;
+	int ret;
+	ret = em28xx_write_regs_req(dev, 0x00, 0x34,
+			&dev->tvnorm->vbi_h_start, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x35,
+			&dev->tvnorm->vbi_v_start, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x36,
+			&dev->tvnorm->vbi_w, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x37,
+			&dev->tvnorm->vbi_h, 1);
+	if (ret < 0)
+		return ret;
+
+	if (enable)
+		vic = em28xx_read_reg(dev, R11_VINCTRL_REG) | 0x48;
+	else
+		vic = em28xx_read_reg(dev, R11_VINCTRL_REG) & ~0x48;
+
+	ret = em28xx_write_regs(dev, R11_VINCTRL_REG, &vic , 1);
+
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin,
+				  u8 ymax)
+{
+	int ret;
+	em28xx_coredbg("em28xx Scale: (%d,%d)-(%d,%d)\n", xmin,
+			ymin, xmax, ymax);
+
+	ret = em28xx_write_regs(dev, R28_XMIN_REG, &xmin, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R29_XMAX_REG, &xmax, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R2A_YMIN_REG, &ymin, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R2B_YMAX_REG, &ymax, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
+				   u16 width, u16 height)
+{
+	int ret;
+	u8 cwidth = width;
+	u8 cheight = height;
+	u8 overflow = (height >> 7 & 0x02) | (width >> 8 & 0x01);
+
+	em28xx_coredbg("em28xx Area Set: (%d,%d)\n",
+			(width | (overflow & 2) << 7),
+			(height | (overflow & 1) << 8));
+
+	ret = em28xx_write_regs(dev, R1C_HSTART_REG, &hstart, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1D_VSTART_REG, &vstart, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1E_CWIDTH_REG, &cwidth, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1F_CHEIGHT_REG, &cheight, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1B_OFLOW_REG, &overflow, 1);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v)
+{
+	int ret;
+	u8 mode;
+	u8 buf[2];
+	/* the em2800 scaler only supports scaling down to 50% */
+
+	switch (dev->em_type) {
+	case EM2750:
+#if 1 /* TODO: PLEASE VERIFY */
+		mode = (h || v)? 0x30: 0x00;
+		ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, "\x00\x30", 2);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, "\x00\x30", 2);
+		if (ret < 0)
+			return ret;
+#endif
+		break;
+	case EM2800:
+		mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00);
+		break;
+	default:
+		buf[0] = h;
+		buf[1] = h >> 8;
+		ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, (char *)buf, 2);
+		if (ret < 0)
+			return ret;
+		buf[0] = v;
+		buf[1] = v >> 8;
+		ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, (char *)buf, 2);
+		if (ret < 0)
+			return ret;
+		/* it seems that both H and V scalers must be active to work
+		   correctly */
+		mode = (h || v)? 0x30: 0x00;
+	}
+	ret = em28xx_write_reg_bits(dev, R26_COMPR_REG, mode, 0x30);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+/* FIXME: this only function read values from dev */
+int em28xx_resolution_set(struct em28xx *dev)
+{
+	int width, height;
+	width = norm_maxw(dev);
+	height = norm_maxh(dev) >> 1;
+
+	em28xx_outfmt_set_yuv422(dev);
+	em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2);
+	em28xx_capture_area_set(dev, 0, 0, width >> 2, height >> 2);
+	return em28xx_scaler_set(dev, dev->hscale, dev->vscale);
+}
+
+
+/******************* isoc transfer handling ****************************/
+
+#ifdef ENABLE_DEBUG_ISOC_FRAMES
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static void em28xx_isoc_dump(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_isoc_dump(struct urb *urb)
+#endif
+{
+	int len = 0;
+	int ntrans = 0;
+	int i;
+
+	printk(KERN_DEBUG "isocIrq: sf=%d np=%d ec=%x\n",
+	       urb->start_frame, urb->number_of_packets,
+	       urb->error_count);
+	for (i = 0; i < urb->number_of_packets; i++) {
+		unsigned char *buf =
+				urb->transfer_buffer +
+				urb->iso_frame_desc[i].offset;
+		int alen = urb->iso_frame_desc[i].actual_length;
+		if (alen > 0) {
+			if (buf[0] == 0x88) {
+				ntrans++;
+				len += alen;
+			} else if (buf[0] == 0x22) {
+				printk(KERN_DEBUG
+						"= l=%d nt=%d bpp=%d\n",
+				len - 4 * ntrans, ntrans,
+				ntrans == 0 ? 0 : len / ntrans);
+				ntrans = 1;
+				len = alen;
+			} else
+				printk(KERN_DEBUG "!\n");
+		}
+		printk(KERN_DEBUG "   n=%d s=%d al=%d %x\n", i,
+		       urb->iso_frame_desc[i].status,
+		       urb->iso_frame_desc[i].actual_length,
+		       (unsigned int)
+				       *((unsigned char *)(urb->transfer_buffer +
+				       urb->iso_frame_desc[i].
+				       offset)));
+	}
+}
+#endif
+
+static int em28xx_isoc_video(struct em28xx *dev, struct em28xx_frame_t **f,
+				struct em28xx_frame_t **vbif,
+				unsigned long *lock_flags, unsigned char buf)
+{
+
+	if (!(buf & 0x01)) {
+		if ((*vbif)) {
+			if ((*vbif)->state == F_GRABBING) {
+				/*previous frame is incomplete */
+				if ((*vbif)->fieldbytesused < dev->vbi_field_size) {
+					(*vbif)->state = F_ERROR;
+				} else {
+					(*vbif)->state = F_DONE;
+					(*vbif)->buf.bytesused = dev->vbi_frame_size;
+				}
+			}
+			if ((*vbif)->state == F_DONE ||
+					(*vbif)->state == F_ERROR) {
+				/* move current frame to outqueue and get next free buffer from inqueue */
+				spin_lock_irqsave(&dev->vbi_queue_lock,
+						*lock_flags);
+				list_move_tail(&(*vbif)->frame,
+						&dev->vbi_outqueue);
+				if (!list_empty(&dev->vbi_inqueue))
+					(*vbif) = list_entry(
+							dev->vbi_inqueue.next,
+							struct em28xx_frame_t,
+							frame);
+				else
+					(*vbif) = NULL;
+				spin_unlock_irqrestore(&dev->vbi_queue_lock,
+						*lock_flags);
+			}
+			if ((*vbif)) {
+				do_gettimeofday(&(*vbif)->buf.timestamp);
+				(*vbif)->buf.sequence = ++dev->vbi_frame_count;
+				(*vbif)->buf.field = V4L2_FIELD_INTERLACED;
+				(*vbif)->state = F_GRABBING;
+				(*vbif)->buf.bytesused = 0;
+				(*vbif)->top_field = 1;
+				(*vbif)->fieldbytesused = 0;
+			}
+		}
+		if ((*f)) {
+			if ((*f)->state == F_GRABBING) {
+				/*previous frame is incomplete */
+				if ((*f)->fieldbytesused < dev->field_size) {
+					(*f)->state = F_ERROR;
+				} else {
+					(*f)->state = F_DONE;
+					(*f)->buf.bytesused = dev->frame_size;
+				}
+			}
+			if ((*f)->state == F_DONE || (*f)->state == F_ERROR) {
+				/* move current frame to outqueue and get next
+				   free buffer from inqueue */
+				spin_lock_irqsave(&dev->queue_lock, *lock_flags);
+				list_move_tail(&(*f)->frame, &dev->outqueue);
+				if (!list_empty(&dev->inqueue)) {
+					(*f) = list_entry(dev->inqueue.next,
+							struct em28xx_frame_t,
+							frame);
+				} else {
+					(*f) = NULL;
+				}
+				spin_unlock_irqrestore(&dev->queue_lock,
+						*lock_flags);
+			}
+			if ((*f)) {
+				do_gettimeofday(&(*f)->buf.timestamp);
+				(*f)->buf.sequence = ++dev->frame_count;
+				(*f)->buf.field = V4L2_FIELD_INTERLACED;
+				(*f)->state = F_GRABBING;
+				(*f)->buf.bytesused = 0;
+				(*f)->top_field = 1;
+				(*f)->fieldbytesused = 0;
+			}
+		}
+	} else {
+		if ((*vbif)) {
+			if ((*vbif)->state == F_QUEUED)
+				(*vbif)->top_field = 0;
+			else if ((*vbif)->state == F_GRABBING) {
+				if (!(*vbif)->top_field)
+					(*vbif)->state = F_ERROR;
+				else if ((*vbif)->fieldbytesused <
+						dev->vbi_field_size - 172)
+					(*vbif)->state = F_ERROR;
+				else {
+					(*vbif)->top_field = 0;
+					(*vbif)->fieldbytesused = 0;
+				}
+			}
+		}
+
+		if ((*f)) {
+			if ((*f)->state == F_GRABBING) {
+				if (!(*f)->top_field)
+					(*f)->state = F_ERROR;
+				else if ((*f)->fieldbytesused <
+						dev->field_size - 172)
+					(*f)->state = F_ERROR;
+				else {
+					(*f)->top_field = 0;
+					(*f)->fieldbytesused = 0;
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+static void em28xx_isoc_video_copy(struct em28xx *dev,
+					  struct em28xx_frame_t **f, unsigned char *buf, int len, int vbioffset)
+{
+	void *fieldstart, *startwrite, *startread;
+	int linesdone, currlinedone, offset, lencopy, remain;
+
+	if (dev->frame_size != (*f)->buf.length)
+		return;
+
+	if (vbioffset > 0) {
+		startread = buf+vbioffset;
+		len -= vbioffset;
+	} else {
+		startread = buf+4;
+		len -= 4;
+	}
+
+	if ((*f)->fieldbytesused + len > dev->field_size)
+		len = dev->field_size - (*f)->fieldbytesused;
+
+	remain = len;
+
+
+	if ((*f)->top_field)
+		fieldstart = (*f)->bufmem;
+	else
+		fieldstart = (*f)->bufmem + dev->bytesperline;
+
+	linesdone = (*f)->fieldbytesused / dev->bytesperline;
+	currlinedone = (*f)->fieldbytesused % dev->bytesperline;
+	offset = linesdone * dev->bytesperline * 2 + currlinedone;
+	startwrite = fieldstart + offset;
+	lencopy = dev->bytesperline - currlinedone;
+	lencopy = lencopy > remain ? remain : lencopy;
+
+	memcpy(startwrite, startread, lencopy);
+	remain -= lencopy;
+
+	while (remain > 0) {
+		startwrite += lencopy + dev->bytesperline;
+		startread += lencopy;
+		if (dev->bytesperline > remain)
+			lencopy = remain;
+		else
+			lencopy = dev->bytesperline;
+
+		memcpy(startwrite, startread, lencopy);
+		remain -= lencopy;
+	}
+
+	(*f)->fieldbytesused += len;
+}
+
+static int em28xx_isoc_vbi_copy(struct em28xx *dev,
+		struct em28xx_frame_t **vbif, unsigned char *buf, int len)
+{
+	void *fieldstart, *startwrite, *startread;
+	int linesdone, currlinedone, offset, lencopy, remain;
+
+	if (((*vbif) && dev->vbi_dropbytes == 0) &&
+		!((*vbif)->top_field == 0 && (*vbif)->state == F_QUEUED)) {
+		if ((*vbif)->fieldbytesused < dev->vbi_field_size) {
+			if ((*vbif)->fieldbytesused + (len-4) >
+					dev->vbi_field_size) {
+				len = dev->vbi_field_size -
+					(*vbif)->fieldbytesused;
+
+				startread = buf+4;
+				remain = len-4;
+
+				if ((*vbif)->top_field)
+					fieldstart = (*vbif)->bufmem;
+				else {
+					if (dev->vbi_interlaced)
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_bytesperline;
+					else
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_field_size;
+				}
+
+				linesdone = (*vbif)->fieldbytesused /
+					dev->vbi_bytesperline;
+				currlinedone = (*vbif)->fieldbytesused %
+					dev->vbi_bytesperline;
+
+				if (dev->vbi_interlaced)
+					offset = linesdone * dev->vbi_bytesperline *
+						2 + currlinedone;
+				else
+					offset = linesdone * dev->vbi_bytesperline +
+						currlinedone;
+
+				startwrite = fieldstart + offset;
+				lencopy = dev->vbi_bytesperline - currlinedone;
+				lencopy = lencopy > remain ? remain : lencopy;
+
+				memcpy(startwrite, startread, lencopy);
+				remain -= lencopy;
+
+				while (remain > 0) {
+					startwrite += lencopy;
+					if (dev->vbi_interlaced)
+						startwrite += dev->vbi_bytesperline;
+
+					startread += lencopy;
+					if (dev->vbi_bytesperline > remain)
+						lencopy = remain;
+					else
+						lencopy = dev->vbi_bytesperline;
+
+					memcpy(startwrite, startread, lencopy);
+					remain -= lencopy;
+				}
+				(*vbif)->fieldbytesused = dev->vbi_field_size;
+			} else {
+				if ((*vbif)->top_field)
+					fieldstart = (*vbif)->bufmem;
+				else {
+					if (dev->vbi_interlaced)
+						fieldstart = (*vbif)->bufmem + 
+							dev->vbi_bytesperline;
+					else 
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_field_size;
+				}
+
+				startread = buf+4;
+				remain = len-4;
+
+				linesdone = (*vbif)->fieldbytesused /
+					dev->vbi_bytesperline;
+				currlinedone = (*vbif)->fieldbytesused %
+					dev->vbi_bytesperline;
+				if (dev->vbi_interlaced)
+					offset = linesdone * dev->vbi_bytesperline *
+						2 + currlinedone;
+				else
+					offset = linesdone * dev->vbi_bytesperline +
+						currlinedone;
+
+				startwrite = fieldstart + offset;
+				lencopy = dev->vbi_bytesperline - currlinedone;
+				lencopy = lencopy > remain ? remain : lencopy;
+
+				memcpy(startwrite, startread, lencopy);
+				remain -= lencopy;
+
+				while (remain > 0) {
+					startwrite += lencopy;
+
+					if (dev->vbi_interlaced)
+						startwrite += dev->vbi_bytesperline;
+
+					startread += lencopy;
+					if (dev->vbi_bytesperline > remain)
+						lencopy = remain;
+					else
+						lencopy = dev->vbi_bytesperline;
+					memcpy(startwrite, startread, lencopy);
+					remain -= lencopy;
+				}
+				(*vbif)->fieldbytesused += (len-4);
+				len -= 4;
+			}
+		} else {
+			len = -4;
+		}
+	} else {
+		if (dev->vbi_dropbytes < dev->vbi_field_size) {
+			if (dev->vbi_dropbytes + (len-4) >
+					dev->vbi_field_size) {
+				len = dev->vbi_field_size - dev->vbi_dropbytes;
+				dev->vbi_dropbytes = dev->vbi_field_size;
+			} else {
+				dev->vbi_dropbytes += (len-4);
+				len -= 4;
+			}
+		} else {
+			len = -4;
+		}
+	}
+	return len;
+}
+
+/*
+ * em28xx_isoIrq()
+ * handles the incoming isoc urbs and fills the frames from our inqueue
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+void em28xx_isocIrq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_isocIrq(struct urb *urb)
+#endif
+{
+	struct em28xx *dev = urb->context;
+	int i, status;
+	struct em28xx_frame_t **f, **vbif;
+	unsigned long lock_flags;
+	int vbioffset = 0;
+
+	if (!dev)
+		return;
+#ifdef ENABLE_DEBUG_ISOC_FRAMES
+	if (isoc_debug > 1)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+		em28xx_isoc_dump(urb, regs);
+#else
+		em28xx_isoc_dump(urb);
+#endif
+#endif
+	if (urb->status == -ENOENT)
+		return;
+
+	f = &dev->frame_current;
+	vbif = &dev->vbi_frame_current;
+
+	if (dev->video_stream == STREAM_INTERRUPT) {
+		dev->video_stream = STREAM_OFF;
+		if ((*f))
+			(*f)->state = F_QUEUED;
+		(*f) = NULL;
+
+		em28xx_isocdbg("stream interrupted");
+		wake_up_interruptible(&dev->video_wait_stream);
+	}
+
+	if (dev->vbi_stream == STREAM_INTERRUPT) {
+		dev->vbi_stream = STREAM_OFF;
+		if ((*vbif)) {
+			if ((*vbif)->fieldbytesused)
+				dev->vbi_dropbytes = (*vbif)->fieldbytesused;
+
+			(*vbif)->state = F_QUEUED;
+		}
+		(*vbif) = NULL;
+		wake_up_interruptible(&dev->vbi_wait_stream);
+	}
+
+	if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))
+		return;
+
+	if (dev->video_stream == STREAM_ON || dev->vbi_stream == STREAM_ON) {
+		if (dev->video_stream == STREAM_ON &&
+			!list_empty(&dev->inqueue) &&
+			!(*f))
+
+			(*f) = list_entry(dev->inqueue.next,
+					struct em28xx_frame_t, frame);
+
+		if ((dev->dev_modes & EM28XX_VBI) &&
+			dev->vbi_stream == STREAM_ON &&
+			!list_empty(&dev->vbi_inqueue) &&
+			!(*vbif))
+
+			(*vbif) = list_entry(dev->vbi_inqueue.next,
+					struct em28xx_frame_t, frame);
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			unsigned char *buf = urb->transfer_buffer +
+					urb->iso_frame_desc[i].offset;
+			int len = urb->iso_frame_desc[i].actual_length;
+
+			if (urb->iso_frame_desc[i].status) {
+				em28xx_isocdbg("data error: [%d] len=%d, "
+						"status=%d", i,
+					urb->iso_frame_desc[i].actual_length,
+					urb->iso_frame_desc[i].status);
+
+				if (urb->iso_frame_desc[i].status != -EPROTO) {
+					em28xx_isocdbg("continue, len=%d\n",
+						urb->iso_frame_desc[i].actual_length);
+					continue;
+				}
+			}
+			if (urb->iso_frame_desc[i].actual_length <= 0) {
+				em28xx_isocdbg("packet %d is empty\n", i);
+				continue;
+			}
+
+			if (urb->iso_frame_desc[i].actual_length >
+						 dev->max_pkt_size) {
+				em28xx_isocdbg("packet bigger than packet "
+					       "size\n");
+				continue;
+			}
+#if 0
+			if (buf[0] != 0x88 && buf[1] != 0x88)
+				printk(KERN_DEBUG"headings: %02x %02x\n",
+						buf[0], buf[1]);
+#endif
+			if (buf[0] == 0x22 && buf[1] == 0x5a) {
+				em28xx_isocdbg("Video frame, length=%i!\n",
+						len);
+				dev->vbi_dropbytes = 0;
+				em28xx_isoc_video(dev, f, vbif, &lock_flags,
+						buf[2]);
+			} else if (buf[0] == 0x33 && buf[1] == 0x95) {
+				dev->vbi_dropbytes = 0;
+				em28xx_isoc_video(dev, f, vbif, &lock_flags,
+						buf[2]);
+			}
+
+			/* actual copying */
+			if (dev->dev_modes & EM28XX_VBI) {
+				vbioffset = em28xx_isoc_vbi_copy(dev, vbif,
+						buf, len) + 4;
+			} else
+				vbioffset = 0;
+
+			if ((*f) && (len - vbioffset > 0) &&
+					(*f)->state == F_GRABBING)
+				em28xx_isoc_video_copy(dev, f, buf, len,
+						vbioffset);
+		}
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+
+	urb->status = 0;
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status) {
+		em28xx_errdev("resubmit of urb failed (error=%i)\n", status);
+		dev->state |= DEV_MISCONFIGURED;
+	}
+	wake_up_interruptible(&dev->wait_frame);
+	if (dev->dev_modes & EM28XX_VBI)
+		wake_up_interruptible(&dev->wait_vbi_frame);
+}
+
+/*
+ * em28xx_uninit_isoc()
+ * deallocates the buffers and urbs allocated during em28xx_init_iosc()
+ */
+void em28xx_uninit_isoc(struct em28xx *dev)
+{
+	int i;
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		if (dev->urb[i]) {
+			usb_kill_urb(dev->urb[i]);
+			if (dev->transfer_buffer[i]) {
+				usb_buffer_free(dev->udev,
+					dev->urb[i]->transfer_buffer_length,
+					dev->transfer_buffer[i],
+					dev->urb[i]->transfer_dma);
+			}
+			usb_free_urb(dev->urb[i]);
+		}
+		dev->urb[i] = NULL;
+		dev->transfer_buffer[i] = NULL;
+	}
+	em28xx_capture_start(dev, 0);
+}
+
+/*
+ * em28xx_init_isoc()
+ * allocates transfer buffers and submits the urbs for isoc transfer
+ */
+int em28xx_init_isoc(struct em28xx *dev)
+{
+	/* change interface to 3 which allowes the biggest packet sizes */
+	int i, errCode;
+	const int sb_size = EM28XX_NUM_PACKETS * dev->max_pkt_size;
+
+	/* reset streaming vars */
+	dev->frame_current = NULL;
+	dev->frame_count = 0;
+
+	dev->vbi_frame_current = NULL;
+	dev->vbi_frame_count = 0;
+
+	/* allocate urbs */
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		struct urb *urb;
+		int j, k;
+		/* allocate transfer buffer */
+		urb = usb_alloc_urb(EM28XX_NUM_PACKETS, GFP_KERNEL);
+		if (!urb) {
+			em28xx_errdev("cannot alloc urb %i\n", i);
+			em28xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		dev->transfer_buffer[i] = usb_buffer_alloc(dev->udev,
+				sb_size, GFP_KERNEL, &urb->transfer_dma);
+		if (!dev->transfer_buffer[i]) {
+			em28xx_errdev("unable to allocate %i bytes for "
+				      "transfer buffer %i\n", sb_size, i);
+			em28xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		memset(dev->transfer_buffer[i], 0, sb_size);
+		urb->dev = dev->udev;
+		urb->context = dev;
+		urb->pipe = usb_rcvisocpipe(dev->udev, 0x82);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = 1;
+		urb->transfer_buffer = dev->transfer_buffer[i];
+		urb->complete = em28xx_isocIrq;
+		urb->number_of_packets = EM28XX_NUM_PACKETS;
+		urb->transfer_buffer_length = sb_size;
+		for (j = k = 0; j < EM28XX_NUM_PACKETS;
+				j++, k += dev->max_pkt_size) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+				dev->max_pkt_size;
+		}
+		dev->urb[i] = urb;
+	}
+
+	/* submit urbs */
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		errCode = usb_submit_urb(dev->urb[i], GFP_KERNEL);
+		if (errCode) {
+			em28xx_errdev("submit of urb %i failed (error=%i)\n", i,
+				      errCode);
+			em28xx_uninit_isoc(dev);
+			return errCode;
+		}
+	}
+
+	return 0;
+}
+
+int em28xx_set_alternate(struct em28xx *dev)
+{
+	int errCode, prev_alt = dev->alt;
+	dev->alt = alt;
+	if (dev->alt == 0) {
+		int i;
+#if 1 /* Always try to get the maximum size value */
+		for (i = 0; i < dev->num_alt; i++)
+			if (dev->alt_max_pkt_size[i] >
+					dev->alt_max_pkt_size[dev->alt])
+				dev->alt = i;
+#endif
+#if 0 /* Should be dependent of horizontal size */
+		if (dev->em_type == EM2800) {
+			/* always use the max packet size for em2800
+			   based devices */
+			for (i = 0; i < dev->num_alt; i++)
+				if (dev->alt_max_pkt_size[i] >
+					dev->alt_max_pkt_size[dev->alt])
+					dev->alt = i;
+		} else {
+			/* FIXME: empiric magic number */
+			unsigned int min_pkt_size = dev->field_size / 137;
+			em28xx_coredbg("minimum isoc packet size: %u",
+					min_pkt_size);
+			dev->alt = 7;
+			for (i = 0; i < dev->num_alt; i ++)
+				if (dev->alt_max_pkt_size[i] >= min_pkt_size) {
+					dev->alt = i;
+					break;
+				}
+		}
+#endif
+	}
+
+	if (dev->alt != prev_alt) {
+		dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt];
+		em28xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n", dev->alt,
+		       dev->max_pkt_size);
+
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, dev->alt);
+		if (errCode < 0) {
+			em28xx_errdev("cannot change alternate number to %d "
+					"(error=%i)\n",
+					dev->alt, errCode);
+			return errCode;
+		}
+	}
+	return 0;
+}
diff --git a/drivers/media/video/empia/em28xx-i2c.c b/drivers/media/video/empia/em28xx-i2c.c
new file mode 100644
index 0000000..1278e6e
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-i2c.c
@@ -0,0 +1,983 @@
+/*
+   em28xx-i2c.c - driver for Empia EM2800/EM2820/2840/2880 USB
+   video capture devices
+
+   Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+		      Markus Rechberger <mrechberger@gmail.com>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+		      Sascha Sommer <saschasommer@freenet.de>
+
+   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/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+
+#include "em28xx.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include "dvb_frontend.h"
+
+#include "xc5000/xc5000_control.h"
+
+
+/* ----------------------------------------------------------- */
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+static unsigned int i2c_debug = 1;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define dprintk1(lvl, fmt, args...) if (i2c_debug >= lvl) do {\
+			printk(fmt, ##args); } while (0)
+#define dprintk2(lvl, fmt, args...) if (i2c_debug >= lvl) do { \
+			printk(KERN_DEBUG "%s at %s: " fmt, \
+			dev->name, __func__, ##args); } while (0)
+
+/*
+ * em2800_i2c_send_max4()
+ * send up to 4 bytes to the i2c device
+ */
+static int em2800_i2c_send_max4(struct em28xx *dev, unsigned char addr,
+				char *buf, int len)
+{
+	int ret;
+	int write_timeout;
+	unsigned char b2[6];
+	BUG_ON(len < 1 || len > 4);
+	b2[5] = 0x80 + len - 1;
+	b2[4] = addr;
+	b2[3] = buf[0];
+	if (len > 1)
+		b2[2] = buf[1];
+	if (len > 2)
+		b2[1] = buf[2];
+	if (len > 3)
+		b2[0] = buf[3];
+
+	ret = dev->em28xx_write_regs(dev, 4 - len, &b2[4 - len], 2 + len);
+	if (ret < 0) {
+		dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs() failed,"
+			"ret = %i\n", __FILE__, __LINE__, __func__, ret);
+		return ret;
+	}
+	if (ret != 2 + len) {
+		em28xx_warn("writing to i2c device failed (error=%i)\n", ret);
+		return -EIO;
+	}
+	for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0;
+	     write_timeout -= 5) {
+		ret = dev->em28xx_read_reg(dev, 0x05);
+		if (ret < 0) {
+			dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg()"
+				"failed, ret = %i\n", __FILE__, __LINE__,
+				__func__, ret);
+			return ret;
+		}
+		if (ret == 0x80 + len - 1)
+			return len;
+		msleep(5);
+	}
+	em28xx_warn("i2c write timed out\n");
+	return -EIO;
+}
+
+int em28xx_gpio_cmd(struct em28xx *dev, unsigned int command, u16 *value,
+		unsigned int *len)
+{
+	struct em28xx_gpio *gpio_map = &em28xx_boards[dev->model].gpio_regs;
+	switch (command) {
+	case EM28XX_ANALOG_ON:
+		*value = gpio_map->a_on;
+		break;
+	case EM28XX_LED1_ON:
+		*value = gpio_map->l1_on;
+		break;
+	case EM28XX_XC3028_SECAM:
+		*value = gpio_map->xc3028_sec;
+		break;
+	case EM28XX_TS1_ON:
+		*value = gpio_map->ts1_on;
+		break;
+	case EM28XX_MODESWITCH:
+		*value = gpio_map->m_switch;
+		break;
+	case EM28XX_DECODER_SLEEP:
+		*value = gpio_map->d_sleep;
+		break;
+	case EM28XX_LED2_ON:
+		*value = gpio_map->l2_on;
+		break;
+	case EM28XX_RF:
+		*value = gpio_map->rf;
+		break;
+	case EM28XX_DVB1_ON:
+		*value = gpio_map->dvbs_lnb;
+		break;
+	case EM28XX_DVB2_ON:
+		*value = gpio_map->dvbs_v;
+		break;
+	case EM28XX_TUNER1_ON:
+		*value = gpio_map->t1_on;
+		break;
+	case EM28XX_DEMOD1_RESET:
+		*value = gpio_map->d1_reset;
+		break;
+	case EM28XX_TUNER1_RESET:
+		*value = gpio_map->t1_reset;
+		break;
+	case EM28XX_DECODER_RESET:
+		*value = gpio_map->dc_reset;
+		break;
+	case EM28XX_DEMOD2_RESET:
+		*value = gpio_map->d2_reset;
+		break;
+	case EM28XX_TUNER2_RESET:
+		*value = gpio_map->t2_reset;
+		break;
+	case EM28XX_TUNER2_ON:
+		*value = gpio_map->t2_on;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr)
+{
+	int cmd;
+	switch (command) {
+	case XC3028_CHIP_RESET:
+		cmd = EM28XX_TUNER1_RESET;
+		break;
+	case XC5000_CHIP_RESET:
+		cmd = EM28XX_TUNER1_RESET;
+		break;
+	default:
+		printk("unknown gpio translate command\n");
+		break;
+	}
+
+	return em28xx_gpio_control(priv, cmd, ptr);
+}
+
+int em28xx_gpio_control(void *priv, unsigned int command, void *ptr)
+{
+	struct em28xx *dev = (struct em28xx *)priv;
+	unsigned int len;
+	u16 gpio_value;
+	u8 buf[2];
+	u8 *index;
+	u8 gpio;
+	int *arg = ptr;
+	u16 gpio_reg;
+	u8 eeprom_offset;
+
+	if (em28xx_boards[dev->model].manual_gpio)
+		em28xx_gpio_cmd(dev, command, &gpio_value, &len);
+	else {
+		switch (dev->em_type) {
+		case EM2888:
+		case EM2889:
+		case EM2875:
+			eeprom_offset = 0xa0;
+			buf[0] = 0;
+			index = &buf[1];
+			len = 2;
+			dev->em28xx_write_regs(dev, 0x06, "\x40", 1);
+			break;
+		case EM2883:
+		default:
+			eeprom_offset = 0x3c;
+			index = &buf[0];
+			len = 1;
+			break;
+		}
+
+		switch (command) {
+		case EM28XX_ANALOG_ON:
+			*index = eeprom_offset + 24;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TS1_ON:
+			*index = eeprom_offset + 25;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_DECODER_SLEEP:
+			*index = eeprom_offset + 20;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_DEMOD1_RESET:
+			*index = eeprom_offset + 16;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TUNER1_RESET:
+			*index = eeprom_offset + 17;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TUNER1_ON:
+			*index = eeprom_offset + 23;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_LED1_ON:
+			printk("reading led\n");
+			*index = eeprom_offset + 22;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			printk("led returned: %02x\n", gpio_value);
+			break;
+		default:
+			switch (dev->em_type) {
+			case EM2888:
+			case EM2889:
+			case EM2875:
+				dev->em28xx_write_regs(dev, 0x06, "\x45", 1);
+				break;
+			default:
+				break;
+			}
+			return -EINVAL;
+		}
+		switch (dev->em_type) {
+		case EM2888:
+		case EM2875:
+		case EM2889:
+			dev->em28xx_write_regs(dev, 0x06, "\x45", 1);
+			break;
+		case EM2883:
+		default:
+			eeprom_offset = 0x3b;
+			index = &buf[0];
+			len = 1;
+			break;
+		}
+	}
+
+	/* check if gpio register enabled */
+	if (gpio_value & 0x80) {
+		if (((gpio_value >> 6)&1) == 0) {
+			if (arg == NULL) {
+				printk(KERN_INFO"no argument given %02x\n",
+						gpio_value);
+				return -EINVAL;
+			}
+			/* on/off command */
+			switch (dev->em_type) {
+			case EM2888:
+			case EM2889:
+			case EM2875:
+				gpio_reg = 0x80;
+				break;
+			case EM2883:
+			default:
+				gpio_reg = gpio_value&0x10 ? 0x04 : 0x08;
+				break;
+			}
+			gpio = dev->em28xx_read_reg(dev, gpio_reg);
+			gpio &= ~((u8)(1 << (gpio_value&0x7)));
+
+			if (*arg == EM28XX_REG_ON)
+				gpio |= ((gpio_value >> 5)&1) <<
+					(gpio_value&7);
+			else
+				gpio |= (((gpio_value >> 5)&1)^1) <<
+					(gpio_value&7);
+
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			dprintk1(2, "(1) writing to %02x -> %02x\n", gpio_reg, gpio);
+		} else {
+			switch (dev->em_type) {
+			case EM2889:
+			case EM2888:
+			case EM2875:
+				gpio_reg = 0x80;
+				break;
+			case EM2883:
+			default:
+				gpio_reg = gpio_value&0x10 ? 0x04 : 0x08;
+				break;
+			}
+			gpio = dev->em28xx_read_reg(dev, gpio_reg);
+			gpio &= ~((u8)(1 << (gpio_value&0xf)));
+
+			gpio |= ((gpio_value >> 5)&1) << (gpio_value&7);
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			dprintk1(2, "(2) writing to %02x -> %02x\n", gpio_reg, gpio);
+			gpio &= ~((u8)(1 << (gpio_value&0xf)));
+			gpio |= (((gpio_value >> 5)&1)^1) << (gpio_value&7);
+			mdelay(100);
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			mdelay(100);
+			dprintk1(2, "(3) writing to %02x -> %02x\n", gpio_reg, gpio);
+		}
+	} else {
+		printk("register disabled\n");
+	}
+
+	return 0;
+}
+
+static int em2800_i2c_send_bytes(void *data, unsigned char addr, char *buf,
+				 short len)
+{
+	char *bufPtr = buf;
+	int ret;
+	int wrcount = 0;
+	int count;
+	int maxLen = 4;
+	struct em28xx *dev = (struct em28xx *)data;
+	while (len > 0) {
+		count = (len > maxLen) ? maxLen : len;
+		ret = em2800_i2c_send_max4(dev, addr, bufPtr, count);
+		if (ret > 0) {
+			len -= count;
+			bufPtr += count;
+			wrcount += count;
+		} else
+			return (ret < 0) ? ret : -EFAULT;
+	}
+	return wrcount;
+}
+
+/*
+ * em2800_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr)
+{
+	char msg;
+	int ret;
+	int write_timeout;
+	msg = addr;
+	ret = dev->em28xx_write_regs(dev, 0x04, &msg, 1);
+	if (ret < 0) {
+		em28xx_warn("setting i2c device address failed (error=%i)\n",
+			    ret);
+		return ret;
+	}
+	msg = 0x84;
+	ret = dev->em28xx_write_regs(dev, 0x05, &msg, 1);
+	if (ret < 0) {
+		em28xx_warn("preparing i2c read failed (error=%i)\n", ret);
+		return ret;
+	}
+	for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0;
+	     write_timeout -= 5) {
+		unsigned int msg;
+		ret = dev->em28xx_read_reg(dev, 0x5);
+		if (ret < 0) {
+			dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg() "
+				"failed, gpval = %i\n", __FILE__, __LINE__,
+				__func__, ret);
+			return ret;
+		}
+		msg = ret;
+		if (msg == 0x94)
+			return -ENODEV;
+		else if (msg == 0x84)
+			return 0;
+		msleep(5);
+	}
+	return -ENODEV;
+}
+
+/*
+ * em2800_i2c_recv_bytes()
+ * read from the i2c device
+ */
+static int em2800_i2c_recv_bytes(struct em28xx *dev, unsigned char addr,
+				 char *buf, int len)
+{
+	int ret;
+	/* check for the device and set i2c read address */
+	ret = em2800_i2c_check_for_device(dev, addr);
+	if (ret) {
+		em28xx_warn
+		    ("preparing read at i2c address 0x%x failed (error=%i)\n",
+		     addr, ret);
+		return ret;
+	}
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x3, buf, len);
+	if (ret < 0) {
+		em28xx_warn("reading from i2c device at 0x%x failed (error=%i)",
+			    addr, ret);
+		return ret;
+	}
+	return ret;
+}
+
+/*
+ * em28xx_i2c_send_bytes()
+ * untested for more than 4 bytes
+ */
+static int em28xx_i2c_send_bytes(void *data, unsigned char addr, char *buf,
+				 short len, int stop)
+{
+	int wrcount = 0;
+	struct em28xx *dev = (struct em28xx *)data;
+	int i;
+
+	wrcount = dev->em28xx_write_regs_req(dev, stop ? 2 : 3, addr, buf, len);
+	if (wrcount < 0) {
+		dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs_req()"
+			"failed, wrcount = %i\n", __FILE__, __LINE__,
+			__func__, wrcount);
+
+		return wrcount;
+	}
+
+	if (dev->em28xx_read_reg(dev, 0x5) != 0) {
+		printk(KERN_ERR"FIXME:em28xx_i2c_send_bytes(%02x): "
+				"write failed:\n", addr);
+		printk(KERN_ERR"===============================\n");
+		for (i = 0; i < len; i++)
+			printk("%02x ", (unsigned char)buf[i]);
+
+		printk("\n");
+		printk(KERN_ERR"================================\n");
+	}
+
+	return wrcount;
+}
+
+/*
+ * em28xx_i2c_recv_bytes()
+ * read a byte from the i2c device
+ */
+static int em28xx_i2c_recv_bytes(struct em28xx *dev, unsigned char addr,
+				 char *buf, int len)
+{
+	int ret;
+	ret = dev->em28xx_read_reg_req_len(dev, 2, addr, buf, len);
+	if (ret < 0) {
+		em28xx_warn("reading i2c device failed (error=%i)\n", ret);
+		return ret;
+	}
+	if (dev->em28xx_read_reg(dev, 0x5) != 0)
+		return -ENODEV;
+	return ret;
+}
+
+/*
+ * em28xx_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+static int em28xx_i2c_check_for_device(struct em28xx *dev, unsigned char addr)
+{
+	char msg;
+	int ret;
+	msg = addr;
+
+	ret = dev->em28xx_read_reg_req(dev, 2, addr);
+	if (ret < 0) {
+		em28xx_warn("reading from i2c device failed (error=%i)\n", ret);
+		return ret;
+	}
+	if (dev->em28xx_read_reg(dev, 0x5) != 0)
+		return -ENODEV;
+	return 0;
+}
+
+/*
+ * em28xx_i2c_xfer()
+ * the main i2c transfer function
+ */
+static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap,
+			   struct i2c_msg msgs[], int num)
+{
+	struct em28xx *dev = i2c_adap->algo_data;
+	int addr, rc, i, byte;
+
+	if (num <= 0)
+		return 0;
+	for (i = 0; i < num; i++) {
+		addr = msgs[i].addr << 1;
+		dprintk2(2, "%s %s addr=%x len=%d:",
+			 (msgs[i].flags & I2C_M_RD) ? "read" : "write",
+			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);
+		/* no len: check only for device presence */
+		if (!msgs[i].len) {
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_check_for_device(dev, addr);
+			else
+				rc = em28xx_i2c_check_for_device(dev, addr);
+			if (rc < 0) {
+				dprintk2(2, " no device\n");
+				return rc;
+			}
+
+		} else if (msgs[i].flags & I2C_M_RD) {
+			/* read bytes */
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_recv_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			else
+				rc = em28xx_i2c_recv_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_INFO" %02x",
+							msgs[i].buf[byte]);
+			}
+		} else {
+			/* write bytes */
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(" %02x", msgs[i].buf[byte]);
+			}
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_send_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			else
+				rc = em28xx_i2c_send_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len,
+							   i == num - 1);
+		}
+		if (rc < 0)
+			goto err;
+		if (i2c_debug >= 2)
+			printk("\n");
+	}
+
+	return num;
+err:
+	dprintk2(2, " ERROR: %i\n", rc);
+	return rc;
+}
+
+static int em28xx_i2c_eeprom(struct em28xx *dev, struct i2c_client *client,
+		unsigned char *eedata, int len)
+{
+	unsigned char buf, *p = eedata;
+	struct em28xx_eeprom *em_eeprom = (void *)eedata;
+	int i, err, size = len, block;
+
+	dev->i2c_client.addr = 0xa0 >> 1;
+
+	/* Check if board has eeprom */
+	err = i2c_master_recv(client, &buf, 0);
+	if (err < 0)
+		return -1;
+
+	buf = 0;
+
+	err = i2c_master_send(client, &buf, 1);
+	if (1 != err) {
+		printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n",
+		       dev->name, err);
+		return -1;
+	}
+
+	while (size > 0) {
+		if (size > 16)
+			block = 16;
+		else
+			block = size;
+
+		if (block !=
+		    (err = i2c_master_recv(client, p, block))) {
+			printk(KERN_WARNING
+			       "%s: i2c eeprom read error (err=%d)\n",
+			       dev->name, err);
+			return -1;
+		}
+		size -= block;
+		p += block;
+	}
+	for (i = 0; i < len; i++) {
+		if (0 == (i % 16))
+			printk(KERN_INFO "%s: i2c eeprom %02x:",
+					dev->name, i);
+		printk(" %02x", eedata[i]);
+
+		if (15 == (i % 16))
+			printk("\n");
+	}
+
+	printk(KERN_INFO "EEPROM ID= 0x%08x\n", em_eeprom->id);
+	printk(KERN_INFO "Vendor/Product ID= %04x:%04x\n",
+			em_eeprom->vendor_ID, em_eeprom->product_ID);
+
+	switch (em_eeprom->chip_conf >> 4 & 0x3) {
+	case 0:
+		printk(KERN_INFO "No audio on board.\n");
+		break;
+	case 1:
+		printk(KERN_INFO "AC97 audio (5 sample rates)\n");
+		break;
+	case 2:
+		printk(KERN_INFO "I2S audio, sample rate=32k\n");
+		break;
+	case 3:
+		printk(KERN_INFO "I2S audio, 3 sample rates\n");
+		break;
+	}
+
+	if (em_eeprom->chip_conf & 1 << 3)
+		printk(KERN_INFO "USB Remote wakeup capable\n");
+
+	if (em_eeprom->chip_conf & 1 << 2)
+		printk(KERN_INFO "USB Self power capable\n");
+
+	switch (em_eeprom->chip_conf & 0x3) {
+	case 0:
+		printk(KERN_INFO "500mA max power\n");
+		break;
+	case 1:
+		printk(KERN_INFO "400mA max power\n");
+		break;
+	case 2:
+		printk(KERN_INFO "300mA max power\n");
+		break;
+	case 3:
+		printk(KERN_INFO "200mA max power\n");
+		break;
+	}
+	printk(KERN_INFO "Table at 0x%02x, strings = 0x%04x, 0x%04x, 0x%04x\n",
+				em_eeprom->string_idx_table,
+				em_eeprom->string1,
+				em_eeprom->string2,
+				em_eeprom->string3);
+
+	return 0;
+}
+
+/* ----------------------------------------------------------- */
+
+/*
+ * functionality()
+ */
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL;
+}
+
+#ifndef I2C_PEC
+static void inc_use(struct i2c_adapter *adap)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+	MOD_INC_USE_COUNT;
+#endif
+}
+
+static void dec_use(struct i2c_adapter *adap)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+	MOD_DEC_USE_COUNT;
+#endif
+}
+#endif
+
+static int em28xx_set_tuner(int check_eeprom, struct i2c_client *client)
+{
+	struct em28xx *dev = client->adapter->algo_data;
+	struct tuner_setup tun_setup;
+
+	if (dev->has_inttuner == 0 && dev->dev_modes != EM28XX_DVBT) {
+		/* do not set up a tuner if it's a dvb only device */
+		tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+		tun_setup.type = dev->tuner_type;
+		tun_setup.addr = dev->tuner_addr;
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+	} else {
+		tun_setup.mode_mask = T_UNINITIALIZED;
+		tun_setup.type = TUNER_ABSENT;
+		tun_setup.addr = dev->tuner_addr;
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+	}
+
+	return 0;
+}
+
+/*
+ * attach_inform()
+ * gets called when a device attaches to the i2c bus
+ * does some basic configuration
+ */
+static int attach_inform(struct i2c_client *client)
+{
+	struct em28xx *dev = client->adapter->algo_data;
+	int ret;
+
+	if (client->driver->id == I2C_DRIVERID_TUNER && dev->has_inttuner==1) {
+		printk(KERN_INFO"em28xx-i2c: using internal tuner, denying "
+			"request to i2c tuner module.\n");
+		em28xx_set_tuner(-1, client);
+		return 0;
+	}
+
+	switch (client->addr << 1) {
+	case 0x86:
+	case 0x84:
+	case 0x96:
+	case 0x94:
+	{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
+		struct v4l2_priv_tun_config tda9887_cfg;
+#endif
+
+		struct tuner_setup tun_setup;
+
+		tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+		tun_setup.type = TUNER_TDA9887;
+		tun_setup.addr = client->addr;
+
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
+		tda9887_cfg.tuner = TUNER_TDA9887;
+		tda9887_cfg.priv = &dev->tda9887_conf;
+		em28xx_i2c_call_clients(dev, TUNER_SET_CONFIG, &tda9887_cfg);
+#endif
+#endif
+		break;
+	}
+	case 0x42:
+		dprintk1(1, "attach_inform: saa7114 detected.\n");
+		break;
+	case 0x4a:
+		dprintk1(1, "attach_inform: saa7113 detected.\n");
+		break;
+	case 0xa0:
+		dprintk1(1, "attach_inform: eeprom detected.\n");
+		em28xx_i2c_eeprom(dev, client, dev->eedata,
+				sizeof(dev->eedata));
+
+		switch (dev->model) {
+		case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+		case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+		case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+		case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+		case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+		case EM2883_BOARD_KWORLD_HYBRID_A316:
+		case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+		{
+#ifdef EM28XX_TVEEPROM
+			struct tveeprom tv;
+			tveeprom_hauppauge_analog(client, &tv, dev->eedata);
+
+			if (dev->tuner_type == TUNER_ABSENT) {
+				dev->tuner_type = tv.tuner_type;
+				printk(KERN_INFO"setting new tuner type now"
+						"%d!\n", tv.tuner_type);
+				em28xx_set_tuner(-1, client);
+			}
+#if defined V4L2_IDENT_MSPX4XX     /*  XXX  2.6.27  */
+			if (tv.audio_processor == V4L2_IDENT_MSPX4XX) {
+#else
+			if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
+#endif
+				dev->i2s_speed = 2048000;
+				dev->has_msp34xx = 1;
+			} else
+				dev->has_msp34xx = 0;
+#endif
+
+			if (dev->has_msp34xx) {
+				/* Send a reset to other chips via gpio */
+				ret = em28xx_write_regs_req(dev, 0x00, 0x08,
+						"\xf7", 1);
+				if (ret < 0) {
+					dprintk1(2, "%s:%u:%s(): FIXME: "
+						"em28xx_write_regs_req() "
+						"failed, ret = %i\n", __FILE__,
+						__LINE__, __func__, ret);
+					return ret;
+				}
+				udelay(2500);
+				ret = em28xx_write_regs_req(dev, 0x00, 0x08,
+						"\xff", 1);
+				if (ret < 0) {
+					dprintk1(2, "%s:%u:%s(): FIXME: "
+						"em28xx_write_regs_req() "
+						"failed, ret = %i\n", __FILE__,
+						__LINE__, __func__, ret);
+					return ret;
+				}
+				udelay(2500);
+			}
+		}
+		break;
+	}
+	case 0x60:
+	case 0x8e:
+	{
+		struct IR_i2c *ir = i2c_get_clientdata(client);
+		em28xx_set_ir(dev, ir);
+		break;
+	}
+	case 0x80:
+	case 0x88:
+		dprintk1(1, "attach_inform: msp34xx/cx25843 detected.\n");
+		break;
+	case 0xb8:
+	case 0xba:
+		dprintk1(1, "attach_inform: tvp5150 detected.\n");
+		break;
+	case 0x1e:
+		dprintk1(1, "zl10353 demodulator found!\n");
+		break;
+	default:
+		dprintk1(1, "attach inform (default): "
+			"detected I2C address %x\n", client->addr << 1);
+		dev->tuner_addr = client->addr;
+		em28xx_set_tuner(-1, client);
+	}
+	return 0;
+}
+
+static struct i2c_algorithm em28xx_algo = {
+	.master_xfer   = em28xx_i2c_xfer,
+	.functionality = functionality,
+};
+
+static struct i2c_adapter em28xx_adap_template = {
+#ifdef I2C_PEC
+	.owner = THIS_MODULE,
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
+	.inc_use = inc_use,
+	.dec_use = dec_use,
+#endif
+#endif
+#ifdef I2C_CLASS_TV_ANALOG
+	.class = I2C_CLASS_TV_ANALOG,
+#endif
+	.name = "em28xx",
+	.id = I2C_HW_B_EM28XX,
+	.algo = &em28xx_algo,
+	.client_register = attach_inform,
+};
+
+static struct i2c_client em28xx_client_template = {
+	.name = "em28xx internal",
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 15)
+	.flags = I2C_CLIENT_ALLOW_USE,
+#endif
+};
+
+/* ----------------------------------------------------------- */
+
+/*
+ * i2c_devs
+ * incomplete list of known devices
+ */
+static char *i2c_devs[128] = {
+	[0x4a >> 1] = "saa7113h",
+	[0x60 >> 1] = "remote IR sensor",
+	[0x8e >> 1] = "remote IR sensor",
+	[0x86 >> 1] = "tda9887",
+	[0x80 >> 1] = "msp34xx",
+	[0x88 >> 1] = "msp34xx/cx25843",
+	[0xa0 >> 1] = "eeprom",
+	[0xb8 >> 1] = "tvp5150a",
+	[0xba >> 1] = "tvp5150a",
+	[0xc0 >> 1] = "tuner (analog)",
+	[0xc2 >> 1] = "tuner (analog)",
+	[0xc4 >> 1] = "tuner (analog)",
+	[0xc6 >> 1] = "tuner (analog)",
+	[0x1e >> 1] = "zl10353/mt352 dvb-t demodulator",
+};
+
+/*
+ * do_i2c_scan()
+ * check i2c address range for devices
+ */
+static void do_i2c_scan(char *name, struct i2c_client *c)
+{
+	unsigned char buf;
+	int i, rc;
+
+	for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) {
+		c->addr = i;
+		rc = i2c_master_recv(c, &buf, 0);
+		if (rc < 0)
+			continue;
+		printk(KERN_INFO "%s: found i2c device @ 0x%x [%s]\n", name,
+		       i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+}
+
+/*
+ * em28xx_i2c_call_clients()
+ * send commands to all attached i2c devices
+ */
+void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg)
+{
+	BUG_ON(NULL == dev->i2c_adap.algo_data);
+	i2c_clients_command(&dev->i2c_adap, cmd, arg);
+}
+EXPORT_SYMBOL(em28xx_i2c_call_clients);
+
+/*
+ * em28xx_i2c_register()
+ * register i2c bus
+ */
+int em28xx_i2c_register(struct em28xx *dev)
+{
+	BUG_ON(!dev->em28xx_write_regs || !dev->em28xx_read_reg);
+	BUG_ON(!dev->em28xx_write_regs_req || !dev->em28xx_read_reg_req);
+	dev->i2c_adap = em28xx_adap_template;
+	dev->i2c_adap.dev.parent = &dev->udev->dev;
+	strcpy(dev->i2c_adap.name, dev->name);
+	dev->i2c_adap.algo_data = dev;
+	i2c_add_adapter(&dev->i2c_adap);
+
+	dev->i2c_client = em28xx_client_template;
+	dev->i2c_client.adapter = &dev->i2c_adap;
+
+	if (i2c_scan || dev->dev_modes == 0)
+		do_i2c_scan(dev->name, &dev->i2c_client);
+	return 0;
+}
+
+/*
+ * em28xx_i2c_unregister()
+ * unregister i2c_bus
+ */
+int em28xx_i2c_unregister(struct em28xx *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}
+
diff --git a/drivers/media/video/empia/em28xx-input.c b/drivers/media/video/empia/em28xx-input.c
new file mode 100644
index 0000000..a0fcba4
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-input.c
@@ -0,0 +1,536 @@
+/*
+  handle em28xx IR remotes via linux kernel input layer.
+
+   Copyright (C) 2005-2007 Markus Rechberger <mrechberger@gmail.com>
+		 2005      Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005      Sascha Sommer <saschasommer@freenet.de>
+
+
+  There have been many issues with this input handling routines, starting
+  from crashing the box, printing random characters or blocking the
+  keyboard when loading it etc. (those issues were caused because it used
+  the global runqueue with the asynchronous module mechanism).
+
+  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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#include "em28xx.h"
+#include "em28xx-keymaps.h"
+
+static unsigned int disable_ir;
+module_param(disable_ir, int, 0444);
+MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+
+static unsigned int ir_debug;
+module_param(ir_debug, int, 0644);
+MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]");
+
+#define dprintk(fmt, arg...)	if (ir_debug) \
+	printk(KERN_DEBUG"%s/ir: " fmt, ir->c.name , ## arg)
+
+/* ----------------------------------------------------------------------- */
+
+static unsigned int get_timestamp(void)
+{
+	struct timeval tm;
+	do_gettimeofday(&tm);
+	return (unsigned int)(tm.tv_sec*1000 + (tm.tv_usec /1000));
+}
+
+int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	struct em2880_ir *ir = dev->ir_em2880;
+	u8 buf[5];
+	static u8 dropkey;
+	static u8 repeatdelay;
+	int retval = 0;
+	unsigned int timeoutval;
+
+	/* this algorithm works best with 3-5 ms polling
+	   10ms in userspace */
+
+	msleep(3);
+
+
+	dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5);
+
+	timeoutval = get_timestamp();
+
+	if ((buf[0] != 0x11 || ir->btn != buf[1]) && dev->board->ir_keytab[buf[4]] != 0) {
+		if ((ir->btn != buf[1] && ir->released == 1) || ir->key != buf[4]) {
+
+#if 0
+			if (ir->key != buffer[4] && ir->released == 0) 
+				printk("old key got released\n");
+#endif
+
+			*ir_key = buf[4];
+			ir->released = 0;
+			ir->oldval = timeoutval;
+			ir->key = buf[4];
+			retval = 1;
+			
+			/* volume should be repeated faster than other keys */
+
+			if (ir->key<IR_KEYTAB_SIZE && (
+					dev->board->ir_keytab[ir->key] == KEY_VOLUMEUP ||
+					dev->board->ir_keytab[ir->key] == KEY_VOLUMEDOWN)) {
+				dropkey = 1; 
+				repeatdelay = 10;
+			} else {
+				dropkey = 3; 
+				repeatdelay = 100;
+			}
+		}
+
+		if (timeoutval - ir->oldval > repeatdelay && ir->released == 0) {
+			ir->oldval = timeoutval;
+			*ir_key = ir->key;
+			if (dropkey) {
+				retval = 0;
+				dropkey--;
+			} else
+				retval = 1;
+		}
+		ir->oldtimeoutval = 0;
+	} else {
+		if (timeoutval - ir->oldtimeoutval > 150 && ir->oldtimeoutval > 0 && ir->released == 0) {
+			ir->released = 1;
+			retval = 0;
+		}
+		if (ir->oldtimeoutval == 0)
+			ir->oldtimeoutval = timeoutval;
+		retval = 0;
+	}
+	ir->btn = buf[1];
+	return retval;
+}
+
+/* get_key for terratec's devices */
+
+int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	int rc;
+	int irc = 0;
+
+	msleep(50);
+
+	rc = em28xx_read_reg_req(dev, 0x0, 0x45);
+	if (rc < 0)
+		return -1;
+	else
+		irc = rc;
+
+	rc = em28xx_read_reg_req(dev, 0x0, 0x47);
+	if (rc < 0)
+		return -1;
+	else
+		*ir_key = rc;
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1];
+	dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2];
+	dev->ir_em2880->sequence[2] = irc;
+	if (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1] &&
+	    dev->ir_em2880->sequence[1] != dev->ir_em2880->sequence[2])
+		return 1;
+	else
+		return 0;
+}
+
+/* get_key for pinnacle's devices */
+/* TODO: this is just a fast implementation ... */
+
+int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	char buf[4];
+	int ret;
+
+	msleep(50);
+
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4);
+
+	if (ret < 0) {
+		switch (ret) {
+		case -ENODEV:
+			/* Device was disconnected */
+			em28xx_warn("reading key failed "
+				    "(error=%i=-ENODEV)\n", ret);
+			return -1;
+		default:
+			em28xx_warn("reading key failed (error=%i)\n", ret);
+			return -1;
+		}
+	}
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1];
+	dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2];
+	dev->ir_em2880->sequence[2] = buf[0]&1;
+	*ir_key = buf[2];
+	if ((dev->ir_em2880->sequence[0] == 1 &&
+	     dev->ir_em2880->sequence[1] == 0 &&
+	     dev->ir_em2880->sequence[2] == 1) ||
+	    (dev->ir_em2880->sequence[0] == 0 &&
+	     dev->ir_em2880->sequence[1] == 1 &&
+	     dev->ir_em2880->sequence[2] == 0))
+		return 1;
+	else
+		return 0;
+}
+
+static int em2880_ir_key_poll(struct em28xx *dev)
+{
+	struct em2880_ir *ir = dev->ir_em2880;
+	static u32 ir_key, keystatus;
+	u32 keycode;
+	static u32 old_keycode;
+	int rc;
+	rc = ir->get_key(dev, &ir_key, &keystatus);
+	switch (rc) {
+	case 0:
+		if (keystatus == 1) {
+			if (old_keycode == 0)
+				return -EINVAL;
+			input_report_key(ir->input, old_keycode, 0);
+			keystatus = 0;
+		}
+		break;
+	case -1:
+		keystatus = 0;
+		return rc;
+	default:
+		/* keydown */
+		keycode = IR_KEYCODE(ir->keymap, ir_key);
+		if (keycode == 0)
+			return -EINVAL;
+		input_report_key(ir->input, keycode, 1);
+		old_keycode = keycode;
+		keystatus = 1;
+	}
+	return 0;
+
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+static void em2880_ir_timer(unsigned long data)
+{
+      struct em2880_ir *ir = ((struct em28xx *)data)->ir_em2880;
+      schedule_work(&ir->work);
+}
+#endif
+
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+static void em28xx_ir_work(void *data)
+{
+	struct em28xx *dev = (struct em28xx *)data;
+	struct em2880_ir *ir = dev->ir_em2880;
+	int rc;
+#else
+static void em28xx_ir_work(struct work_struct *work)
+{
+	struct em2880_ir *ir = container_of(work, struct em2880_ir, work.work);
+	struct em28xx *dev = ir->dev;
+	int rc;
+#endif
+	if (ir->state == EM28XX_REMOTE_POLLING) {
+		rc = em2880_ir_key_poll(dev);
+		if (rc != 0) {
+			ir->state = EM28XX_REMOTE_IDLE;
+			return;
+		}
+		mutex_lock(&ir->state_lock);
+		if (ir->state == EM28XX_REMOTE_POLLING)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+			mod_timer(&dev->ir_em2880->timer, jiffies+msecs_to_jiffies(50));
+#else
+			schedule_delayed_work(&dev->ir_em2880->work, msecs_to_jiffies(50));
+#endif
+		mutex_unlock(&ir->state_lock);
+	}
+}
+
+int em2880_ir_detach(struct em28xx *dev)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct em2880_ir *ir = dev->ir_em2880;
+
+	return 0;
+	mutex_lock(&dev->input_lock);
+	if (ir == NULL) {
+		printk(KERN_INFO"em28xx-input.c: ir==NULL, skipping"
+				"em2880_ir_detach()\n");
+		mutex_unlock(&dev->input_lock);
+		return 0;
+	}
+
+	mutex_lock(&ir->state_lock);
+	ir->state = EM28XX_REMOTE_INTERRUPT;
+	mutex_unlock(&ir->state_lock);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+	del_timer_sync(&ir->timer);
+#else
+	if (delayed_work_pending(&ir->work))
+		cancel_rearming_delayed_work(&ir->work);
+#endif
+
+	input_unregister_device(ir->input);
+	kfree(dev->ir_em2880);
+	dev->ir_em2880 = NULL;
+	printk(KERN_INFO"em28xx-input.c: remote control handler detached\n");
+	mutex_unlock(&dev->input_lock);
+#endif
+	return 0;
+}
+
+int em2880_ir_attach(struct em28xx *dev)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct em2880_ir *ir;
+	struct input_dev *input_dev;
+	char buf[5];
+	int i;
+
+	/* DISABLED this remote control support is broken by design 
+           upcoming support will be moved to userspace plus interrupt
+           triggering support has to be used here, there are alot problems
+           with polling at a high interval, where I already think the timer
+           API is still not bugfree */
+	return 0;
+
+	mutex_lock(&dev->input_lock);
+	if (dev->ir_em2880) {
+		mutex_unlock(&dev->input_lock);
+		printk(KERN_INFO"RC Handler already registered\n");
+		return 0;
+	}
+	dev->ir_em2880 = kzalloc(sizeof(struct em2880_ir), GFP_KERNEL);
+	ir = dev->ir_em2880;
+	ir->keymap = dev->board->ir_keytab;
+	ir->get_key = dev->board->ir_getkey;
+	ir->dev = dev;
+	mutex_init(&ir->state_lock);
+	init_waitqueue_head(&ir->remote_loop);
+	input_dev = input_allocate_device();
+	ir->input = input_dev;
+	input_dev->id.bustype = BUS_USB;
+	sprintf(ir->name, "em2880/em2870 remote control");
+	sprintf(ir->phys, "USB");
+	input_dev->name = ir->name;
+	input_dev->phys = ir->phys; /* FIXME: this is the wrong entry here,
+				    some applications depend on it */
+	input_dev->keycode = ir->keymap;
+	for (i = 0; i < IR_KEYTAB_SIZE; i++)
+		set_bit(ir->keymap[i], input_dev->keybit);
+
+	input_dev->keycodesize = sizeof(IR_KEYTAB_TYPE);
+	input_dev->keycodemax = IR_KEYTAB_SIZE;
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+
+	input_register_device(ir->input);
+
+	ir->state = EM28XX_REMOTE_POLLING;
+
+	if (dev->board->ir_getkey == em2888_get_key_empia) {
+		dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5);
+		ir->key = buf[4];
+		ir->oldval = get_timestamp();
+		ir->btn = buf[1];
+		ir->released = 1;
+	}
+
+	INIT_DELAYED_WORK(&ir->work, em28xx_ir_work);
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(50));
+	printk(KERN_INFO"em28xx-input.c: remote control handler attached\n");
+	mutex_unlock(&dev->input_lock);
+#endif
+	return 0;
+}
+
+
+static int get_key_terratec(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char b;
+
+	/* poll IR chip */
+	if (1 != i2c_master_recv(&ir->c, &b, 1)) {
+		dprintk("read error\n");
+		return -EIO;
+	}
+
+	/* it seems that 0xFE indicates that a button is still hold
+	   down, while 0xff indicates that no button is hold
+	   down. 0xfe sequences are sometimes interrupted by 0xFF */
+
+	dprintk("key %02x\n", b);
+
+	if (b == 0xff)
+		return 0;
+
+	if (b == 0xfe)
+		/* keep old data */
+		return 1;
+
+	*ir_key = b;
+	*ir_raw = b;
+	return 1;
+}
+
+
+static int get_key_em_haup(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char buf[2];
+	unsigned char code;
+
+	/* poll IR chip */
+	if (2 != i2c_master_recv(&ir->c, buf, 2))
+		return -EIO;
+
+	/* Does eliminate repeated parity code */
+	if (buf[1] == 0xff)
+		return 0;
+
+	ir->old = buf[1];
+
+	/* Rearranges bits to the right order */
+	code =   ((buf[0]&0x01)<<5) | /* 0010 0000 */
+		 ((buf[0]&0x02)<<3) | /* 0001 0000 */
+		 ((buf[0]&0x04)<<1) | /* 0000 1000 */
+		 ((buf[0]&0x08)>>1) | /* 0000 0100 */
+		 ((buf[0]&0x10)>>3) | /* 0000 0010 */
+		 ((buf[0]&0x20)>>5);  /* 0000 0001 */
+
+	dprintk("ir hauppauge (em2840): code=0x%02x (rcv=0x%02x)\n",
+			code, buf[0]);
+
+	/* return key */
+	*ir_key = code;
+	*ir_raw = code;
+	return 1;
+}
+
+static int get_key_pinnacle_usb(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char buf[3];
+
+	/* poll IR chip */
+
+	if (3 != i2c_master_recv(&ir->c, buf, 3)) {
+		dprintk("read error\n");
+		return -EIO;
+	}
+
+	dprintk("key %02x\n", buf[2]&0x3f);
+	if (buf[0] != 0x00) {
+		return 0;
+	}
+
+	*ir_key = buf[2]&0x3f;
+	*ir_raw = buf[2]&0x3f;
+
+	return 1;
+}
+
+int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	char buf[4];
+	int ret;
+
+	msleep(50);
+
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4);
+
+	if (ret < 0) {
+		switch (ret) {
+		case -ENODEV:
+			/* Device was disconnected */
+			em28xx_warn("reading key failed "
+				    "(error=%i=-ENODEV)\n", ret);
+			return -1;
+		default:
+			em28xx_warn("reading key failed (error=%i)\n", ret);
+			return -1;
+		}
+	}
+
+	ret = em28xx_read_reg_req(dev, 0x0, 0x44);
+	if (ret < 0)
+		return -1;
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1]&0x80;
+	dev->ir_em2880->sequence[1] = buf[0];
+	*ir_key = buf[2];
+	return (buf[0]&0x7f) || (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1]);
+}
+
+
+
+/* ----------------------------------------------------------------------- */
+void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir)
+{
+	if (disable_ir) {
+		ir->get_key = NULL;
+		return;
+	}
+
+	/* detect & configure */
+	switch (dev->model) {
+	case EM2800_BOARD_GENERIC:
+		break;
+	case EM2820_BOARD_GENERIC:
+		break;
+	case EM2800_BOARD_TERRATEC_CINERGY_200:
+	case EM2820_BOARD_TERRATEC_CINERGY_250:
+		ir->ir_codes = ir_codes_em_terratec_u;
+		ir->get_key = get_key_terratec;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM28XX Terratec)");
+		break;
+	case EM2820_BOARD_PINNACLE_USB_2:
+		ir->ir_codes = ir_codes_em_pinnacle_usb;
+		ir->get_key = get_key_pinnacle_usb;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM28XX Pinnacle PCTV)");
+		break;
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+		ir->ir_codes = ir_codes_hauppauge_new_u;
+		ir->get_key = get_key_em_haup;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM2840 Hauppauge)");
+		break;
+	case EM2820_BOARD_MSI_VOX_USB_2:
+		break;
+	case EM2800_BOARD_LEADTEK_WINFAST_USBII:
+		break;
+	case EM2800_BOARD_KWORLD_USB2800:
+		break;
+	}
+}
+
+/* ----------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/empia/em28xx-keymaps.c b/drivers/media/video/empia/em28xx-keymaps.c
new file mode 100644
index 0000000..5f82f91
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-keymaps.c
@@ -0,0 +1,350 @@
+#include "em28xx-keymaps.h"
+
+IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE] = {
+        [ 0x01 ] = KEY_CHANNEL,
+        [ 0x02 ] = KEY_SELECT,
+        [ 0x03 ] = KEY_MUTE,
+        [ 0x04 ] = KEY_POWER,
+        [ 0x05 ] = KEY_1,
+        [ 0x06 ] = KEY_2,
+        [ 0x07 ] = KEY_3,
+        [ 0x08 ] = KEY_CHANNELUP,
+        [ 0x09 ] = KEY_4,
+        [ 0x0a ] = KEY_5,
+        [ 0x0b ] = KEY_6,
+        [ 0x0c ] = KEY_CHANNELDOWN,
+        [ 0x0d ] = KEY_7,
+        [ 0x0e ] = KEY_8,
+        [ 0x0f ] = KEY_9,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_0,
+        [ 0x12 ] = KEY_MENU,
+        [ 0x13 ] = KEY_PRINT,
+        [ 0x14 ] = KEY_VOLUMEDOWN,
+        [ 0x16 ] = KEY_PAUSE,
+        [ 0x18 ] = KEY_RECORD,
+        [ 0x19 ] = KEY_REWIND,
+        [ 0x1a ] = KEY_PLAY,
+        [ 0x1b ] = KEY_FORWARD,
+        [ 0x1c ] = KEY_BACKSPACE,
+        [ 0x1e ] = KEY_STOP,
+        [ 0x40 ] = KEY_ZOOM,
+};
+
+EXPORT_SYMBOL_GPL(ir_codes_em_terratec_u);
+
+IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE] = {
+        /* Keys 0 to 9 */
+        [ 0x00 ] = KEY_0,
+        [ 0x01 ] = KEY_1,
+        [ 0x02 ] = KEY_2,
+        [ 0x03 ] = KEY_3,
+        [ 0x04 ] = KEY_4,
+        [ 0x05 ] = KEY_5,
+        [ 0x06 ] = KEY_6,
+        [ 0x07 ] = KEY_7,
+        [ 0x08 ] = KEY_8,
+        [ 0x09 ] = KEY_9,
+
+        [ 0x0a ] = KEY_TEXT,            /* keypad asterisk as well */
+        [ 0x0b ] = KEY_RED,             /* red button */
+        [ 0x0c ] = KEY_RADIO,
+        [ 0x0d ] = KEY_MENU,
+        [ 0x0e ] = KEY_SUBTITLE,        /* also the # key */
+        [ 0x0f ] = KEY_MUTE,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_VOLUMEDOWN,
+        [ 0x12 ] = KEY_PREVIOUS,        /* previous channel */
+        [ 0x14 ] = KEY_UP,
+        [ 0x15 ] = KEY_DOWN,
+        [ 0x16 ] = KEY_LEFT,
+        [ 0x17 ] = KEY_RIGHT,
+        [ 0x18 ] = KEY_VIDEO,           /* Videos */
+        [ 0x19 ] = KEY_AUDIO,           /* Music */
+        /* 0x1a: Pictures - presume this means
+           "Multimedia Home Platform" -
+           no "PICTURES" key in input.h
+         */
+        [ 0x1a ] = KEY_MHP,
+
+        [ 0x1b ] = KEY_EPG,             /* Guide */
+        [ 0x1c ] = KEY_TV,
+        [ 0x1e ] = KEY_NEXTSONG,        /* skip >| */
+        [ 0x1f ] = KEY_EXIT,            /* back/exit */
+        [ 0x20 ] = KEY_CHANNELUP,       /* channel / program + */
+        [ 0x21 ] = KEY_CHANNELDOWN,     /* channel / program - */
+        [ 0x22 ] = KEY_CHANNEL,         /* source (old black remote) */
+        [ 0x24 ] = KEY_PREVIOUSSONG,    /* replay |< */
+        [ 0x25 ] = KEY_ENTER,           /* OK */
+        [ 0x26 ] = KEY_SLEEP,           /* minimize (old black remote) */
+        [ 0x29 ] = KEY_BLUE,            /* blue key */
+        [ 0x2e ] = KEY_GREEN,           /* green button */
+        [ 0x30 ] = KEY_PAUSE,           /* pause */
+        [ 0x32 ] = KEY_REWIND,          /* backward << */
+        [ 0x34 ] = KEY_FASTFORWARD,     /* forward >> */
+        [ 0x35 ] = KEY_PLAY,
+        [ 0x36 ] = KEY_STOP,
+        [ 0x37 ] = KEY_RECORD,          /* recording */
+        [ 0x38 ] = KEY_YELLOW,          /* yellow key */
+        [ 0x3b ] = KEY_SELECT,          /* top right button */
+        [ 0x3c ] = KEY_ZOOM,            /* full */
+        [ 0x3d ] = KEY_POWER,           /* system power (green button) */
+};
+
+EXPORT_SYMBOL_GPL(ir_codes_hauppauge_new_u);
+
+
+
+IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE] = {
+        /* Keys 0 to 9 */
+        [ 0x15 ] = KEY_0,
+        [ 0x08 ] = KEY_1,
+        [ 0x09 ] = KEY_2,
+        [ 0x0a ] = KEY_3,
+        [ 0x0c ] = KEY_4,
+        [ 0x0d ] = KEY_5,
+        [ 0x0e ] = KEY_6,
+        [ 0x10 ] = KEY_7,
+        [ 0x11 ] = KEY_8,
+        [ 0x12 ] = KEY_9,
+
+        [ 0x03 ] = KEY_POWER,
+
+        [ 0x0b ] = KEY_VOLUMEUP,
+        [ 0x0f ] = KEY_VOLUMEDOWN,
+        [ 0x13 ] = KEY_CHANNELUP,
+        [ 0x17 ] = KEY_CHANNELDOWN,
+        [ 0x14 ] = KEY_INFO,
+
+        [ 0x00 ] = KEY_MUTE,
+
+        [ 0x06 ] = KEY_PLAY,
+        [ 0x04 ] = KEY_REWIND,
+        [ 0x07 ] = KEY_FORWARD,
+        [ 0x06 ] = KEY_PAUSE,
+        [ 0x05 ] = KEY_STOP,
+        [ 0x01 ] = KEY_RECORD,
+        [ 0x02 ] = KEY_ZOOM, /* fullscreen */
+        [ 0x16 ] = KEY_M,
+
+};
+EXPORT_SYMBOL_GPL(ir_codes_pinnacle2);
+
+IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE] = {
+        [ 0x00 ] = KEY_1,
+        [ 0x01 ] = KEY_2,
+        [ 0x02 ] = KEY_3,
+        [ 0x03 ] = KEY_4,
+        [ 0x04 ] = KEY_5,
+        [ 0x05 ] = KEY_6,
+        [ 0x06 ] = KEY_7,
+        [ 0x07 ] = KEY_8,
+        [ 0x08 ] = KEY_9,
+        [ 0x09 ] = KEY_0,
+        [ 0x0a ] = KEY_A,
+        [ 0x0b ] = KEY_VIDEO,
+        [ 0x0c ] = KEY_MUTE,
+        [ 0x0d ] = KEY_PLAYPAUSE,
+        [ 0x0e ] = KEY_DVD,
+        [ 0x0f ] = KEY_RADIO,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_VOLUMEDOWN,
+        [ 0x12 ] = KEY_CHANNELUP,
+        [ 0x13 ] = KEY_CHANNELDOWN,
+        [ 0x14 ] = KEY_POWER,
+        [ 0x15 ] = KEY_MENU,
+        [ 0x17 ] = KEY_STOP,
+        [ 0x18 ] = KEY_TV,
+        [ 0x1a ] = KEY_RECORD,
+        [ 0x1c ] = KEY_PREVIOUS,
+        [ 0x1e ] = KEY_B,
+        [ 0x1f ] = KEY_C,
+        [ 0x44 ] = KEY_E,
+        [ 0x46 ] = KEY_D,
+        [ 0x4a ] = KEY_ZOOM,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_gadmei_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE] = {
+        [ 0x00 ] = KEY_MUTE,
+        [ 0x01 ] = KEY_A,
+        [ 0x39 ] = KEY_POWER,
+        [ 0x03 ] = KEY_VOLUMEUP,
+        [ 0x06 ] = KEY_CHANNELUP,
+        [ 0x09 ] = KEY_VOLUMEDOWN,
+        [ 0x0c ] = KEY_CHANNELDOWN,
+        [ 0x0f ] = KEY_1,
+        [ 0x15 ] = KEY_2,
+        [ 0x10 ] = KEY_3,
+        [ 0x18 ] = KEY_4,
+        [ 0x1b ] = KEY_5,
+        [ 0x1e ] = KEY_6,
+        [ 0x11 ] = KEY_7,
+        [ 0x21 ] = KEY_8,
+        [ 0x12 ] = KEY_9,
+        [ 0x24 ] = KEY_ZOOM,
+        [ 0x27 ] = KEY_0,
+        [ 0x2a ] = KEY_T,
+        [ 0x2d ] = KEY_REWIND,
+        [ 0x30 ] = KEY_PLAY,
+        [ 0x33 ] = KEY_FORWARD,
+        [ 0x36 ] = KEY_RECORD,
+        [ 0x3c ] = KEY_STOP,
+        [ 0x3f ] = KEY_INFO,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle2_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE] = {
+        [ 0x3a ] = KEY_0,
+        [ 0x31 ] = KEY_1,
+        [ 0x32 ] = KEY_2,
+        [ 0x33 ] = KEY_3,
+        [ 0x34 ] = KEY_4,
+        [ 0x35 ] = KEY_5,
+        [ 0x36 ] = KEY_6,
+        [ 0x37 ] = KEY_7,
+        [ 0x38 ] = KEY_8,
+        [ 0x39 ] = KEY_9,
+
+        [ 0x2f ] = KEY_POWER,
+
+        [ 0x2e ] = KEY_P,
+        [ 0x1f ] = KEY_L,
+        [ 0x2b ] = KEY_I,
+
+        [ 0x2d ] = KEY_ZOOM,
+        [ 0x1e ] = KEY_ZOOM,
+        [ 0x1b ] = KEY_VOLUMEUP,
+        [ 0x0f ] = KEY_VOLUMEDOWN,
+        [ 0x17 ] = KEY_CHANNELUP,
+        [ 0x1c ] = KEY_CHANNELDOWN,
+        [ 0x25 ] = KEY_INFO,
+
+        [ 0x3c ] = KEY_MUTE,
+
+        [ 0x3d ] = KEY_LEFT,
+        [ 0x3b ] = KEY_RIGHT,
+
+        [ 0x3f ] = KEY_UP,
+        [ 0x3e ] = KEY_DOWN,
+        [ 0x1a ] = KEY_PAUSE,
+
+        [ 0x1d ] = KEY_MENU,
+        [ 0x19 ] = KEY_PLAY,
+        [ 0x16 ] = KEY_REWIND,
+        [ 0x13 ] = KEY_FORWARD,
+        [ 0x15 ] = KEY_PAUSE,
+        [ 0x0e ] = KEY_REWIND,
+        [ 0x0d ] = KEY_PLAY,
+        [ 0x0b ] = KEY_STOP,
+        [ 0x07 ] = KEY_FORWARD,
+        [ 0x27 ] = KEY_RECORD,
+        [ 0x26 ] = KEY_TUNER,
+        [ 0x29 ] = KEY_TEXT,
+        [ 0x2a ] = KEY_MEDIA,
+        [ 0x18 ] = KEY_EPG,
+        [ 0x27 ] = KEY_RECORD,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE] = {
+        [ 0x01 ] = KEY_POWER,
+        [ 0x02 ] = KEY_1,
+        [ 0x03 ] = KEY_2,
+        [ 0x04 ] = KEY_3,
+        [ 0x05 ] = KEY_4,
+        [ 0x06 ] = KEY_5,
+        [ 0x07 ] = KEY_6,
+        [ 0x08 ] = KEY_7,
+        [ 0x09 ] = KEY_8,
+        [ 0x0a ] = KEY_9,
+        [ 0x0b ] = KEY_TUNER,
+        [ 0x0c ] = KEY_0,
+        [ 0x0d ] = KEY_PREVIOUSSONG,
+
+        [ 0x41 ] = KEY_HOME,
+        [ 0x42 ] = KEY_MENU,
+        [ 0x43 ] = KEY_SUBTITLE,
+        [ 0x44 ] = KEY_TEXT,
+        [ 0x45 ] = KEY_DELETE,
+        [ 0x46 ] = KEY_TV,
+        [ 0x47 ] = KEY_DVD,
+        [ 0x49 ] = KEY_VIDEO,
+        [ 0x4a ] = KEY_AUDIO,
+        [ 0x4b ] = KEY_SHUFFLE,
+
+        [ 0x10 ] = KEY_UP,
+        [ 0x11 ] = KEY_LEFT,
+        [ 0x12 ] = KEY_OK,
+        [ 0x13 ] = KEY_RIGHT,
+        [ 0x14 ] = KEY_DOWN,
+
+        [ 0x0f ] = KEY_EPG,
+        [ 0x16 ] = KEY_INFO,
+        [ 0x4d ] = KEY_BACK,
+
+        [ 0x1c ] = KEY_VOLUMEUP,
+        [ 0x4c ] = KEY_PLAY,
+        [ 0x1b ] = KEY_CHANNELUP,
+        [ 0x1e ] = KEY_VOLUMEDOWN,
+        [ 0x1d ] = KEY_MUTE,
+        [ 0x1f ] = KEY_CHANNELDOWN,
+
+        [ 0x17 ] = KEY_RED,
+        [ 0x18 ] = KEY_GREEN,
+        [ 0x19 ] = KEY_YELLOW,
+        [ 0x1a ] = KEY_BLUE,
+
+        [ 0x58 ] = KEY_RECORD,
+        [ 0x48 ] = KEY_STOP,
+        [ 0x40 ] = KEY_PAUSE,
+        [ 0x54 ] = KEY_LAST,
+        [ 0x4e ] = KEY_REWIND,
+        [ 0x4f ] = KEY_FORWARD,
+        [ 0x5c ] = KEY_NEXT,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_terratec2);
+
+
+IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE] = {
+        [ 0x43 ] = KEY_POWER,
+	[ 0x03 ] = KEY_POWER2,
+
+        [ 0x04 ] = KEY_1,
+        [ 0x08 ] = KEY_2,
+        [ 0x02 ] = KEY_3,
+        [ 0x0f ] = KEY_4,
+        [ 0x05 ] = KEY_5,
+        [ 0x06 ] = KEY_6,
+        [ 0x0c ] = KEY_7,
+        [ 0x0d ] = KEY_8,
+        [ 0x0a ] = KEY_9,
+        [ 0x11 ] = KEY_0,
+
+        [ 0x09 ] = KEY_CHANNELUP,
+        [ 0x07 ] = KEY_CHANNELDOWN,
+        [ 0x0e ] = KEY_VOLUMEUP,
+        [ 0x13 ] = KEY_VOLUMEDOWN,
+
+        [ 0x01 ] = KEY_TUNER,
+	[ 0x0b ] = KEY_ZOOM,
+
+        [ 0x16 ] = KEY_PLAY,
+        [ 0x17 ] = KEY_MUTE,
+        [ 0x14 ] = KEY_RECORD,
+        [ 0x15 ] = KEY_STOP,
+        
+	[ 0x18 ] = KEY_UP,
+        [ 0x19 ] = KEY_DOWN,
+        [ 0x1a ] = KEY_LEFT,
+        [ 0x1b ] = KEY_RIGHT,
+        
+	[ 0x1c ] = KEY_RED,
+        [ 0x1d ] = KEY_GREEN,
+        [ 0x1e ] = KEY_YELLOW,
+        [ 0x1f ] = KEY_BLUE,
+
+	[ 0x12 ] = KEY_OK,
+	[ 0x10 ] = KEY_HOME
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_kworld);
diff --git a/drivers/media/video/empia/em28xx-keymaps.h b/drivers/media/video/empia/em28xx-keymaps.h
new file mode 100644
index 0000000..1205419
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-keymaps.h
@@ -0,0 +1,21 @@
+#ifndef _EM28XX_KEYMAPS
+#define _EM28XX_KEYMAPS
+#include <linux/input.h>
+#include <linux/workqueue.h>
+
+/* TODO move all that stuff to userspace, fixed keytab definitions in the kernel
+ * are broken by design, there are multiple remotes available with custom return keys
+ * -> raw lirc module is required here */
+
+#define IR_KEYTAB_TYPE  u32 
+#define IR_KEYTAB_SIZE  128  // enougth for rc5, probably need more some day ...
+
+extern IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE];
+#endif
diff --git a/drivers/media/video/empia/em28xx-video.c b/drivers/media/video/empia/em28xx-video.c
new file mode 100644
index 0000000..49db364
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-video.c
@@ -0,0 +1,4244 @@
+/*
+   em28xx-video.c - driver for Empia
+		    EM2800/2820/2840/2870/2880
+		    USB video capture devices
+
+   Copyright (C) 2005 Sascha Sommer <saschasommer@freenet.de>
+		 2005-2007 Markus Rechberger <mrechberger@gmail.com>
+		 2005-2006 Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005 Ludovico Cavedon <cavedon@sssup.it>
+
+   Some parts based on SN9C10x PC Camera Controllers GPL driver made
+   by Luca Risolia <luca.risolia@studio.unibo.it>
+
+   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/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#include "dvb_frontend.h"
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+#include <linux/mutex.h>
+#endif
+
+#include "em28xx.h"
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
+#include <media/v4l2-ioctl.h>
+#endif
+#include "include/tunerchip.h"
+#include "xc3028/xc3028_control.h"
+#include "xc3028/xc3028_module.h"
+
+#include "xc5000/xc5000_control.h"
+#include "xc5000/xc5000_module.h"
+#include "cx25843/em28xx-cx25843.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+#include "i2c-compat.h"
+#include <linux/moduleparam.h>
+#endif
+
+#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@sssup.it>, " \
+		      "Markus Rechberger <mrechberger@gmail.com>, " \
+		      "Mauro Carvalho Chehab <mchehab@infradead.org>, " \
+		      "Sascha Sommer <saschasommer@freenet.de>"
+
+#define DRIVER_NAME         "em28xx"
+#define DRIVER_DESC         "Empia em28xx based USB video device driver"
+#define EM28XX_VERSION_CODE  KERNEL_VERSION(0, 0, 1)
+
+#define em28xx_videodbg(fmt, arg...) do {\
+	if (video_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+unsigned char *XC5000_firmware_SEQUENCE;
+
+LIST_HEAD(em28xx_devlist);
+static LIST_HEAD(em28xx_extension_devlist);
+static DEFINE_MUTEX(em28xx_extension_devlist_lock);
+
+static unsigned int card[]     = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int video_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int vbi_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+MODULE_PARM(card, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+MODULE_PARM(video_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+MODULE_PARM(vbi_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+static int dummy;
+module_param_array(card,  int, dummy, 0444);
+module_param_array(video_nr, int, dummy, 0444);
+module_param_array(vbi_nr, int, dummy, 0444);
+#else
+module_param_array(card,  int, NULL, 0444);
+module_param_array(video_nr, int, NULL, 0444);
+module_param_array(vbi_nr, int, NULL, 0444);
+#endif
+#endif
+MODULE_PARM_DESC(card, "card type");
+MODULE_PARM_DESC(video_nr, "video device numbers");
+MODULE_PARM_DESC(vbi_nr, "vbi device numbers");
+
+static int tuner = -1;
+module_param(tuner, int, 0444);
+MODULE_PARM_DESC(tuner, "tuner type");
+
+static unsigned int video_debug;
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+
+static unsigned int device_mode;
+module_param(device_mode, int, 0644);
+MODULE_PARM_DESC(device_mode, "device mode (DVB-T/Analogue TV)");
+
+static unsigned int vbi_mode = 1;
+module_param(vbi_mode, int, 0644);
+MODULE_PARM_DESC(vbi_mode, "VBI mode (0 disabled/1 enabled(default, "
+				"if appropriate))");
+
+static unsigned int vbi_interlaced;
+module_param(vbi_interlaced, int, 0644);
+MODULE_PARM_DESC(vbi_interlaced, "VBI Interlaced (default 0 - off)");
+
+/* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS */
+static unsigned long em28xx_devused;
+
+static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma);
+static int em28xx_stream_interrupt(struct em28xx *dev, int type);
+
+struct workqueue_struct *em28xx_wq; /* global workqueue for polling the
+					      remote control and requesting
+					      submodules */
+/* supported controls */
+/* Common to all boards */
+static struct v4l2_queryctrl em28xx_qctrl[] = {
+	{
+		.id = V4L2_CID_AUDIO_VOLUME,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Volume",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x1f,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_AUDIO_MUTE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "Mute",
+		.minimum = 0,
+		.maximum = 1,
+		.step = 1,
+		.default_value = 1,
+		.flags = 0,
+	}
+};
+
+/* FIXME: These are specific to saa711x - should be moved to its code */
+static struct v4l2_queryctrl saa711x_qctrl[] = {
+	{
+		.id = V4L2_CID_BRIGHTNESS,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Brightness1",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_CONTRAST,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Contrast",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x10,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_SATURATION,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Saturation",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x10,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_RED_BALANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Red chroma balance",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_BLUE_BALANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Blue chroma balance",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_GAMMA,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Gamma",
+		.minimum = 0x0,
+		.maximum = 0x3f,
+		.step = 0x1,
+		.default_value = 0x20,
+		.flags = 0,
+	}
+#ifdef V4L2_CID_SHARPNESS
+	 , {
+		.id = V4L2_CID_SHARPNESS,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Sharpness",
+		.minimum = 0x0,
+		.maximum = 0xf,
+		.step = 0x1,
+		.default_value = 0x8,
+		.flags = 0,
+	}
+#endif
+};
+
+struct em28xx_output_fmt em28xx_out_fmt[]={
+	{
+		.fmt = {
+			.index = 0,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "Y0-U-Y1-V, 16 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUYV,
+		},
+		.config = 0x14
+	}, {
+		.fmt = {
+			.index = 1,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "Y1-U-Y0-V, 16 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUY1,
+		},
+		.config = 0x15
+	}, {
+		.fmt = {
+			.index = 2,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "YUV411, 12 bpp",
+			.pixelformat = V4L2_PIX_FMT_Y41P
+		},
+		.config = 0x18
+	}, {
+		.fmt = {
+			.index = 3,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "YUV211, 8 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUV211
+		},
+		.config = 0x10
+	}, {
+		.fmt = {
+			.index = 4,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 16 bpp, 6-5-6", /* 16 != 656->17 */
+			.pixelformat = V4L2_PIX_FMT_RGB565   /* 565->16 */
+		},
+		.config = 0x04,
+	}, {
+		.fmt = {
+			.index = 5,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit RGRG",
+			.pixelformat = 1 /* TODO */
+		},
+		.config = 0x00
+	}, {
+		.fmt = {
+			.index = 6,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit GRGR",
+			.pixelformat = 2 /* TODO */
+		},
+		.config = 0x01
+	}, {
+		.fmt = {
+			.index = 7,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit GBGB",
+			.pixelformat = 3 /* TODO */
+		},
+		.config = 0x02
+	}, {
+		.fmt = {
+			.index = 8,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit BGBG",
+			.pixelformat = 4 /* TODO */
+		},
+		.config = 0x03
+	}
+};
+
+static struct usb_driver em28xx_usb_driver;
+
+static DEFINE_MUTEX(em28xx_sysfs_lock);
+static DECLARE_RWSEM(em28xx_disconnect);
+
+/* ----------------------------------------------------------- */
+/* delayed request_module                                      */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+#if defined(CONFIG_MODULES) && defined(MODULE)
+static void request_module_async(struct work_struct *work)
+{
+	struct em28xx *dev = container_of(work,
+			struct em28xx,
+			request_module_wk.work);
+
+	if ((dev->state & DEV_DISCONNECTED) ||
+		(dev->state & DEV_MISCONFIGURED))
+		goto out;
+
+	if (dev->dev_modes & EM28XX_AUDIO)
+		request_module("em28xx-audio");
+
+	if ((dev->dev_modes & EM28XX_DVBT || dev->dev_modes & EM28XX_ATSC || dev->dev_modes & EM28XX_ISDB))
+		request_module("em28xx-dvb");
+
+	/* unlock all the device nodes here */
+out:
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+static void request_modules(struct em28xx *dev) {
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#else
+/* this is a bit fishy here, if we'd directly try to load the module here
+   it would end up in a deadlock since modprobe locks the em28xx module.
+   When loading the em2880-dvb module it also tries to open the em28xx module
+   and it gets stuck and won't return, so a small workaround is to set up
+   a workqueue and run modprobe from another process
+
+   the previous approach used the global workqueue, although this could have
+   locked up the keyboard input system, currently when running
+
+   while :; do modprobe em28xx; rmmod em28xx-dvb; rmmod em28xx-audio; rmmod
+   em28xx; done
+
+   the unloading process might deadlock with the request thread, the requesting-   insmod needs to get killed manually ...(the "bug/feature" is in the
+   linux module code...)
+
+   This whole issue goes back for a very long time already and quite a few
+   people went on this before...
+ */
+
+static void request_modules(struct em28xx *dev)
+{
+       schedule_delayed_work(&dev->request_module_wk, msecs_to_jiffies(50));
+}
+#endif
+#else
+static void request_modules(struct em28xx *dev) {
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#endif
+#endif
+
+/*********************  v4l2 interface  ******************************************/
+
+/*
+ * em28xx_config()
+ * inits registers with sane defaults
+ */
+int em28xx_config(struct em28xx *dev)
+{
+	em28xx_audio_usb_mute(dev, EM28XX_MUTED);
+	dev->mute = 1;		/* maybe not the right place... */
+	dev->volume = 0x1f;
+	em28xx_audio_analog_set(dev);
+	em28xx_audio_analog_setup(dev);
+	em28xx_outfmt_set_yuv422(dev);
+	em28xx_colorlevels_set_default(dev);
+	em28xx_compression_disable(dev);
+
+	return 0;
+}
+
+/*
+ * em28xx_config_i2c()
+ * configure i2c attached devices
+ * TODO: add MSI_VOX_USB_2 support again!
+ *
+ */
+void em28xx_config_i2c(struct em28xx *dev)
+{
+	struct v4l2_routing route;
+
+	route.input = INPUT(dev->ctl_input)->vmux;
+	route.output = 0;
+
+	/* configure decoder */
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0);
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
+	em28xx_i2c_call_clients(dev, VIDIOC_STREAMON, NULL);
+
+}
+
+/*
+ * em28xx_empty_framequeues()
+ * prepare queues for incoming and outgoing frames
+ */
+static void em28xx_empty_framequeues(struct em28xx *dev, int type)
+{
+	u32 i;
+	/* FIXME please verify EM28XX_NUM_FRAMES */
+
+	if (type == EM28XX_VBI) {
+		INIT_LIST_HEAD(&dev->vbi_inqueue);
+		INIT_LIST_HEAD(&dev->vbi_outqueue);
+		for (i = 0; i < EM28XX_NUM_FRAMES; i++) {
+			dev->vbi_frame[i].state = F_UNUSED;
+			dev->vbi_frame[i].buf.bytesused = 0;
+		}
+	} else {
+		INIT_LIST_HEAD(&dev->inqueue);
+		INIT_LIST_HEAD(&dev->outqueue);
+		for (i = 0; i < EM28XX_NUM_FRAMES; i++) {
+			dev->frame[i].state = F_UNUSED;
+			dev->frame[i].buf.bytesused = 0;
+		}
+	}
+
+}
+
+static void video_mux(struct em28xx *dev, int index)
+{
+	int input = INPUT(index)->vmux;
+	struct v4l2_routing route;
+	int ainput;
+
+	route.input = INPUT(index)->vmux;
+	route.output = 0;
+
+	dev->ctl_input = index;
+	dev->ctl_ainput = INPUT(index)->amux;
+	dev->ctl_amix = INPUT(index)->amix;
+
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
+
+	em28xx_videodbg("Setting input index = %d, vmux = %d, amux = %d\n",
+			index, input, dev->ctl_ainput);
+
+	if (dev->has_msp34xx) {
+		if (dev->i2s_speed)
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_I2S_CLOCK_FREQ,
+					&dev->i2s_speed);
+		em28xx_i2c_call_clients(dev, VIDIOC_S_AUDIO, &dev->ctl_ainput);
+		ainput = EM28XX_AUDIO_SRC_TUNER;
+		em28xx_audio_source(dev, ainput);
+	} else {
+		switch (dev->model) {
+		case EM2860_BOARD_GADMEI_UTV330:
+		{
+			u8 val;
+			switch (dev->ctl_ainput) {
+				case 0:
+					val = 0xfd;
+					break;
+				case 1:
+					val = 0xfc;
+					break;
+				default :
+					val = 0xfe;
+					break;
+			}
+			if (dev->mute)
+				val = 0xfe;
+
+			em28xx_write_regs(dev, 0x08, &val, 1);
+			break;
+		}
+		default :
+			switch (dev->ctl_ainput) {
+			case 0:
+				ainput = EM28XX_AUDIO_SRC_TUNER;
+				break;
+			default:
+				ainput = EM28XX_AUDIO_SRC_LINE;
+			}
+			em28xx_audio_source(dev, ainput);
+			break;
+		}
+	}
+	em28xx_audio_set_mixer(dev, dev->ctl_amix);
+}
+
+
+static int em28xx_acquire(struct em28xx *dev, int mode, int lock)
+{
+	int ret = 0;
+
+	if (dev->mode_lock && mode != EM28XX_LOCK)
+		return -EBUSY;
+
+	switch (mode) {
+	case EM28XX_LOCK:
+		if (lock)
+			dev->mode_lock = 1;
+		else
+			dev->mode_lock = 0;
+		break;
+	/* radio is allowed if TV and DVB is not in use */
+	case EM28XX_RADIO:
+		if (lock) {
+			if (dev->fe_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->radio_user++;
+		} else
+			dev->radio_user--;
+		break;
+	/* video, vbi and analogue audio are allowed at the same time */
+	case EM28XX_VIDEO:
+		if (lock) {
+			if (dev->fe_user || dev->radio_user)
+				ret = -EBUSY;
+			else
+				dev->video_user++;
+		} else
+			dev->video_user--;
+		break;
+	case EM28XX_VBI:
+		if (lock) {
+			if (dev->fe_user || dev->radio_user)
+				ret = -EBUSY;
+			else
+				dev->vbi_user++;
+		} else
+			dev->vbi_user--;
+		break;
+	case EM28XX_DVBT:
+		if (lock)  {
+			if (dev->radio_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->fe_user++;
+		} else {
+			dev->fe_user--;
+		}
+		break;
+	case EM28XX_ATSC:
+		if (lock) {
+			if (dev->radio_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->fe_user++;
+		} else
+			dev->fe_user--;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+/*
+ * em28xx_v4l2_open()
+ * inits the device and starts isoc transfer
+ */
+
+static int em28xx_v4l2_open(struct inode *inode, struct file *filp)
+{
+	int minor = iminor(inode);
+	int errCode = 0;
+	int mode = V4L2_TUNER_ANALOG_TV;
+	int type = 0;
+	int ret;
+	struct em28xx *h, *dev = NULL;
+	struct em28xx_fh *fh;
+	struct list_head *list;
+
+	list_for_each(list, &em28xx_devlist) {
+		h = list_entry(list, struct em28xx, devlist);
+		if (h == NULL) {
+			printk("em28xx-devlist empty\n");
+			return -ENODEV;
+		}
+
+		if (h->vdev &&
+				h->vdev->minor == minor) {
+			dev  = h;
+			type = EM28XX_VIDEO;
+			mode = V4L2_TUNER_ANALOG_TV;
+			break;
+		}
+
+		if ((h->dev_modes & EM28XX_RADIO) &&
+				h->rdev &&
+				h->rdev->minor == minor) {
+			dev = h;
+			type = EM28XX_RADIO;
+			mode = V4L2_TUNER_RADIO;
+			break;
+		}
+
+		if ((h->dev_modes & EM28XX_VBI) &&
+				h->vbi_dev &&
+				h->vbi_dev->minor == minor) {
+			dev  = h;
+			type = EM28XX_VBI;
+			mode = V4L2_TUNER_ANALOG_TV;
+			break;
+		}
+	}
+
+	if (NULL == dev) {
+		printk(KERN_INFO"device struct is not set\n");
+		return -ENODEV;
+	}
+
+	ret = dev->em28xx_acquire(dev, type, 1);
+
+	if (ret != 0)
+		return ret;
+
+	if (dev->has_inttuner) {
+		if (dev->mode != mode) {
+			int arg;
+
+			dev->mode = mode;
+			arg = EM28XX_REG_ON;
+
+			dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg);
+			arg = EM28XX_REG_OFF;
+			dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &arg);
+			mdelay(100);
+
+			em28xx_config(dev);
+			em28xx_config_i2c(dev);
+			if (dev->mode == V4L2_TUNER_ANALOG_TV)
+				em28xx_i2c_call_clients(dev, VIDIOC_S_STD,
+						&dev->tvnorm->id);
+
+			switch (dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+			{
+				struct xc3028_init_cmd cmd;
+
+				if (dev->tuner == NULL) {
+					printk(KERN_INFO"tuner is not "
+							"attached\n");
+					dev->em28xx_acquire(dev, type, 0);
+					return -EINVAL;
+				}
+				switch (dev->mode) {
+				case V4L2_TUNER_RADIO:
+					/* this is moreover to switch the
+					   decoder to FM */
+
+					cmd.new_tv_mode_ptr =
+						dev->fmnorm->tv_mode;
+					cmd.new_channel_map_ptr =
+						dev->fmnorm->channelmap;
+					if (dev->tuner)
+						dev->tuner->tuner_cmd(
+							dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+					break;
+				case V4L2_TUNER_ANALOG_TV:
+					cmd.new_tv_mode_ptr =
+						dev->tvnorm->tv_mode;
+					cmd.new_channel_map_ptr =
+						dev->tvnorm->channelmap;
+					if (dev->tuner && dev->tuner->tuner_cmd)
+						dev->tuner->tuner_cmd(
+							dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+					break;
+				}
+				break;
+			}
+			case TUNER_XCEIVE_XC5000:
+			{
+				if (dev->tuner == NULL) {
+					printk("tuner is not attached\n");
+					dev->em28xx_acquire(dev, type, 0);
+					return -EINVAL;
+				}
+
+				if (dev->tuner && dev->tuner->tuner_cmd)
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_INIT_TUNER,
+							NULL);
+				switch (dev->mode) {
+				case V4L2_TUNER_RADIO:
+				{
+					struct xc_std_conf cmd;
+					cmd.index = dev->fmnorm->index;
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_SET_MODE, &cmd);
+					break;
+				}
+				case V4L2_TUNER_ANALOG_TV:
+				{
+					struct xc_std_conf cmd;
+					cmd.index = dev->tvnorm->index;
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_SET_MODE, &cmd);
+					break;
+				}
+				}
+				break;
+			}
+			}
+			if (dev->mode == V4L2_TUNER_RADIO) {
+				struct v4l2_routing arouting;
+				arouting.input = CX25843_RADIO;
+				em28xx_i2c_call_clients(dev,
+						VIDIOC_INT_S_AUDIO_ROUTING,
+						&arouting);
+			}
+
+		}
+	}
+
+	fh = kzalloc(sizeof(struct em28xx_fh), GFP_KERNEL);
+
+	if (!fh) {
+		printk(KERN_INFO"em28xx-video.c: Out of memory\n");
+		dev->em28xx_acquire(dev, type, 0);
+		return -ENOMEM;
+	}
+	fh->type = type;
+	fh->dev = dev;
+	filp->private_data = fh;
+
+	em28xx_videodbg("open minor = %d type = %s users = %d\n",
+				minor, v4l2_type_names[dev->type], dev->users);
+
+	if (!down_read_trylock(&em28xx_disconnect)) {
+		dev->em28xx_acquire(dev, type, 0);
+		return -ERESTARTSYS;
+	}
+
+	dev->users++;
+	if (dev->vbi_user == 1 && fh->type == EM28XX_VBI)
+		dev->vbi_bytesread = 0;
+
+	if ((fh->type == EM28XX_VIDEO || fh->type == EM28XX_VBI) &&
+			dev->users == 1) {
+
+		em28xx_set_alternate(dev);
+
+		dev->width = norm_maxw(dev);
+		dev->height = norm_maxh(dev);
+		dev->frame_size = dev->width * dev->height * 2;
+		dev->vbi_frame_size = 720 * 2 *
+			(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+		dev->field_size = dev->frame_size >> 1;
+		dev->vbi_field_size = dev->vbi_frame_size >> 1;
+		dev->bytesperline = dev->width * 2;
+		dev->vbi_bytesperline = dev->width * 2;
+		dev->hscale = 0;
+		dev->vscale = 0;
+		dev->video_bytesread = 0;
+		dev->vbi_bytesread = 0;
+		dev->vbi_dropbytes = 0;
+
+		dev->vbi_frame_current = NULL;
+		dev->frame_current = NULL;
+
+		em28xx_capture_start(dev, 1);
+		em28xx_resolution_set(dev);
+
+		/* start the transfer */
+		errCode = em28xx_init_isoc(dev);
+
+		em28xx_empty_framequeues(dev, EM28XX_VBI);
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		if (errCode) {
+			dev->users--;
+			goto err;
+		}
+		dev->video_io = IO_NONE;
+		dev->vbi_io = IO_NONE;
+		dev->stream = STREAM_OFF;
+		dev->num_frames = 0;
+		dev->vbi_num_frames = 0;
+		dev->state |= DEV_INITIALIZED;
+		dev->state &= ~DEV_MISCONFIGURED;
+	}
+	up_read(&em28xx_disconnect);
+	return 0;
+err:
+	up_read(&em28xx_disconnect);
+	dev->em28xx_acquire(dev, type, 0);
+	return errCode;
+}
+
+/*
+ * em28xx_realease_resources()
+ * unregisters the v4l2, i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+static void em28xx_release_resources(struct em28xx *dev)
+{
+	mutex_lock(&em28xx_sysfs_lock);
+
+	list_del(&dev->devlist);
+	if (dev->dev_modes&EM28XX_VIDEO) {
+		em28xx_info("disconnecting %s\n", dev->vdev->name);
+		em28xx_info("V4L2 VIDEO devices /dev/video%d deregistered\n",
+				dev->vdev->num);
+		video_unregister_device(dev->vdev);
+		if (dev->dev_modes & EM28XX_VBI) {
+			em28xx_info("V4L2 VBI devices /dev/vbi%d "
+				"deregistered\n",
+					dev->vbi_dev->num);
+			video_unregister_device(dev->vbi_dev);
+		}
+	}
+	if (dev->dev_modes & EM28XX_RADIO)
+		video_unregister_device(dev->rdev);
+
+	em28xx_i2c_unregister(dev);
+	usb_put_dev(dev->udev);
+	mutex_unlock(&em28xx_sysfs_lock);
+
+		
+	/* Mark device as unused */
+	em28xx_devused &= ~(1<<dev->devno);
+
+}
+
+/*
+ * em28xx_v4l2_close()
+ * stops streaming and deallocates all resources allocated by the
+ * v4l2 calls and ioctls
+ */
+static int em28xx_v4l2_close(struct inode *inode, struct file *filp)
+{
+	int errCode;
+	struct em28xx_fh *fh;
+	struct em28xx *dev;
+	int ret = 0;
+
+	if (filp->private_data == NULL)
+		return 0;
+
+
+	fh = filp->private_data;
+	dev = fh->dev;
+	em28xx_videodbg("users = %d\n", dev->users);
+
+	if ((dev->vbi_user == 1 || fh->reader == 1) &&
+			fh->type == EM28XX_VBI) {
+		if (dev->vbi_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting "
+					"stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			dev->vbi_stream = STREAM_OFF;
+		}
+		dev->vbi_io = IO_NONE;
+		fh->reader = 0;
+		dev->vbi_reader = 0;
+	}
+	if ((dev->video_user == 1 || fh->reader == 1) &&
+			fh->type == EM28XX_VIDEO) {
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting "
+					"stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+			dev->video_stream = STREAM_OFF;
+		}
+		fh->reader = 0;
+		dev->video_reader = 0;
+		dev->video_io = IO_NONE;
+	}
+
+	dev->em28xx_acquire(dev, fh->type, 0);
+
+	/* assume that all handles are closed */
+	if (dev->users == 1) {
+		int gpio_arg;
+		dev->video_reader = 0;
+		dev->vbi_reader = 0;
+
+		em28xx_uninit_isoc(dev);
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+		em28xx_release_buffers(dev, EM28XX_VBI);
+
+		/* turn off led */
+		gpio_arg = EM28XX_REG_OFF;
+		em28xx_gpio_control(dev, EM28XX_LED1_ON,      &gpio_arg);
+		/* the device is already disconnect, free the remaining
+		   resources */
+		if (dev->state & DEV_DISCONNECTED) {
+			em28xx_release_resources(dev);
+			kfree(dev->alt_max_pkt_size);
+			tuner_chip_detach(dev->tuner);
+			kfree(dev);
+			kfree(fh);
+			return 0;
+		}
+
+		/* set alternate 0 */
+		dev->alt = 0;
+		em28xx_videodbg("setting alternate 0\n");
+		if (dev->adev && dev->adev->users == 0) {
+			dev->alt = 0;
+			em28xx_videodbg("setting alternate 0\n");
+			errCode = usb_set_interface(dev->udev, dev->usb_interface, 0);
+			if (errCode < 0) {
+				em28xx_errdev("cannot change alternate "
+					"number to 0 (error = %i)\n",
+						errCode);
+			}
+		}
+	}
+	kfree(fh);
+	dev->users--;
+	wake_up_interruptible_nr(&dev->open, 1);
+	return ret;
+}
+
+/*
+ * em28xx_v4l2_read()
+ * will allocate buffers when called for the first time
+ */
+static ssize_t
+em28xx_v4l2_read(struct file *filp, char __user *buf, size_t count,
+		 loff_t *f_pos)
+{
+	struct em28xx_frame_t *f, *i;
+	unsigned long lock_flags;
+	int ret = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	/* TODO: either add support for sliced VBI */
+	if (fh->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+		em28xx_videodbg("V4L2_BUF_TYPE_SLICED_VBI_CAPTURE is set\n");
+		if (copy_to_user(buf, "", 1))
+			return -EFAULT;
+		return 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("device misconfigured; close and open it "
+				"again\n");
+		return -EIO;
+	}
+
+	if (fh->type == EM28XX_VIDEO && dev->video_reader > 0 &&
+			fh->reader == 0)
+		return -EBUSY;
+
+	if (fh->type == EM28XX_VBI && dev->vbi_reader > 0 && fh->reader == 0)
+		return -EBUSY;
+
+	if (fh->type == EM28XX_VIDEO && dev->video_io == IO_MMAP) {
+		em28xx_videodbg("Video IO method is set to mmap"
+				" the device again to choose the"
+				"read method\n");
+		return -EINVAL;
+	} else {
+		if (fh->type == EM28XX_VIDEO) {
+			dev->video_reader = 1;
+			fh->reader = 1;
+			dev->video_io = IO_READ;
+		}
+	}
+
+	if (fh->type == EM28XX_VBI && dev->vbi_io == IO_MMAP) {
+		em28xx_videodbg("VBI IO method is set to mmap\n");
+		return -EINVAL;
+	} else {
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_reader = 1;
+			fh->reader = 1;
+			dev->vbi_io = IO_READ;
+		}
+	}
+
+	if (fh->type == EM28XX_VIDEO && dev->video_stream == STREAM_OFF) {
+		if (!em28xx_request_buffers(dev,
+					EM28XX_NUM_READ_FRAMES,
+					EM28XX_VIDEO)) {
+			em28xx_errdev("read failed, not enough memory\n");
+			return -ENOMEM;
+		}
+
+		dev->video_stream = STREAM_ON;
+		em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+	}
+
+	if (fh->type == EM28XX_VBI && dev->vbi_stream == STREAM_OFF) {
+		if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES,
+					EM28XX_VBI)) {
+			em28xx_errdev("read failed not enough memory\n");
+			return -ENOMEM;
+		}
+
+		dev->vbi_stream = STREAM_ON;
+		em28xx_queue_unusedframes(dev, EM28XX_VBI);
+	}
+
+
+
+	if (!count)
+		return 0;
+
+	if (fh->type == EM28XX_VIDEO) {
+		if (list_empty(&dev->outqueue)) {
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			ret = wait_event_interruptible
+				(dev->wait_frame,
+				 (!list_empty(&dev->outqueue)) ||
+				 (dev->state & DEV_DISCONNECTED));
+			if (ret)
+				return ret;
+
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+
+			dev->video_bytesread = 0;
+		}
+
+		f = list_entry(dev->outqueue.prev, struct em28xx_frame_t,
+				frame);
+
+
+		em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+
+		if (count > f->buf.length)
+			count = f->buf.length;
+
+		if ((dev->video_bytesread+count) > dev->frame_size)
+			count = dev->frame_size - dev->video_bytesread;
+
+		if (copy_to_user(buf, f->bufmem + dev->video_bytesread, count))
+			return -EFAULT;
+
+		dev->video_bytesread += count;
+
+		if (dev->video_bytesread == dev->frame_size) {
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			list_for_each_entry(i, &dev->outqueue, frame)
+				i->state = F_UNUSED;
+			INIT_LIST_HEAD(&dev->outqueue);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+			em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+			dev->video_bytesread = 0;
+		}
+
+		*f_pos += count;
+	} else if (fh->type == EM28XX_VBI) {
+
+		if (list_empty(&dev->vbi_outqueue)) {
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			if (dev->vbi_num_frames == 0) {
+				em28xx_request_buffers(dev,
+						EM28XX_NUM_READ_FRAMES,
+						EM28XX_VBI);
+				em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			}
+
+			ret = wait_event_interruptible
+				(dev->wait_vbi_frame,
+				 (!list_empty(&dev->vbi_outqueue)) ||
+				 (dev->state & DEV_DISCONNECTED));
+
+			if (ret)
+				return ret;
+
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+
+			dev->vbi_bytesread = 0;
+		}
+
+		f = list_entry(dev->vbi_outqueue.prev, struct em28xx_frame_t,
+				frame);
+		em28xx_queue_unusedframes(dev, EM28XX_VBI);
+
+		if (count > f->buf.length)
+			count = f->buf.length;
+
+		if ((dev->vbi_bytesread + count) > dev->vbi_frame_size)
+			count = dev->vbi_frame_size-dev->vbi_bytesread;
+
+		if (copy_to_user(buf, f->bufmem+dev->vbi_bytesread, count))
+			return -EFAULT;
+
+		dev->vbi_bytesread += count;
+		if (dev->vbi_bytesread == dev->vbi_frame_size) {
+			spin_lock_irqsave(&dev->vbi_queue_lock, lock_flags);
+			list_for_each_entry(i, &dev->vbi_outqueue, frame)
+				i->state = F_UNUSED;
+			INIT_LIST_HEAD(&dev->vbi_outqueue);
+			spin_unlock_irqrestore(&dev->vbi_queue_lock,
+					lock_flags);
+			em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			dev->vbi_bytesread = 0;
+		}
+
+		*f_pos += count;
+	}
+
+	return count;
+}
+
+/*
+ * em28xx_v4l2_poll()
+ * will allocate buffers when called for the first time
+ */
+static unsigned int em28xx_v4l2_poll(struct file *filp, poll_table * wait)
+{
+	unsigned int mask = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (dev->state & DEV_DISCONNECTED)
+		em28xx_videodbg("device not present\n");
+	else if (dev->state & DEV_MISCONFIGURED)
+		em28xx_videodbg("device is misconfigured; close and open it "
+				"again\n");
+	else if (fh->type == EM28XX_VBI) {
+		if (fh->reader == 0 && dev->vbi_io != IO_NONE)
+			return -EINVAL;
+
+		if (dev->vbi_num_frames == 0 && dev->vbi_reader == 0) {
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			if (!em28xx_request_buffers(dev,
+						EM28XX_NUM_READ_FRAMES,
+						EM28XX_VBI))
+				return -ENOMEM;
+		}
+
+		if (dev->vbi_io == IO_NONE && dev->vbi_reader == 0) {
+			em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			dev->vbi_io = IO_READ;
+			dev->vbi_stream = STREAM_ON;
+			dev->vbi_reader = 1;
+			fh->reader = 1;
+		}
+
+		poll_wait(filp, &dev->wait_vbi_frame, wait);
+		if (!list_empty(&dev->vbi_outqueue))
+			mask |= POLLIN | POLLRDNORM;
+		return mask;
+	} else if (fh->type == EM28XX_VIDEO) {
+
+		if (dev->num_frames == 0 && dev->reader == 0) {
+			em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+			if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES,
+						EM28XX_VIDEO))
+				return -ENOMEM;
+		}
+
+		if (dev->video_io == IO_NONE && dev->reader == 0) {
+			em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+			dev->video_io = IO_READ;
+			dev->video_stream = STREAM_ON;
+			dev->reader = 1;
+			fh->reader = 1;
+		}
+
+		poll_wait(filp, &dev->wait_frame, wait);
+		if (!list_empty(&dev->outqueue))
+			mask |= POLLIN | POLLRDNORM;
+		return mask;
+	}
+
+	return POLLERR;
+}
+
+/*
+ * em28xx_vm_open()
+ */
+static void em28xx_vm_open(struct vm_area_struct *vma)
+{
+	struct em28xx_frame_t *f = vma->vm_private_data;
+	f->vma_use_count++;
+}
+
+/*
+ * em28xx_vm_close()
+ */
+static void em28xx_vm_close(struct vm_area_struct *vma)
+{
+	/* NOTE: buffers are not freed here */
+	struct em28xx_frame_t *f = vma->vm_private_data;
+	f->vma_use_count--;
+}
+
+static struct vm_operations_struct em28xx_vm_ops = {
+	.open = em28xx_vm_open,
+	.close = em28xx_vm_close,
+};
+
+/*
+ * em28xx_v4l2_mmap()
+ */
+static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start,
+	    start = vma->vm_start;
+	void *pos;
+	u32 i;
+
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (fh->type == EM28XX_VBI)
+		return em28xx_v4l2_vbi_mmap(filp, vma);
+
+	if (fh->type != EM28XX_VIDEO)
+		return -EINVAL;
+
+
+	if (dev->video_reader > 0 && fh->reader == 0)
+		return -EBUSY;
+	else {
+		dev->video_reader = 1;
+		fh->reader = 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("mmap: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("mmap: Device is misconfigured; close and "
+						"open it again\n");
+		return -EIO;
+	}
+
+	if (dev->video_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*||
+	    size != PAGE_ALIGN(dev->frame[0].buf.length) */) {
+		return -EINVAL;
+	}
+
+	for (i = 0; i < dev->num_frames; i++)
+		if ((dev->frame[i].buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
+			break;
+
+	if (i == dev->num_frames) {
+		em28xx_videodbg("mmap: user supplied mapping address is out "
+				"of range\n");
+		return -EINVAL;
+	}
+
+	/* VM_IO is eventually going to replace PageReserved altogether */
+	vma->vm_flags |= VM_IO;
+	vma->vm_flags |= VM_RESERVED;	/* avoid to swap out this VMA */
+
+	pos = dev->frame[i].bufmem;
+	if (pos == 0) {
+		em28xx_videodbg("em28xx-video.c: exception pos is 0\n");
+		return -EINVAL;
+	}
+	if (dev->frame[0].buf.length == 0)
+
+		return -EINVAL;
+
+	if (dev->frame[i].buf.length == 0)
+		return -EINVAL;
+
+	while (size > 0) {	/* size is page-aligned */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		unsigned long page = vmalloc_to_pfn(pos);
+		if (remap_pfn_range(vma, start, page, PAGE_SIZE,
+				    vma->vm_page_prot)) {
+			em28xx_videodbg("mmap: rename page map failed\n");
+#else
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			em28xx_videodbg("mmap: vm_insert_page failed\n");
+#endif
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vma->vm_ops = &em28xx_vm_ops;
+	vma->vm_private_data = &dev->frame[i];
+
+	em28xx_vm_open(vma);
+	return 0;
+}
+
+static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start,
+	    start = vma->vm_start;
+	void *pos;
+	u32 i;
+
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (fh->type != EM28XX_VBI)
+		return -EINVAL;
+
+	if (dev->vbi_reader > 0 && fh->reader == 0) {
+		return -EBUSY;
+	} else {
+		dev->vbi_reader = 1;
+		fh->reader = 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("mmap: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("mmap: Device is misconfigured; close and "
+						"open it again\n");
+		return -EIO;
+	}
+
+	if (dev->vbi_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*||
+	    size != PAGE_ALIGN(dev->frame[0].buf.length) */) {
+		return -EINVAL;
+	}
+
+	for (i = 0; i < dev->vbi_num_frames; i++) {
+		if ((dev->vbi_frame[i].buf.m.offset >> PAGE_SHIFT) ==
+				vma->vm_pgoff)
+			break;
+	}
+	if (i == dev->vbi_num_frames) {
+		em28xx_videodbg("mmap: user supplied mapping address is out "
+				"of range\n");
+		return -EINVAL;
+	}
+
+	/* VM_IO is eventually going to replace PageReserved altogether */
+	vma->vm_flags |= VM_IO;
+	vma->vm_flags |= VM_RESERVED;	/* avoid to swap out this VMA */
+
+	pos = dev->vbi_frame[i].bufmem;
+	if (pos == 0)
+		return -EINVAL;
+
+	if (dev->vbi_frame[0].buf.length == 0)
+		return -EINVAL;
+
+	if (dev->vbi_frame[i].buf.length == 0)
+		return -EINVAL;
+
+	while (size > 0) {	/* size is page-aligned */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		unsigned long page = vmalloc_to_pfn(pos);
+		if (remap_pfn_range(vma, start, page, PAGE_SIZE,
+				    vma->vm_page_prot)) {
+			em28xx_videodbg("mmap: rename page map failed\n");
+#else
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			em28xx_videodbg("mmap: vm_insert_page failed\n");
+#endif
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vma->vm_ops = &em28xx_vm_ops;
+	vma->vm_private_data = &dev->vbi_frame[i];
+
+	em28xx_vm_open(vma);
+	return 0;
+}
+
+/*
+ * em28xx_get_ctrl()
+ * return the current saturation, brightness or contrast, mute state
+ */
+static int em28xx_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		ctrl->value = dev->mute;
+		return 0;
+	case V4L2_CID_AUDIO_VOLUME:
+		ctrl->value = dev->volume;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/*FIXME: should be moved to saa711x */
+static int saa711x_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	s32 tmp;
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		tmp = em28xx_brightness_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: cleaner way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_CONTRAST:
+		ctrl->value = em28xx_contrast_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	case V4L2_CID_SATURATION:
+		ctrl->value = em28xx_saturation_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	case V4L2_CID_RED_BALANCE:
+		tmp = em28xx_v_balance_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: clenaer way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_BLUE_BALANCE:
+		tmp = em28xx_u_balance_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: clenaer way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_GAMMA:
+		ctrl->value = em28xx_gamma_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * em28xx_set_ctrl()
+ * mute or set new saturation, brightness or contrast
+ */
+static int em28xx_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (ctrl->value != dev->mute) {
+			dev->mute = ctrl->value;
+			em28xx_audio_usb_mute(dev, ctrl->value);
+			return em28xx_audio_analog_set(dev);
+		}
+		return 0;
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->volume = ctrl->value;
+		return em28xx_audio_analog_set(dev);
+	default:
+		return -EINVAL;
+	}
+}
+
+/*FIXME: should be moved to saa711x */
+static int saa711x_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		return em28xx_brightness_set(dev, ctrl->value);
+	case V4L2_CID_CONTRAST:
+		return em28xx_contrast_set(dev, ctrl->value);
+	case V4L2_CID_SATURATION:
+		return em28xx_saturation_set(dev, ctrl->value);
+	case V4L2_CID_RED_BALANCE:
+		return em28xx_v_balance_set(dev, ctrl->value);
+	case V4L2_CID_BLUE_BALANCE:
+		return em28xx_u_balance_set(dev, ctrl->value);
+	case V4L2_CID_GAMMA:
+		return em28xx_gamma_set(dev, ctrl->value);
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * em28xx_stream_interrupt()
+ * stops streaming
+ */
+static int em28xx_stream_interrupt(struct em28xx *dev, int type)
+{
+	int ret = 0;
+
+	/* stop reading from the device */
+	switch (type) {
+	case EM28XX_VBI:
+		dev->vbi_stream = STREAM_INTERRUPT;
+		ret = wait_event_timeout(dev->vbi_wait_stream,
+				(dev->vbi_stream == STREAM_OFF) ||
+				(dev->state & DEV_DISCONNECTED),
+				EM28XX_URB_TIMEOUT);
+
+		if (dev->state & DEV_DISCONNECTED)
+			return -ENODEV;
+		else if (ret) {
+			dev->state |= DEV_MISCONFIGURED;
+			em28xx_videodbg("device is misconfigured; close and "
+					"open /dev/video%d again\n",
+					dev->vdev->num);
+		}
+		break;
+	case EM28XX_VIDEO:
+		dev->video_stream = STREAM_INTERRUPT;
+		ret = wait_event_timeout(dev->video_wait_stream,
+					 (dev->video_stream == STREAM_OFF) ||
+					 (dev->state & DEV_DISCONNECTED),
+					 EM28XX_URB_TIMEOUT);
+		if (dev->state & DEV_DISCONNECTED)
+			return -ENODEV;
+		else if (ret) {
+			dev->state |= DEV_MISCONFIGURED;
+			em28xx_videodbg("device is misconfigured; close and "
+				"open /dev/video%d again\n",
+					dev->vdev->num);
+			return ret;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int em28xx_set_norm(struct em28xx *dev, int width, int height)
+{
+	unsigned int hscale, vscale;
+	unsigned int maxh, maxw;
+
+	maxw = norm_maxw(dev);
+	maxh = norm_maxh(dev);
+
+	/* width must even because of the YUYV format */
+	/* height must be even because of interlacing */
+	height &= 0xfffe;
+	width &= 0xfffe;
+
+	if (height < 32)
+		height = 32;
+	if (height > maxh)
+		height = maxh;
+	if (width < 48)
+		width = 48;
+	if (width > maxw)
+		width = maxw;
+
+	hscale = (((unsigned long)maxw) << 12) / width - 4096L;
+
+	if (hscale >= 0x4000)
+		hscale = 0x3fff;
+	width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+
+	vscale = (((unsigned long)maxh) << 12) / height - 4096L;
+
+	if (vscale >= 0x4000)
+		vscale = 0x3fff;
+
+	height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+
+	/* set new image size */
+	dev->width = width;
+	dev->height = height;
+	dev->frame_size = dev->width * dev->height * 2;
+	dev->field_size = dev->frame_size >> 1;
+	dev->bytesperline = dev->width * 2;
+	dev->hscale = hscale;
+	dev->vscale = vscale;
+
+	em28xx_resolution_set(dev);
+
+	return 0;
+}
+
+static int em28xx_get_fmt(struct em28xx *dev, struct v4l2_format *format)
+{
+	em28xx_videodbg("VIDIOC_G_FMT: type = %s\n",
+		(format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+		"V4L2_BUF_TYPE_VIDEO_CAPTURE" :
+		(format->type == V4L2_BUF_TYPE_VBI_CAPTURE) ?
+		"V4L2_BUF_TYPE_VBI_CAPTURE" :
+		(format->type == V4L2_CAP_SLICED_VBI_CAPTURE) ?
+		"V4L2_BUF_TYPE_SLICED_VBI_CAPTURE " :
+		"not supported");
+
+	switch (format->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	{
+		format->fmt.pix.width = dev->width;
+		format->fmt.pix.height = dev->height;
+		format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat;
+	//	V4L2_PIX_FMT_YUYV;
+		format->fmt.pix.bytesperline = dev->bytesperline;
+		format->fmt.pix.sizeimage = dev->frame_size;
+		format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+		format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+#if 0
+		format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB;
+#endif
+
+		em28xx_videodbg("VIDIOC_G_FMT: %dx%d\n", dev->width,
+			dev->height);
+		break;
+	}
+
+	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+	{
+		format->fmt.sliced.service_set = 0;
+		em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format);
+		if (format->fmt.sliced.service_set == 0)
+			return -EINVAL;
+		break;
+	}
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+	{
+		format->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+
+		switch (dev->tvnorm->id) {
+		case V4L2_STD_NTSC_M:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+			break;
+		case V4L2_STD_PAL:
+		default:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+		}
+
+		format->fmt.vbi.samples_per_line =
+			dev->tvnorm->vbi_samples_per_line;
+		format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+		format->fmt.vbi.offset = dev->tvnorm->vbi_offset;
+		format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0;
+		format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0;
+		format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1;
+		format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1;
+		if (dev->vbi_interlaced)
+			format->fmt.vbi.flags = V4L2_VBI_INTERLACED;
+		else 
+			format->fmt.vbi.flags = 0;
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int em28xx_set_fmt(struct em28xx *dev, unsigned int cmd, struct v4l2_format *format)
+{
+	u32 i;
+	int ret = 0;
+	int width = format->fmt.pix.width;
+	int height = format->fmt.pix.height;
+	unsigned int hscale, vscale;
+	unsigned int maxh, maxw;
+
+	maxw = norm_maxw(dev);
+	maxh = norm_maxh(dev);
+
+	em28xx_videodbg("%s: type = %s\n",
+			cmd == VIDIOC_TRY_FMT ?
+			"VIDIOC_TRY_FMT" : "VIDIOC_S_FMT",
+			format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
+			"V4L2_BUF_TYPE_VIDEO_CAPTURE" :
+			format->type == V4L2_BUF_TYPE_VBI_CAPTURE ?
+			"V4L2_BUF_TYPE_VBI_CAPTURE " :
+			"not supported");
+
+	if (format->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+		em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format);
+
+		if (format->fmt.sliced.service_set == 0)
+			return -EINVAL;
+
+		return 0;
+	}
+	if (format->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+			dev->dev_modes&EM28XX_VBI) {
+		em28xx_i2c_call_clients(dev, VIDIOC_S_FMT, format);
+		format->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+		switch (dev->tvnorm->id) {
+		case V4L2_STD_NTSC_M:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+			break;
+		case V4L2_STD_PAL:
+		default:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+		}
+		format->fmt.vbi.samples_per_line =
+			dev->tvnorm->vbi_samples_per_line;
+		format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+		format->fmt.vbi.offset = dev->tvnorm->vbi_offset;
+
+		if (dev->vbi_interlaced)
+			format->fmt.vbi.flags = V4L2_VBI_INTERLACED;
+		else 
+			format->fmt.vbi.flags = 0;
+
+		format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0;
+		format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1;
+		format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0;
+		format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1;
+		dev->vbi_frame_size = 720 * 2 *
+			(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+		dev->vbi_field_size = dev->vbi_frame_size >> 1;
+
+		return 0;
+	}
+
+	if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	em28xx_videodbg("%s: requested %dx%d\n",
+		cmd == VIDIOC_TRY_FMT ?
+		"VIDIOC_TRY_FMT" : "VIDIOC_S_FMT",
+		format->fmt.pix.width, format->fmt.pix.height);
+
+	/* FIXME: Move some code away from here */
+	/* width must even because of the YUYV format */
+	/* height must be even because of interlacing */
+	height &= 0xfffe;
+	width &= 0xfffe;
+
+	if (height < 32)
+		height = 32;
+	if (height > maxh)
+		height = maxh;
+	if (width < 48)
+		width = 48;
+	if (width > maxw)
+		width = maxw;
+
+	if (dev->em_type == EM2800) {
+		/* the em2800 can only scale down to 50% */
+		if (height % (maxh / 2))
+			height = maxh;
+		if (width % (maxw / 2))
+			width = maxw;
+		/* according to empiatech support */
+		/* the MaxPacketSize is to small to support */
+		/* framesizes larger than 640x480 @ 30 fps */
+		/* or 640x576 @ 25 fps. As this would cut */
+		/* of a part of the image we prefer */
+		/* 360x576 or 360x480 for now */
+		if (width == maxw && height == maxh)
+			width /= 2;
+	}
+
+	hscale = (((unsigned long)maxw) << 12) / width - 4096L;
+	if (hscale >= 0x4000)
+		hscale = 0x3fff;
+
+	width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+
+	vscale = (((unsigned long)maxh) << 12) / height - 4096L;
+	if (vscale >= 0x4000)
+		vscale = 0x3fff;
+
+	height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+
+	format->fmt.pix.width = width;
+	format->fmt.pix.height = height;
+
+	dev->outfmt = &em28xx_out_fmt[0];
+
+	for (i = 0; i < ARRAY_SIZE(em28xx_out_fmt); i++) {
+		if (em28xx_out_fmt[i].fmt.pixelformat == format->fmt.pix.pixelformat) {
+			dev->outfmt = &em28xx_out_fmt[i];
+			break;
+		}
+	}
+		
+	format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat;
+	format->fmt.pix.bytesperline = width * 2;
+	format->fmt.pix.sizeimage = width * 2 * height;
+	format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+#if 0
+	/* available since what kernel version ? */
+	format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB;
+#endif
+
+	em28xx_videodbg("%s: returned %dx%d (%d, %d)\n",
+		cmd == VIDIOC_TRY_FMT ?
+		"VIDIOC_TRY_FMT" :"VIDIOC_S_FMT",
+		format->fmt.pix.width, format->fmt.pix.height, hscale, vscale);
+
+	if (cmd == VIDIOC_TRY_FMT) {
+		return 0;
+	}
+
+	for (i = 0; i < dev->num_frames; i++)
+		if (dev->frame[i].vma_use_count) {
+			em28xx_videodbg("VIDIOC_S_FMT failed. "
+				"Unmap the buffers first.\n");
+			return -EINVAL;
+		}
+
+	/* stop io in case it is already in progress */
+
+	/* set new image size */
+	dev->width = width;
+	dev->height = height;
+	dev->frame_size = dev->width * dev->height * 2;
+	dev->field_size = dev->frame_size >> 1;
+	dev->bytesperline = dev->width * 2;
+	dev->hscale = hscale;
+	dev->vscale = vscale;
+	dev->vbi_frame_size = 720 * 2 *
+		(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+	dev->vbi_field_size = dev->vbi_frame_size >> 1;
+
+	if (dev->video_stream == STREAM_ON) {
+		em28xx_videodbg("VIDIOC_SET_FMT: interupting video stream\n");
+		ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+		if (ret)
+			return ret;
+	}
+	if (dev->vbi_stream == STREAM_ON) {
+		em28xx_videodbg("VIDIOC_SET_FMT: interupting vbi stream\n");
+		ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+		if (ret)
+			return ret;
+	}
+
+	em28xx_release_buffers(dev, EM28XX_VIDEO);
+	em28xx_release_buffers(dev, EM28XX_VBI);
+	dev->video_io = IO_NONE;
+	dev->vbi_io = IO_NONE;
+	em28xx_uninit_isoc(dev);
+	em28xx_set_alternate(dev);
+	em28xx_capture_start(dev, 1);
+	em28xx_resolution_set(dev);
+	em28xx_init_isoc(dev);
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_do_ioctl()
+ * This function is _not_ called directly, but from
+ * em28xx_v4l2_ioctl. Userspace
+ * copying is done already, arg is a kernel pointer.
+ */
+static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
+			   struct em28xx *dev, unsigned int cmd, void *arg,
+			   v4l2_kioctl driver_ioctl)
+{
+	int ret;
+	struct em28xx_fh *fh = filp->private_data;
+
+	switch (cmd) {
+		/* ---------- tv norms ---------- */
+	case VIDIOC_ENUMSTD:
+	{
+		struct v4l2_standard *e = arg;
+		unsigned int i;
+
+		i = e->index;
+		if (i >= MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			return -EINVAL;
+
+		ret = v4l2_video_std_construct(e,
+				dev->board->tvnorms[e->index].id,
+				dev->board->tvnorms[e->index].name);
+
+		e->index = i;
+		if (ret < 0)
+			return ret;
+		return 0;
+	}
+	case VIDIOC_G_STD:
+	{
+		v4l2_std_id *id = arg;
+		*id = dev->tvnorm->id;
+		return 0;
+	}
+	case VIDIOC_S_STD:
+	{
+		v4l2_std_id *id = arg;
+		unsigned int i;
+		int switch_std = 0;
+
+		for (i = 0; i < MAX_EM28XX_TVNORMS &&
+				dev->board->tvnorms[i].id; i++)
+			if (dev->board->tvnorms[i].id == *id)
+				break;
+
+		if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			for (i = 0; i < MAX_EM28XX_TVNORMS &&
+					dev->board->tvnorms[i].id; i++)
+				if ((*id & dev->board->tvnorms[i].id) &&
+						dev->board->tvnorms[i].id != 0)
+					break;
+
+		if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			return -EINVAL;
+
+		if (((dev->tvnorm->id & V4L2_STD_SECAM) &&
+			(dev->board->tvnorms[i].id & V4L2_STD_SECAM) == 0) ||
+		   (((dev->tvnorm->id & V4L2_STD_SECAM) == 0) &&
+		    (dev->board->tvnorms[i].id & V4L2_STD_SECAM))) {
+			switch_std = 1;
+		}
+
+		if (((dev->tvnorm->id & V4L2_STD_625_50) &&
+		    ((dev->board->tvnorms[i].id & V4L2_STD_625_50) == 0)) ||
+		      (((dev->tvnorm->id & V4L2_STD_625_50) == 0) &&
+		      (dev->board->tvnorms[i].id & V4L2_STD_625_50))) {
+			if (dev->vbi_stream == STREAM_ON ||
+			    dev->video_stream == STREAM_ON) {
+				printk(KERN_INFO"em28xx: don't switch the standard "
+						"while the device is capturing");
+				return -EBUSY;
+			}
+			dev->vbi_frame_size = 720 * 2 *
+				(dev->board->tvnorms[i].vbi_count_0 + dev->board->tvnorms[i].vbi_count_1);
+			dev->vbi_field_size = dev->vbi_frame_size >> 1;
+		}
+
+		dev->tvnorm = &dev->board->tvnorms[i];
+
+		if (dev->dev_modes & EM28XX_VBI)
+			em28xx_set_vbi(dev, 1);
+
+		em28xx_set_norm(dev, dev->width, dev->height);
+
+		if (dev->has_inttuner) {
+			switch (dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+			{
+				struct xc3028_init_cmd cmd;
+				int gpio_arg;
+				cmd.new_tv_mode_ptr = dev->tvnorm->tv_mode;
+				cmd.new_channel_map_ptr =
+					dev->tvnorm->channelmap;
+
+				if (switch_std) {
+					if (*id & V4L2_STD_SECAM)
+						gpio_arg = EM28XX_REG_ON;
+					else
+						gpio_arg = EM28XX_REG_OFF;
+
+					em28xx_gpio_control(dev,
+							EM28XX_XC3028_SECAM,
+							&gpio_arg);
+				}
+
+				if (dev->tuner && dev->tuner->tuner_cmd)
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+				break;
+			}
+			case TUNER_XCEIVE_XC5000:
+			{
+				struct xc_std_conf cmd;
+				cmd.index = dev->tvnorm->index;
+				dev->tuner->tuner_cmd(dev->tuner,
+						XC5000_SET_MODE,
+						&cmd);
+				break;
+			}
+			default:
+				break;
+			}
+		}
+
+		em28xx_i2c_call_clients(dev, VIDIOC_S_STD,
+				&dev->tvnorm->id);
+
+		return 0;
+	}
+
+	/* ------ input switching ---------- */
+	case VIDIOC_ENUMINPUT:
+	{
+		struct v4l2_input *i = arg;
+		unsigned int n;
+		static const char *iname[] = {
+			[EM28XX_VMUX_COMPOSITE1] = "Composite1",
+			[EM28XX_VMUX_COMPOSITE2] = "Composite2",
+			[EM28XX_VMUX_COMPOSITE3] = "Composite3",
+			[EM28XX_VMUX_COMPOSITE4] = "Composite4",
+			[EM28XX_VMUX_SVIDEO] = "S-Video",
+			[EM28XX_VMUX_TELEVISION] = "Television",
+			[EM28XX_VMUX_CABLE] = "Cable TV",
+			[EM28XX_VMUX_DVB] = "DVB",
+			[EM28XX_VMUX_DEBUG] = "for debug only",
+		};
+
+		n = i->index;
+		if (n >= MAX_EM28XX_INPUT)
+			return -EINVAL;
+		if (0 == INPUT(n)->type)
+			return -EINVAL;
+		memset(i, 0, sizeof(*i));
+		i->index = n;
+		i->type = V4L2_INPUT_TYPE_CAMERA;
+		strcpy(i->name, iname[INPUT(n)->type]);
+		if ((EM28XX_VMUX_TELEVISION == INPUT(n)->type) ||
+			(EM28XX_VMUX_CABLE == INPUT(n)->type))
+			i->type = V4L2_INPUT_TYPE_TUNER;
+		for (n = 0; dev->board->tvnorms[n].id; n++)
+			i->std |= dev->board->tvnorms[n].id;
+		return 0;
+	}
+	case VIDIOC_G_INPUT:
+	{
+		int *i = arg;
+		*i = dev->ctl_input;
+
+		return 0;
+	}
+	case VIDIOC_S_INPUT:
+	{
+		int *index = arg;
+
+		if (*index >= MAX_EM28XX_INPUT)
+			return -EINVAL;
+
+		if (0 == INPUT(*index)->type)
+			return -EINVAL;
+
+		video_mux(dev, *index);
+
+		return 0;
+	}
+	case VIDIOC_G_AUDIO:
+	{
+		struct v4l2_audio *a = arg;
+		unsigned int index = a->index;
+
+		if (a->index > 1)
+			return -EINVAL;
+		memset(a, 0, sizeof(*a));
+		index = dev->ctl_ainput;
+
+		if (index == 0)
+			strcpy(a->name, "Television");
+		else
+			strcpy(a->name, "Line In");
+
+		a->capability = V4L2_AUDCAP_STEREO;
+		a->index = index;
+		return 0;
+	}
+	case VIDIOC_S_AUDIO:
+	{
+		struct v4l2_audio *a = arg;
+
+		if (a->index != dev->ctl_ainput)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* --- controls ---------------------------------------------- */
+	case VIDIOC_QUERYCTRL:
+	{
+		struct v4l2_queryctrl *qc = arg;
+		int i, id = qc->id;
+		int retval;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		memset(qc, 0, sizeof(*qc));
+		qc->id = id;
+
+		if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (qc->id && qc->id == em28xx_qctrl[i].id) {
+					memcpy(qc, &(em28xx_qctrl[i]),
+					sizeof(*qc));
+					return 0;
+				}
+			}
+		}
+		if (dev->em28xx_qctrl) {
+			retval = dev->em28xx_qctrl(arg);
+			if (retval == 0)
+				return 0;
+		}
+
+		em28xx_i2c_call_clients(dev, cmd, qc);
+
+		if (qc->type)
+			return 0;
+		else
+			return -EINVAL;
+		break;
+	}
+	case VIDIOC_G_CTRL:
+	{
+		struct v4l2_control *ctrl = arg;
+		int retval = -EINVAL;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		if (!dev->has_msp34xx)
+			retval = em28xx_get_ctrl(dev, ctrl);
+		if (retval == -EINVAL) {
+			if (dev->decoder == EM28XX_TVP5150) {
+				em28xx_i2c_call_clients(dev, cmd, arg);
+				return 0;
+			}
+
+			return saa711x_get_ctrl(dev, ctrl);
+		} else if (dev->em28xx_gctrl)
+			return dev->em28xx_ctrl(dev, arg);
+		else
+			return retval;
+	}
+	case VIDIOC_S_CTRL:
+	{
+		struct v4l2_control *ctrl = arg;
+		u8 i;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (ctrl->id == em28xx_qctrl[i].id) {
+					if (ctrl->value <
+					em28xx_qctrl[i].minimum
+					|| ctrl->value >
+					em28xx_qctrl[i].maximum)
+						return -ERANGE;
+					return em28xx_set_ctrl(dev, ctrl);
+				}
+			}
+		}
+
+		if (dev->decoder == EM28XX_TVP5150) {
+			em28xx_i2c_call_clients(dev, cmd, arg);
+			return 0;
+		} else if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (ctrl->id == em28xx_qctrl[i].id) {
+					if (ctrl->value <
+					em28xx_qctrl[i].minimum
+					|| ctrl->value >
+					em28xx_qctrl[i].maximum)
+						return -ERANGE;
+					return em28xx_set_ctrl(dev, ctrl);
+				}
+			}
+			for (i = 0; i < ARRAY_SIZE(saa711x_qctrl); i++) {
+				if (ctrl->id == saa711x_qctrl[i].id) {
+					if (ctrl->value <
+					saa711x_qctrl[i].minimum
+					|| ctrl->value >
+					saa711x_qctrl[i].maximum)
+						return -ERANGE;
+					return saa711x_set_ctrl(dev, ctrl);
+				}
+			}
+		}
+
+		return -EINVAL;
+	}
+	/* --- tuner ioctls ------------------------------------------ */
+	case VIDIOC_G_TUNER:
+	{
+		struct v4l2_tuner *t = arg;
+		static unsigned int tv_range[2] = { 44, 958 };
+		unsigned short int lock_status = 0;
+
+		if (0 != t->index)
+			return -EINVAL;
+
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			t->type = V4L2_TUNER_RADIO;
+			/* em2882 + cx2584x + xc5000 uses the videodecoder
+			   for radio detection */
+			em28xx_i2c_call_clients(dev, cmd, t);
+			break;
+		case EM28XX_VBI:
+		case EM28XX_VIDEO:
+			memset(t, 0, sizeof(*t));
+			strcpy(t->name, "Tuner");
+			t->type = V4L2_TUNER_ANALOG_TV;
+			t->capability |= V4L2_TUNER_CAP_NORM;
+			t->rangelow = tv_range[0] * 16;
+			t->rangehigh = tv_range[1] * 16;
+
+			if (dev->tuner && dev->tuner->get_lock_status) {
+				dev->tuner->get_lock_status(dev->tuner, &lock_status);
+				if (lock_status == 1)
+					t->signal = 0xffff;
+			} else
+				em28xx_i2c_call_clients(dev, cmd, t);
+
+			em28xx_videodbg("VIDIO_G_TUNER: signal = %x, afc = %x\n", t->signal,
+					t->afc);
+		}
+		return 0;
+	}
+	case VIDIOC_S_TUNER:
+	{
+		struct v4l2_tuner *t = arg;
+
+		if (0 != t->index)
+			return -EINVAL;
+		/* there are only a very few devices with more than one tuner
+		   available, this can be updated to work correctly later */
+		return 0;
+	}
+	case VIDIOC_G_FREQUENCY:
+	{
+		struct v4l2_frequency *f = arg;
+
+		memset(f, 0, sizeof(*f));
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			f->type = V4L2_TUNER_RADIO;
+			f->frequency = dev->rctl_freq;
+			break;
+		case EM28XX_VIDEO:
+		case EM28XX_VBI:
+			f->type = V4L2_TUNER_ANALOG_TV;
+			f->frequency = dev->vctl_freq;
+			break;
+		}
+		return 0;
+	}
+	case VIDIOC_S_FREQUENCY:
+	{
+		struct v4l2_frequency *f = arg;
+
+		if (0 != f->tuner)
+			return -EINVAL;
+
+		if ((fh->type == EM28XX_RADIO && f->type != V4L2_TUNER_RADIO) ||
+			(fh->type == EM28XX_VIDEO && f->type != V4L2_TUNER_ANALOG_TV))
+			return -EINVAL;
+
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			dev->rctl_freq = f->frequency;
+			break;
+		case EM28XX_VIDEO:
+		case EM28XX_VBI:
+			dev->vctl_freq = f->frequency;
+			break;
+		}
+
+		if (dev->tuner) {
+			switch (fh->type) {
+			case EM28XX_VBI:
+			case EM28XX_VIDEO:
+				dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency*1000/16*1000);
+				break;
+			case EM28XX_RADIO:
+				dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency/16*1000);
+				break;
+			}
+		} else
+			em28xx_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, f);
+
+		return 0;
+	}
+#if 0				/* ioctl is optional */
+	case VIDIOC_G_PARM:
+	{
+		struct v4l2_captureparm *parm = arg;
+		memset(parm, 0, sizeof(*parm));
+		return 0;
+	}
+#endif
+	case VIDIOC_CROPCAP:
+	{
+		struct v4l2_cropcap *cc = arg;
+
+		if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+			return -EINVAL;
+		cc->bounds.left = 0;
+		cc->bounds.top = 0;
+		cc->bounds.width = dev->width;
+		cc->bounds.height = dev->height;
+		cc->defrect = cc->bounds;
+		cc->pixelaspect.numerator = 54;	/* 4:3 FIXME: remove magic numbers */
+		cc->pixelaspect.denominator = 59;
+		return 0;
+	}
+	case VIDIOC_STREAMON:
+	{
+		int *type = arg;
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_stream = STREAM_ON;
+			return 0;
+		}
+
+		if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+#if 0
+		if (list_empty(&dev->inqueue))
+			return -EINVAL;
+#endif
+
+		dev->video_stream = STREAM_ON;
+
+		em28xx_videodbg("VIDIOC_STREAMON: starting stream\n");
+
+		return 0;
+
+	}
+	case VIDIOC_STREAMOFF:
+	{
+		int *type = arg;
+		int ret;
+
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_reader = 0;
+			fh->reader = 0;
+			if (dev->vbi_stream == STREAM_ON) {
+				em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n");
+				ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+				if (ret)
+					return ret;
+			}
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			dev->vbi_stream = STREAM_OFF;
+			return 0;
+		}
+
+		if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			if (ret)
+				return ret;
+		}
+		dev->video_reader = 0;
+		dev->video_stream = STREAM_OFF;
+		fh->reader = 0;
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+
+		return 0;
+	}
+	default:
+		return v4l_compat_translate_ioctl(inode, filp, cmd, arg,
+						  driver_ioctl);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_do_ioctl()
+ * This function is _not_ called directly, but from
+ * em28xx_v4l2_ioctl. Userspace
+ * copying is done already, arg is a kernel pointer.
+ */
+static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp,
+				 unsigned int cmd, void *arg)
+{
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (!dev)
+		return -ENODEV;
+
+	if (video_debug > 1)
+		v4l_print_ioctl(dev->name, cmd);
+
+	switch (cmd) {
+
+		/* --- capabilities ------------------------------------------ */
+	case VIDIOC_QUERYCAP:
+		{
+		struct v4l2_capability *cap = arg;
+
+		memset(cap, 0, sizeof(*cap));
+		strlcpy(cap->driver, "em28xx", sizeof(cap->driver));
+		strlcpy(cap->card, em28xx_boards[dev->model].name,
+			sizeof(cap->card));
+		strlcpy(cap->bus_info, dev->udev->dev.bus_id,
+			sizeof(cap->bus_info));
+		cap->version = EM28XX_VERSION_CODE;
+		cap->capabilities =
+#if 0
+				V4L2_CAP_SLICED_VBI_CAPTURE |
+#endif
+				V4L2_CAP_VIDEO_CAPTURE |
+				V4L2_CAP_AUDIO |
+				V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+		if (dev->has_tuner)
+			cap->capabilities |= V4L2_CAP_TUNER;
+		if (dev->dev_modes & EM28XX_RADIO)
+			cap->capabilities |= V4L2_CAP_TUNER;
+		if (dev->dev_modes & EM28XX_VBI)
+			cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
+		return 0;
+	}
+	/* --- capture ioctls ---------------------------------------- */
+	case VIDIOC_ENUM_FMT:
+	{
+		struct v4l2_fmtdesc *fmtd = arg;
+		int index = fmtd->index;
+
+		if (fmtd->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+			return -EINVAL;
+
+		if (index >= ARRAY_SIZE(em28xx_out_fmt))
+			return -EINVAL;
+
+		memset(fmtd, 0, sizeof(*fmtd));
+		fmtd->index = index;
+		fmtd->type = em28xx_out_fmt[index].fmt.type;
+		strcpy(fmtd->description, em28xx_out_fmt[index].fmt.description);
+		fmtd->pixelformat = em28xx_out_fmt[index].fmt.pixelformat;
+		return 0;
+	}
+	case VIDIOC_G_FMT:
+		return em28xx_get_fmt(dev, (struct v4l2_format *) arg);
+
+	case VIDIOC_TRY_FMT:
+	case VIDIOC_S_FMT:
+	{
+		struct v4l2_format *fmt = arg;
+		if ((dev->video_reader == 1 && fh->reader == 0) ||
+		   (dev->vbi_reader == 1 && fh->reader == 0)) {
+
+			/* this is something to struggle around however you need it
+			   mplayer tries to set 640x480 and ongoing formats till
+			   it hits 720x576 if requested, if the first try will fail
+			   the ongoing tries will also fail and mplayer will not
+			   work with the em28xx driver
+
+			   the enabled code is mplayer aware, commented out code
+			   would be a small workaround not perfect either but
+			   slightly better.
+			 */
+			if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+				fmt->fmt.pix.height = dev->height;
+				fmt->fmt.pix.width = dev->width;
+				return 0;
+#if 0
+				if (dev->width == (fmt->fmt.pix.width & 0xfffe) &&
+						dev->height == (fmt->fmt.pix.height & 0xfffe)) {
+					fmt->fmt.pix.height &= 0xfffe;
+					fmt->fmt.pix.width &= 0xfffe;
+					printk(KERN_INFO"everything ok not changing anything and returning 0\n");
+					return 0;
+				}
+#endif
+			}
+			printk("em28xx-video.c: device is currently busy!\n");
+			return -EBUSY;
+		}
+		return em28xx_set_fmt(dev, cmd, (struct v4l2_format *)arg);
+	}
+
+	/* obsolete v4l1 implementation, but it's still used by some applications -
+	 * it should be implemented but we don't support it
+	 */
+
+	case VIDIOCGMBUF:
+	{
+		struct video_mbuf *mbuf = arg;
+		int i;
+
+		int gbuffers = EM28XX_NUM_READ_FRAMES;
+		int gbufsize = PAGE_ALIGN(dev->frame_size); /* max resolution of em28xx based devices */
+
+		if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+			return -EINVAL;
+		}
+
+		if (dev->video_io != IO_NONE) {
+			em28xx_videodbg("method is set to read;"
+				" close and open the device again to"
+				" choose the mmap I/O method\n");
+			printk(KERN_INFO"VIDEO_IO is busy\n");
+			return -EBUSY;
+		}
+
+		memset(mbuf, 0, sizeof(*mbuf));
+		mbuf->frames = gbuffers;
+
+		mbuf->size = gbufsize * gbuffers;
+		for (i = 0; i < gbuffers; i++) {
+			mbuf->offsets[i] = i*gbufsize;
+		}
+
+		/* FIXME: setting resolution using the v4l1 PICT command */
+
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+
+		if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES, EM28XX_VIDEO)) {
+			printk(KERN_INFO"em28xx-video.c: unable to map"
+					" buffers\n");
+			return -ENOMEM;
+		}
+
+		dev->video_stream = STREAM_ON;
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		dev->frame_current = NULL;
+		dev->video_io = IO_MMAP;
+		return 0;
+	}
+	case VIDIOCMCAPTURE:
+	{
+		struct video_mmap *vm = arg;
+		unsigned long lock_flags;
+
+		if (vm->format != VIDEO_PALETTE_YUV422)
+			return -EINVAL;
+
+		if (dev->frame[vm->frame].state != F_UNUSED) {
+			return -EAGAIN;
+		}
+
+		dev->frame[vm->frame].state = F_QUEUED;
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		list_add_tail(&dev->frame[vm->frame].frame, &dev->inqueue);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+		return 0;
+	}
+	case VIDIOCSYNC:
+	{
+		/* int *frame = arg; FIXME - argument not used?*/
+		struct em28xx_frame_t *f;
+		unsigned long lock_flags;
+		int ret = 0;
+
+		if (list_empty(&dev->outqueue)) {
+			if (dev->video_stream == STREAM_OFF) {
+				return -EINVAL;
+			}
+			if (filp->f_flags & O_NONBLOCK) {
+				return -EAGAIN;
+			}
+			ret = wait_event_interruptible(
+					dev->wait_frame,
+					(!list_empty(&dev->outqueue)) ||
+					(dev->state&DEV_DISCONNECTED));
+			if (ret) {
+				return ret;
+			}
+			if (dev->state & DEV_DISCONNECTED) {
+				return -ENODEV;
+			}
+		}
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		f = list_entry(dev->outqueue.next, struct em28xx_frame_t, frame);
+		list_del(dev->outqueue.next);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+		f->state = F_UNUSED;
+		return 0;
+	}
+
+	/* end v4l1 compat code */
+
+	case VIDIOC_REQBUFS:
+	{
+		struct v4l2_requestbuffers *rb = arg;
+		u32 i;
+		int ret;
+
+		if (rb->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				rb->memory == V4L2_MEMORY_MMAP) {
+			if (dev->vbi_reader == 1 && fh->reader == 0)
+				return -EBUSY;
+
+			if (dev->vbi_io == IO_READ) {
+				printk("method is set to read;"
+				       " close and open the device again to"
+				       " choose the mmap I/O method\n");
+				return -EBUSY;
+			}
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+
+			if (dev->vbi_stream == STREAM_ON) {
+				em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n");
+				ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+				if (ret) {
+					printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n");
+					return ret;
+				}
+			}
+
+			em28xx_release_buffers(dev, EM28XX_VBI);
+
+			if (rb->count)
+				rb->count = em28xx_request_buffers(dev, rb->count, EM28XX_VBI);
+
+			dev->vbi_frame_current = NULL;
+
+			dev->vbi_stream = STREAM_ON;
+			em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n",
+					rb->count);
+
+			dev->vbi_io = rb->count ? IO_MMAP : IO_NONE;
+			fh->reader = 1;
+			dev->vbi_reader = 1;
+			return 0;
+		}
+
+		if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			rb->memory != V4L2_MEMORY_MMAP) {
+			return -EINVAL;
+		}
+
+		if (dev->video_io == IO_READ) {
+			em28xx_videodbg("method is set to read;"
+				" close and open the device again to"
+				" choose the mmap I/O method\n");
+			printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 2\n");
+			return -EBUSY;
+		}
+
+		for (i = 0; i < dev->num_frames; i++)
+			if (dev->frame[i].vma_use_count) {
+				em28xx_videodbg("VIDIOC_REQBUFS failed; previous buffers are still mapped\n");
+				printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 3\n");
+				return -EINVAL;
+			}
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			if (ret) {
+				printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n");
+				return ret;
+			}
+		}
+
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+		if (rb->count)
+			rb->count =
+				em28xx_request_buffers(dev, rb->count, EM28XX_VIDEO);
+
+		dev->frame_current = NULL;
+
+		dev->video_stream = STREAM_ON;
+		em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n",
+				rb->count);
+		dev->video_io = rb->count ? IO_MMAP : IO_NONE;
+		return 0;
+	}
+	case VIDIOC_QUERYBUF:
+	{
+		struct v4l2_buffer *b = arg;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			memcpy(b, &dev->vbi_frame[b->index].buf, sizeof(*b));
+
+			if (dev->vbi_frame[b->index].vma_use_count)
+				b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+			if (dev->frame[b->index].state == F_DONE)
+				b->flags |= V4L2_BUF_FLAG_DONE;
+			else if (dev->frame[b->index].state != F_UNUSED)
+				b->flags |= V4L2_BUF_FLAG_QUEUED;
+
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			b->index >= dev->num_frames || dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		memcpy(b, &dev->frame[b->index].buf, sizeof(*b));
+
+		if (dev->frame[b->index].vma_use_count)
+			b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+		if (dev->frame[b->index].state == F_DONE)
+			b->flags |= V4L2_BUF_FLAG_DONE;
+		else if (dev->frame[b->index].state != F_UNUSED)
+			b->flags |= V4L2_BUF_FLAG_QUEUED;
+
+		return 0;
+	}
+	case VIDIOC_QBUF:
+	{
+		struct v4l2_buffer *b = arg;
+		unsigned long lock_flags;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			if (dev->vbi_frame[b->index].state != F_UNUSED)
+				return -EAGAIN;
+
+			dev->vbi_frame[b->index].state = F_QUEUED;
+
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			list_add_tail(&dev->vbi_frame[b->index].frame,
+					&dev->vbi_inqueue);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			b->index >= dev->num_frames || dev->video_io != IO_MMAP) {
+			return -EINVAL;
+		}
+
+		if (dev->frame[b->index].state != F_UNUSED)
+			return -EAGAIN;
+
+		dev->frame[b->index].state = F_QUEUED;
+
+		/* add frame to fifo */
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		list_add_tail(&dev->frame[b->index].frame,
+				&dev->inqueue);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+		return 0;
+	}
+	case VIDIOC_DQBUF:
+	{
+		struct v4l2_buffer *b = arg;
+		struct em28xx_frame_t *f;
+		unsigned long lock_flags;
+		int ret = 0;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			if (list_empty(&dev->vbi_outqueue)) {
+				if (dev->vbi_stream == STREAM_OFF)
+					return -EINVAL;
+				if (filp->f_flags & O_NONBLOCK)
+					return -EAGAIN;
+				ret = wait_event_interruptible(dev->wait_vbi_frame, (!list_empty(&dev->vbi_outqueue)) ||
+						(dev->state & DEV_DISCONNECTED));
+
+				if (ret)
+					return ret;
+
+				if (dev->state & DEV_DISCONNECTED)
+					return -ENODEV;
+			}
+
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			f = list_entry(dev->vbi_outqueue.next,
+					struct em28xx_frame_t, frame);
+			list_del(dev->vbi_outqueue.next);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+			f->state = F_UNUSED;
+			memcpy(b, &f->buf, sizeof(*b));
+
+			if (f->vma_use_count)
+				b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		if (list_empty(&dev->outqueue)) {
+			if (dev->video_stream == STREAM_OFF)
+				return -EINVAL;
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+			ret = wait_event_interruptible
+				(dev->wait_frame,
+				(!list_empty(&dev->outqueue)) ||
+				(dev->state & DEV_DISCONNECTED));
+			if (ret)
+				return ret;
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+		}
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		f = list_entry(dev->outqueue.next,
+				struct em28xx_frame_t, frame);
+		list_del(dev->outqueue.next);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+		f->state = F_UNUSED;
+		memcpy(b, &f->buf, sizeof(*b));
+
+		if (f->vma_use_count)
+			b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+		return 0;
+	}
+	default:
+		return em28xx_do_ioctl(inode, filp, dev, cmd, arg,
+				       em28xx_video_do_ioctl);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_ioctl()
+ * handle v4l2 ioctl the main action happens in em28xx_v4l2_do_ioctl()
+ */
+static int em28xx_v4l2_ioctl(struct inode *inode, struct file *filp,
+			     unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_errdev("v4l2 ioctl: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_errdev
+		    ("v4l2 ioctl: device is misconfigured; close and open it again\n");
+		return -EIO;
+	}
+
+	ret = video_usercopy(inode, filp, cmd, arg, em28xx_video_do_ioctl);
+
+	return ret;
+}
+
+static struct file_operations em28xx_v4l_fops = {
+	.owner = THIS_MODULE,
+	.open = em28xx_v4l2_open,
+	.release = em28xx_v4l2_close,
+	.ioctl = em28xx_v4l2_ioctl,
+	.read = em28xx_v4l2_read,
+	.poll = em28xx_v4l2_poll,
+	.mmap = em28xx_v4l2_mmap,
+	.llseek = no_llseek,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
+#if 0
+	.compat_ioctl   = v4l_compat_ioctl32,
+#endif
+#endif
+
+};
+
+int em28xx_aad_control(void *priv, unsigned int command, void *ptr) {
+    struct em28xx *dev = (struct em28xx*)priv;
+    int ret = 0;
+    switch(command) {
+    case EM28XX_ENABLE_AUDIO:
+        ret = dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+	if(dev->alt == 0 ) {
+		int errCode;
+		dev->alt = 7;
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, 7);
+		printk("changing alternate number to 7\n");
+	}
+        break;
+    case EM28XX_DISABLE_AUDIO:
+        if(dev->alt != 0) {
+            int errCode;
+            dev->alt = 0;
+            errCode = usb_set_interface(dev->udev, dev->usb_interface, 0);
+            printk("changing alternate number to 0\n");
+        }
+        break;
+    default:
+        break;
+    }
+    return ret;
+}
+
+/******************************** usb interface *****************************************/
+
+/*
+ * em28xx_init_dev()
+ * allocates and inits the device structs, registers i2c bus and v4l device
+ */
+static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
+			   int minor, int model)
+{
+	struct em28xx *dev = *devhandle;
+	int retval = -ENOMEM;
+	int errCode, i;
+	unsigned int maxh, maxw;
+	int ret;
+	int gpio_arg;
+	struct list_head *pos = NULL;
+	struct em28xx_ops *ops = NULL;
+	u8 value;
+
+	dev->model = model;
+	dev->dvb_dev = NULL;
+	init_waitqueue_head(&dev->open);
+
+	mutex_init(&dev->input_lock);
+
+	dev->em_type = em28xx_boards[model].em_type;
+	dev->has_tuner = em28xx_boards[model].has_tuner;
+	dev->has_inttuner = em28xx_boards[model].has_inttuner;
+	dev->powersaving = em28xx_boards[model].powersaving;
+	dev->has_msp34xx = em28xx_boards[model].has_msp34xx;
+	dev->tda9887_conf = em28xx_boards[model].tda9887_conf;
+	dev->decoder = em28xx_boards[model].decoder;
+	dev->em28xx_ctrl = em28xx_boards[model].ctrl;
+	dev->em28xx_qctrl = em28xx_boards[model].qctrl;
+	dev->em28xx_gctrl = em28xx_boards[model].gctrl;
+	dev->dev_modes = em28xx_boards[model].dev_modes;
+	dev->board = &em28xx_boards[model];
+	dev->outfmt = &em28xx_out_fmt[0]; /* default to YUYV */
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	INIT_DELAYED_WORK(&dev->request_module_wk, request_module_async);
+#endif
+	dev->tvnorm = &em28xx_boards[model].tvnorms[0];
+	dev->dvbnorm = &em28xx_boards[model].dvbnorms[0];
+	dev->fmnorm = &em28xx_boards[model].fmnorms[0];
+	dev->qamnorm = &em28xx_boards[model].qamnorms[0];
+	dev->atscnorm = &em28xx_boards[model].atscnorms[0];
+
+	dev->vbi_interlaced = vbi_interlaced;
+
+	dev->em28xx_gpio_control = em28xx_gpio_control;
+	dev->manual_gpio = em28xx_boards[model].manual_gpio;
+        dev->em28xx_aad_control = em28xx_aad_control;
+
+	/* check if gpio config is stored onboard */
+	dev->qamnorm = &em28xx_boards[model].qamnorms[0];
+	dev->atscnorm = &em28xx_boards[model].atscnorms[0];
+
+	em28xx_write_regs_req(dev, 0x02, 0xa0, "\x23", 1);
+	ret = dev->em28xx_read_reg(dev, 0x05);
+
+	if (ret == 0) {
+		value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+		/* keep it overridable */
+		if (dev->manual_gpio == 0)
+			dev->manual_gpio = (em28xx_read_reg_req(dev, 0x2, 0xa0)>>3)&1;
+	}
+
+
+	if (dev->udev == NULL)
+		dev->udev = udev;
+
+	if (dev->dev_modes == EM28XX_DVBT || dev->dev_modes == EM28XX_ATSC)
+		dev->device_mode = 1;
+	else
+		dev->device_mode = device_mode;
+
+	if (vbi_mode == 0)
+		dev->dev_modes &= ~EM28XX_VBI;
+
+	if (dev->dev_modes & EM28XX_VBI)
+		em28xx_set_vbi(dev, 1);
+
+	if (tuner >= 0)
+		dev->tuner_type = tuner;
+	else
+		dev->tuner_type = em28xx_boards[model].tuner_type;
+
+	/* Do board specific init and eeprom reading */
+	ret = em28xx_card_setup(dev);
+
+	if (ret)
+		return ret;
+
+	gpio_arg = EM28XX_REG_OFF;
+	em28xx_gpio_control(dev, EM28XX_LED1_ON,      &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TUNER2_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+	mdelay(100);
+	gpio_arg = EM28XX_REG_ON;
+	em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+	mdelay(100);
+
+	em28xx_gpio_control(dev, EM28XX_DECODER_RESET, NULL);
+
+#ifdef CONFIG_MODULES
+	/* request some modules */
+	if (dev->has_tuner && dev->has_inttuner == 0)
+		request_module("tuner");
+	if (dev->decoder == EM28XX_SAA7113 || dev->decoder == EM28XX_SAA7114)
+		request_module("saa7115");
+	if (dev->decoder == EM28XX_TVP5150)
+		request_module("tvp5150");
+	if (dev->tda9887_conf)
+		request_module("tda9887");
+	if (dev->decoder == EM28XX_CX25843)
+		request_module("em28xx-cx25843");
+#endif
+	spin_lock_init(&dev->queue_lock);
+	spin_lock_init(&dev->vbi_queue_lock);
+	init_waitqueue_head(&dev->wait_frame);
+	init_waitqueue_head(&dev->wait_vbi_frame);
+	init_waitqueue_head(&dev->video_wait_stream);
+	init_waitqueue_head(&dev->vbi_wait_stream);
+
+	if (ret < 0) {
+		printk("%s:%u:%s(): em28xx_card_setup() failed, ret = %i\n", __FILE__, __LINE__, __FUNCTION__, ret);
+		return ret;
+	}
+
+	em28xx_i2c_register(dev);
+
+	em28xx_config_i2c(dev);
+
+	if (dev->has_inttuner) {
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_config config;
+			memset(&config, 0x0, sizeof(struct xc3028_config));
+			config.new_tv_mode_ptr = em28xx_boards[model].tvnorms[0].tv_mode;
+			config.new_channel_map_ptr = em28xx_boards[model].tvnorms[0].channelmap;
+			config.callback = em28xx_gpio_control_translate;
+			config.dev = dev;
+			config.adap = &dev->i2c_adap;
+			config.i2c_address = 0xc2>>1;
+#if 0
+			em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, arg);
+#endif
+			dev->tuner = tuner_chip_attach(xc3028_tuner_attach, &config);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc5000_config config;
+			printk("trying to attach xc5000\n");
+			memset(&config, 0x0, sizeof(struct xc5000_config));
+			config.callback = em28xx_gpio_control_translate;
+			config.dev = dev;
+			config.adap = &dev->i2c_adap;
+			config.i2c_address = 0xc2>>1;
+			dev->tuner = tuner_chip_attach(xc5000_tuner_attach, &config, XC5000_firmware_SEQUENCE);
+			break;
+		}
+		}
+
+		/* configure the device */
+
+		if (dev->tuner == NULL)
+			printk(KERN_INFO"unable to attach tuner\n");
+		else
+			printk(KERN_INFO"successfully attached tuner\n");
+	}
+
+	dev->em28xx_acquire = em28xx_acquire;
+
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 1);
+
+	list_add_tail(&dev->devlist, &em28xx_devlist);
+
+	if (dev->dev_modes&EM28XX_RADIO) {
+		dev->rdev = video_device_alloc();
+		memset(dev->rdev, 0x0, sizeof(struct video_device));
+
+		if (NULL == dev->rdev) {
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENOMEM;
+		}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+		dev->rdev->type = VID_TYPE_TUNER;
+		dev->rdev->dev = &dev->udev->dev;
+#else
+		dev->rdev->vfl_type = VID_TYPE_TUNER;
+		dev->rdev->parent = &dev->udev->dev;
+#endif
+		dev->rdev->fops = &em28xx_v4l_fops;
+		dev->rdev->minor = -1;
+		dev->rdev->release = video_device_release;
+		snprintf(dev->rdev->name, sizeof(dev->rdev->name), "%s#%d %s",
+				"em28xx", dev->devno, "radio");
+
+		if (video_register_device(dev->rdev, VFL_TYPE_RADIO,
+					radio_nr[dev->devno]) < 0) {
+			printk(KERN_INFO"unable to register radio device\n");
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENODEV;
+		}
+		printk(KERN_INFO"radio device registered as /dev/radio%d\n",
+					dev->rdev->minor & 0x1f);
+	}
+
+	if (dev->dev_modes&EM28XX_VIDEO) {
+
+		dev->video_inputs = em28xx_boards[model].vchannels;
+
+		dev->tvnorm = &em28xx_boards[model].tvnorms[0];	/* set default norm */
+		for (i = 0; em28xx_boards[model].tvnorms[i].id; i++)
+			if (em28xx_boards[model].norm == em28xx_boards[model].tvnorms[i].id) {
+				dev->tvnorm = &em28xx_boards[model].tvnorms[i];	/* set default norm */
+				break;
+			}
+
+		em28xx_videodbg("tvnorm = %s\n", dev->tvnorm->name);
+
+		maxw = norm_maxw(dev);
+		maxh = norm_maxh(dev);
+
+		/* set default image size */
+		dev->width = maxw;
+		dev->height = maxh;
+		dev->interlaced = EM28XX_INTERLACED_DEFAULT;
+		dev->field_size = dev->width * dev->height;
+		dev->frame_size =
+			dev->interlaced ? dev->field_size << 1 : dev->field_size;
+		dev->bytesperline = dev->width * 2;
+		dev->hscale = 0;
+		dev->vscale = 0;
+		dev->ctl_input = 2;
+
+		if (dev->dev_modes&EM28XX_VIDEO) {
+			errCode = em28xx_config(dev); /* TODO: check for errorvalue */
+			if (errCode) {
+				em28xx_errdev("error configuring device\n");
+				em28xx_devused &= ~(1<<dev->devno);
+				list_del(&dev->devlist);
+				kfree(dev);
+				return -ENOMEM;
+			}
+		}
+
+#ifdef CONFIG_MODULES
+		/* FIXME: is this module really required? some devices just spit errors
+			  when using that modules */
+		if (dev->has_msp34xx)
+			request_module("msp3400");
+#endif
+
+		/* allocate and fill v4l2 device struct */
+		dev->vdev = video_device_alloc();
+		if (NULL == dev->vdev) {
+			em28xx_errdev("cannot allocate video_device.\n");
+			em28xx_devused &= ~(1<<dev->devno);
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENOMEM;
+		}
+		memset(dev->vdev, 0x0, sizeof(struct video_device));
+
+
+		/* Fills CAPTURE device info */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+		dev->vdev->type = VID_TYPE_CAPTURE;
+		if (dev->has_tuner)
+			dev->vdev->type |= VID_TYPE_TUNER;
+		dev->vdev->dev = &dev->udev->dev;
+#else
+		dev->vdev->vfl_type = VID_TYPE_CAPTURE;
+		if (dev->has_tuner)
+			dev->vdev->vfl_type |= VID_TYPE_TUNER;
+		dev->vdev->parent = &dev->udev->dev;
+#endif
+		dev->vdev->fops = &em28xx_v4l_fops;
+		dev->vdev->minor = -1;
+		dev->vdev->release = video_device_release;
+		snprintf(dev->vdev->name, sizeof(dev->vbi_dev->name), "%s#%d %s",
+				"em28xx", dev->devno, "video");
+
+		/* register v4l2 device */
+		if ((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER,
+						video_nr[dev->devno]))) {
+			em28xx_errdev("unable to register video device (error = %i).\n",
+					retval);
+			list_del(&dev->devlist);
+			video_device_release(dev->vdev);
+			em28xx_devused &= ~(1<<dev->devno);
+			kfree(dev);
+			return -ENODEV;
+		}
+		if (dev->dev_modes&EM28XX_VBI) {
+
+			dev->vbi_dev = video_device_alloc();
+			if (NULL == dev->vbi_dev) {
+				em28xx_errdev("cannot allocate video_device.\n");
+				em28xx_devused &= ~(1<<dev->devno);
+				list_del(&dev->devlist);
+				video_device_release(dev->vdev);
+				kfree(dev);
+				return -ENOMEM;
+			}
+			memset(dev->vbi_dev, 0x0, sizeof(struct video_device));
+
+			/* Fills VBI device info */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+			dev->vbi_dev->type = VFL_TYPE_VBI;
+			dev->vbi_dev->dev = &dev->udev->dev;
+#else
+			dev->vbi_dev->vfl_type = VFL_TYPE_VBI;
+			dev->vbi_dev->parent = &dev->udev->dev;
+#endif
+			dev->vbi_dev->fops = &em28xx_v4l_fops;
+			dev->vbi_dev->minor = -1;
+			dev->vbi_dev->release = video_device_release;
+			snprintf(dev->vbi_dev->name, sizeof(dev->vbi_dev->name), "%s#%d %s",
+					"em28xx", dev->devno, "vbi");
+
+			if (video_register_device(dev->vbi_dev, VFL_TYPE_VBI,
+						vbi_nr[dev->devno]) < 0) {
+				printk(KERN_INFO"unable to register vbi device\n");
+				list_del(&dev->devlist);
+				video_device_release(dev->vbi_dev);
+				video_device_release(dev->vdev);
+				em28xx_devused &= ~(1<<dev->devno);
+				kfree(dev);
+				return -ENODEV;
+			}
+			em28xx_info("V4L2 VBI device registered as /dev/vbi%d\n",
+					dev->vbi_dev->num);
+		}
+		video_mux(dev, 0);
+		em28xx_info("V4L2 device registered as /dev/video%d\n",
+				dev->vdev->num);
+	}
+
+	/* when reattaching the device reinitialize the attached submodules */
+	mutex_lock(&em28xx_extension_devlist_lock);
+	if (!list_empty(&em28xx_extension_devlist)) {
+		list_for_each(pos, &em28xx_extension_devlist) {
+			ops = list_entry(pos, struct em28xx_ops, next);
+			if (ops->id & dev->dev_modes)
+				ops->init(dev);
+		}
+	}
+	mutex_unlock(&em28xx_extension_devlist_lock);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	request_modules(dev);
+#else
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+#endif
+	if (dev->board->ir_i2c == 0 &&
+			dev->board->ir_keytab &&
+			dev->board->ir_getkey) 
+		em2880_ir_attach(dev);
+
+	return 0;
+}
+
+/*
+ * em28xx_generic_probe()
+ * probes generic em28xx devices
+ * TODO: add em2800 based card detection (cinergy 200)
+ *
+ */
+
+struct drequest_t {
+	int brequest;
+	int index;
+	char *buffer;
+	int retval;
+	int blen;
+	int msecs;
+};
+
+static int em28xx_generic_probe(int *model, struct usb_device *udev,
+		struct em28xx *dev)
+{
+	int i;
+	char buffer = -1;
+	char *sendbuf;
+	int ret;
+	int rv = 0;
+	switch ((*model)) {
+		case EM2870_BOARD_TERRATEC_XS:
+		{
+			sendbuf = kmalloc(1, GFP_KERNEL);
+			memcpy(sendbuf, "\x00", 1);
+			ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02,
+					USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x00c0, sendbuf, 1, HZ);
+			kfree(sendbuf);
+			ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+					USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x00c0, &buffer, 1, HZ);
+			if (buffer == 0x63) {
+				(*model) = EM2870_BOARD_TERRATEC_XS_MT2060;
+				printk(KERN_INFO"em28xx-video.c: New Terratec XS Detected\n");
+			}
+		}
+		return 0;
+		case EM2800_BOARD_LEADTEK_WINFAST_USBII:
+		{
+			/* seems like leadtek didn't change the product id for em2820/em2800 based
+			   devices */
+			ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+					USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x000a, &buffer, 1, HZ);
+			switch (buffer) {
+			case 0x12:
+				printk("EM2820 based device detected\n");
+				(*model) = EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE;
+				return 0;
+			}
+		}
+		case EM2800_BOARD_GENERIC:
+		{
+			u8 i2c_devs[128];
+			int nr_i2c_devs = 0;
+			printk(KERN_INFO"em28xx: generic EM2800 board trying to guess card by i2c addresses\n");
+			/* do a i2c scan */
+			for (i = 0; i < 128; i++) {
+				if (!em2800_i2c_check_for_device(dev, i<<1))
+					i2c_devs[nr_i2c_devs++] = i<<1;
+			}
+			/* something went wrong */
+			if (!nr_i2c_devs)
+				break;
+			/* now loop through all em2800 cards */
+			for (i = 0; i < em28xx_bcount; i++) {
+				/* if the card definition contains a list of i2c devices */
+				if (em28xx_boards[i].em_type == EM2800 &&
+						em28xx_boards[i].i2c_devs) {
+					int x;
+					/* compare the list to the scan results */
+					for (x = 0; x < nr_i2c_devs &&
+						em28xx_boards[i].i2c_devs[x];
+						x++) {
+						if (i2c_devs[x] !=
+						   em28xx_boards[i].i2c_devs[x])
+							break;
+					}
+					if (x == nr_i2c_devs  && !em28xx_boards[i].i2c_devs[x]) {
+						(*model) = i;
+						return 0;
+					}
+				}
+			}
+		}
+		break;
+		case EM2861_BOARD_GENERIC:
+		{
+			struct drequest_t yakumo[] = {
+				{0x03, 0xb8, "\x00", 0x00, 1, 0}
+				/* this is the only i2c port which is
+				   available */
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(yakumo); i++) {
+				sendbuf = kmalloc(yakumo[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, yakumo[i].buffer, yakumo[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), yakumo[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, yakumo[i].index, sendbuf, yakumo[i].blen, HZ);
+				kfree(sendbuf);
+				if (yakumo[i].msecs)
+					msleep(yakumo[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != yakumo[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2861_BOARD_YAKUMO_MOVIE_MIXER;
+				return 0;
+			}
+
+		}
+		break;
+		case EM2820_BOARD_GENERIC:
+		{
+			struct drequest_t videology[] = { /* todo videology
+							     has a device
+							     identifier
+							     somewhere in the
+							     eeprom */
+				{0x03, 0xa2, "\x00", 0x00, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0x68, "\x00", 0x00, 1, 0},
+				{0x03, 0xa4, "\x00", 0x00, 1, 0},
+				{0x03, 0xa6, "\x00", 0x00, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+
+			struct drequest_t hercules_stv[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x00, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc6, "\x00", 0x00, 1, 0},
+			};
+
+			struct drequest_t gadmei_utv_310[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x10, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+
+			struct drequest_t msi_vox[] = {
+				{0x03, 0x42, "\x00", 0x00, 1, 0},
+				{0x03, 0x4a, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x00, 1, 0},
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+			};
+			struct drequest_t msi_vox_ntsc[] = {
+				{0x03, 0x42, "\x00", 0x00, 1, 0},
+/*					{0x03, 0x66, "\x00", 0x00, 1, 0}, */
+/*					{0x03, 0x68, "\x00", 0x00, 1, 0}, */
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+			};
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(videology); i++) {
+				/* usb_control_msg() expects kmalloced memory, otherwise the host controller will die */
+				sendbuf = kmalloc(videology[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, videology[i].buffer, videology[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), videology[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, videology[i].index, sendbuf, videology[i].blen, HZ);
+				kfree(sendbuf);
+				if (videology[i].msecs)
+					msleep(videology[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != videology[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_VIDEOLOGY_20K14XUSB;
+				return 0;
+			}
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox); i++) {
+				sendbuf = kmalloc(msi_vox[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, msi_vox[i].buffer, msi_vox[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, msi_vox[i].index, sendbuf, msi_vox[i].blen, HZ);
+				kfree(sendbuf);
+				if (msi_vox[i].msecs)
+					msleep(msi_vox[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != msi_vox[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_MSI_VOX_USB_2;
+				return 0;
+			}
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(hercules_stv); i++) {
+				sendbuf = kmalloc(hercules_stv[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, hercules_stv[i].buffer, hercules_stv[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), hercules_stv[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, hercules_stv[i].index, sendbuf, hercules_stv[i].blen, HZ);
+				kfree(sendbuf);
+				if (hercules_stv[i].msecs)
+					msleep(hercules_stv[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != hercules_stv[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_HERCULES_SMART_TV_USB2;
+				return 0;
+			}
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_310); i++) {
+				sendbuf = kmalloc(gadmei_utv_310[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, gadmei_utv_310[i].buffer, gadmei_utv_310[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_310[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, gadmei_utv_310[i].index, sendbuf, gadmei_utv_310[i].blen, HZ);
+				kfree(sendbuf);
+				if (gadmei_utv_310[i].msecs)
+					msleep(gadmei_utv_310[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != gadmei_utv_310[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_GADMEI_UTV310;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox_ntsc); i++) {
+				sendbuf = kmalloc(msi_vox_ntsc[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, msi_vox_ntsc[i].buffer, msi_vox_ntsc[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox_ntsc[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, msi_vox_ntsc[i].index, sendbuf, msi_vox_ntsc[i].blen, HZ);
+				kfree(sendbuf);
+				if (msi_vox_ntsc[i].msecs)
+					msleep(msi_vox_ntsc[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != msi_vox_ntsc[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_MSI_VOX_USB_2;
+				return 0;
+			}
+		}
+		break;
+		case EM2750_BOARD_GENERIC:
+		{
+			/* TODO */
+			(*model) = EM2750_BOARD_DLCW_130;
+			return 0;
+		}
+		break;
+		case EM2860_BOARD_GENERIC:
+		{
+			struct drequest_t gadmei_utv_330[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x10, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+			struct drequest_t netgmbh_cam[] = {
+#if 0
+				{0x02, 0x48, "\x00", 0x00, 1, 0},
+#endif
+				{0x03, 0x5a, "\x00", 0x00, 1, 0},
+			};
+
+			struct drequest_t typhoon_dvdmaker[] = {
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+			};
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_330);
+					i++) {
+				sendbuf = kmalloc(gadmei_utv_330[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, gadmei_utv_330[i].buffer, gadmei_utv_330[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_330[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, gadmei_utv_330[i].index, sendbuf, gadmei_utv_330[i].blen, HZ);
+				kfree(sendbuf);
+				if (gadmei_utv_330[i].msecs)
+					msleep(gadmei_utv_330[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != gadmei_utv_330[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_GADMEI_UTV330;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(netgmbh_cam); i++) {
+				sendbuf = kmalloc(netgmbh_cam[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, netgmbh_cam[i].buffer, netgmbh_cam[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), netgmbh_cam[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, netgmbh_cam[i].index, sendbuf, netgmbh_cam[i].blen, HZ);
+				kfree(sendbuf);
+				if (netgmbh_cam[i].msecs)
+					msleep(netgmbh_cam[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != netgmbh_cam[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_NETGMBH_CAM;
+				return 0;
+			}
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(typhoon_dvdmaker); i++) {
+				sendbuf = kmalloc(typhoon_dvdmaker[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, typhoon_dvdmaker[i].buffer, typhoon_dvdmaker[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), typhoon_dvdmaker[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, typhoon_dvdmaker[i].index, sendbuf, typhoon_dvdmaker[i].blen, HZ);
+				kfree(sendbuf);
+				if (typhoon_dvdmaker[i].msecs)
+					msleep(typhoon_dvdmaker[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != typhoon_dvdmaker[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_TYPHOON_DVD_MAKER;
+				return 0;
+			}
+		}
+		break;
+		case EM2821_BOARD_GENERIC:
+			{
+				struct drequest_t usbgear[] = {
+					{0x03, 0xa2, "\x00", 0x10, 1, 0},
+					{0x03, 0xa0, "\x00", 0x10, 1, 0},
+					{0x03, 0x68, "\x00", 0x10, 1, 0},
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+					{0x03, 0x66, "\x00", 0x10, 1, 0},
+					{0x03, 0x68, "\x00", 0x10, 1, 0},
+					{0x03, 0x86, "\x00", 0x10, 1, 0},
+					{0x03, 0xc0, "\x00", 0x10, 1, 0},
+					{0x03, 0xc2, "\x00", 0x10, 1, 0},
+				};
+				struct drequest_t siig_prolink[] = {
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+					{0x03, 0x60, "\x00", 0x00, 1, 0},
+					{0x03, 0xa0, "\x00", 0x00, 1, 0},
+					{0x03, 0xc6, "\x00", 0x00, 1, 0},
+				};
+				struct drequest_t supercomp[] = {
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+#if 0
+					{0x03, 0x60, "\x00", 0x00, 1, 0},
+#endif
+					{0x03, 0xc2, "\x00", 0x00, 1, 0},
+					{0x03, 0xc6, "\x00", 0x00, 1, 0},
+				};
+
+				for (i = 0, rv = 0; i < ARRAY_SIZE(usbgear); i++) {
+					sendbuf = kmalloc(usbgear[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, usbgear[i].buffer, usbgear[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), usbgear[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, usbgear[i].index, sendbuf, usbgear[i].blen, HZ);
+					kfree(sendbuf);
+					if (usbgear[i].msecs)
+						msleep(usbgear[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != usbgear[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_USBGEAR_VD204;
+					return 0;
+				}
+
+				for (i = 0, rv = 0; i < ARRAY_SIZE(siig_prolink); i++) {
+					sendbuf = kmalloc(siig_prolink[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, siig_prolink[i].buffer, siig_prolink[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), siig_prolink[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, siig_prolink[i].index, sendbuf, siig_prolink[i].blen, HZ);
+					kfree(sendbuf);
+					if (siig_prolink[i].msecs)
+						msleep(siig_prolink[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != siig_prolink[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_PROLINK_PLAYTV_USB2;
+					return 0;
+				}
+				for (i = 0, rv = 0; i < ARRAY_SIZE(supercomp); i++) {
+					sendbuf = kmalloc(supercomp[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, supercomp[i].buffer, supercomp[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), supercomp[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, supercomp[i].index, sendbuf, supercomp[i].blen, HZ);
+					kfree(sendbuf);
+					if (supercomp[i].msecs)
+						msleep(supercomp[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != supercomp[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_SUPERCOMP_USB_2;
+					return 0;
+				}
+			}
+			break;
+		case EM2870_BOARD_GENERIC:
+		{
+			struct drequest_t pinnacle_pctv[] = {
+				/* {0x03, 0xb8, "\x00", 0x00, 1, 0}, tvp5150 -- it's possible that this IC isn't reachable*/
+				{0x03, 0xa0, "\x00", 0x00, 1, 0}, /* eeprom */
+				{0x03, 0xc0, "\x00", 0x00, 1, 0}, /* mt2060 */
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_pctv);
+					i++) {
+				sendbuf = kmalloc(pinnacle_pctv[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, pinnacle_pctv[i].buffer, pinnacle_pctv[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_pctv[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, pinnacle_pctv[i].index, sendbuf, pinnacle_pctv[i].blen, HZ);
+				kfree(sendbuf);
+				if (pinnacle_pctv[i].msecs)
+					msleep(pinnacle_pctv[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != pinnacle_pctv[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2870_BOARD_PINNACLE_PCTV_DVB;
+				return 0;
+			}
+		}
+		break;
+		/* no other card has been available yet with that IC,
+		   so we assume HVR_950 by default */
+		case EM2883_BOARD_GENERIC:
+		{
+			(*model) = EM2883_BOARD_EMPIA_HYBRID_ATSC;
+			return 0;
+		}
+		break;
+		case EM2881_BOARD_GENERIC:
+		{
+			struct drequest_t dnt_hybrid[] = {
+				{0x03, 0xa0, "\x15", 0x56, 1, 0},
+#if 0
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x08", 0x00, 1, 20},
+				{0x00, 0x08, "\x6f", 0x00, 1, 20},
+				{0x00, 0x08, "\x7f", 0x00, 1, 20},
+				{0x03, 0xb8, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x0c", 0x00, 1, 20},
+				{0x00, 0x08, "\x6e", 0x00, 1, 20},
+				{0x00, 0x08, "\x7e", 0x00, 1, 20},
+				{0x03, 0x1e, "\x00", 0x00, 1, 0},
+#endif
+			};
+			struct drequest_t pinnacle_hybrid_pro[] = {
+				{0x03, 0xa0, "\x15", 0x57, 1, 0},
+#if 0
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x08", 0x00, 1, 20},
+				{0x00, 0x08, "\x6f", 0x00, 1, 20},
+				{0x00, 0x08, "\x7f", 0x00, 1, 20},
+				{0x03, 0xb8, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x0c", 0x00, 1, 20},
+				{0x00, 0x08, "\x6e", 0x00, 1, 20},
+				{0x00, 0x08, "\x7e", 0x00, 1, 20},
+				{0x03, 0x1e, "\x00", 0x00, 1, 0},
+#endif
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_hybrid_pro); i++)
+			{
+				sendbuf = kmalloc(pinnacle_hybrid_pro[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, pinnacle_hybrid_pro[i].buffer, pinnacle_hybrid_pro[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_hybrid_pro[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, pinnacle_hybrid_pro[i].index, sendbuf, pinnacle_hybrid_pro[i].blen, HZ);
+				kfree(sendbuf);
+				if (pinnacle_hybrid_pro[i].msecs)
+					msleep(pinnacle_hybrid_pro[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (pinnacle_hybrid_pro[i].index == 0xa0)
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ);
+
+				if (buffer != pinnacle_hybrid_pro[i].retval) {
+					printk("FAILED: 0x%02x 0x%02x\n", pinnacle_hybrid_pro[i].index, buffer);
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2881_BOARD_PINNACLE_HYBRID_PRO;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(dnt_hybrid); i++)
+			{
+				sendbuf = kmalloc(dnt_hybrid[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, dnt_hybrid[i].buffer, dnt_hybrid[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), dnt_hybrid[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, dnt_hybrid[i].index, sendbuf, dnt_hybrid[i].blen, HZ);
+				kfree(sendbuf);
+				if (dnt_hybrid[i].msecs)
+					msleep(dnt_hybrid[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (dnt_hybrid[i].index == 0xa0)
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ);
+				if (buffer != dnt_hybrid[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2881_BOARD_DNT_DA2_HYBRID;
+				return 0;
+			}
+			}
+			break;
+		default:
+			return 0;
+	}
+
+	em28xx_errdev("Your board has no eeprom inside it and thus can't\n"
+			"%s: be autodetected.  Please pass card = <n> insmod option to\n"
+			"%s: workaround that.  Redirect complaints to the vendor of\n"
+			"%s: the TV card. Generic type will be used.\n"
+			"%s: Best regards, \n"
+			"%s:         -- tux\n",
+			dev->name, dev->name, dev->name, dev->name, dev->name);
+	em28xx_errdev("%s: Here is a list of valid choices for the card = <n> insmod option:\n",
+			dev->name);
+	for (i = 0; i < em28xx_bcount; i++) {
+		em28xx_errdev("    card = %d -> %s\n", i,
+				em28xx_boards[i].name);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_usb_probe()
+ * checks for supported devices
+ */
+
+static int em28xx_usb_probe(struct usb_interface *interface,
+			    const struct usb_device_id *id)
+{
+	const struct usb_endpoint_descriptor *endpoint;
+	struct usb_device *udev;
+	struct usb_interface *uif;
+	struct em28xx *dev = NULL;
+	int retval = -ENODEV;
+	int model, i, nr, ifnum, is_em2875 = 0;
+
+	udev = usb_get_dev(interface_to_usbdev(interface));
+
+	if (udev == NULL) {
+		printk("something's weird here udev returned 0!\n");
+		return -EINVAL;
+	}
+
+	ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+
+	/* Check to see next free device and mark as used */
+	nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
+	em28xx_devused |= 1<<nr;
+
+	/* Don't register audio interfaces */
+	if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+		printk(KERN_INFO "audio device (%04x:%04x): interface %i, class %i\n",
+				udev->descriptor.idVendor, udev->descriptor.idProduct,
+				ifnum,
+				interface->altsetting[0].desc.bInterfaceClass);
+
+		em28xx_devused &= ~(1<<nr);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "em28xx: new video device (%04x:%04x): interface %i, class %i\n",
+			udev->descriptor.idVendor, udev->descriptor.idProduct,
+			ifnum,
+			interface->altsetting[0].desc.bInterfaceClass);
+
+
+	/* check if the device has the iso in endpoint at the correct place */
+
+        endpoint = &interface->cur_altsetting->endpoint[0].desc;
+	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+			USB_ENDPOINT_XFER_ISOC &&
+		(interface->altsetting[1].endpoint[0].desc.wMaxPacketSize == 940)) {
+		printk(KERN_INFO "em28xx: DTV Device detected\n");
+		is_em2875 = 1;
+	} else {
+		endpoint = &interface->cur_altsetting->endpoint[1].desc;
+		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+				USB_ENDPOINT_XFER_ISOC) {
+			em28xx_err("em28xx: probing error: endpoint is non-ISO endpoint!\n");
+			em28xx_devused &= ~(1<<nr);
+			return -ENODEV;
+		}
+		if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) {
+			em28xx_err("em28xx: probing error: endpoint is ISO OUT endpoint!\n");
+			em28xx_devused &= ~(1<<nr);
+			return -ENODEV;
+		}
+	}
+
+	switch (udev->speed) {
+	case USB_SPEED_HIGH:
+		printk(KERN_INFO"em28xx: device is attached to a USB 2.0 bus\n");
+		break;
+	case USB_SPEED_FULL:
+		printk(KERN_INFO"em28xx: setting up device on a USB 1.1 bus\n");
+		/* passthrough */
+	default:
+		printk(KERN_INFO"em28xx: your device won't work properly when\n");
+		printk(KERN_INFO"em28xx: not attached to a USB 2.0 highspeed bus\n");
+		printk(KERN_INFO"em28xx: more information:\n");
+		printk(KERN_INFO"em28xx: http://mcentral.de/wiki/index.php5/Talk:Em2880\n");
+	}
+
+	model = id->driver_info;
+
+	if (nr >= EM28XX_MAXBOARDS) {
+		printk(DRIVER_NAME ": Supports only %i em28xx boards.\n", EM28XX_MAXBOARDS);
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(struct em28xx), GFP_KERNEL);
+	if (dev == NULL) {
+		em28xx_err(DRIVER_NAME ": out of memory!\n");
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	snprintf(dev->name, 28, "em28xx #%d", nr);
+	dev->devno = nr;
+
+	dev->udev = udev;
+
+	dev->em28xx_write_regs = em28xx_write_regs;
+	dev->em28xx_read_reg = em28xx_read_reg;
+	dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len;
+	dev->em28xx_write_regs_req = em28xx_write_regs_req;
+	dev->em28xx_read_reg_req = em28xx_read_reg_req;
+	dev->em28xx_write_reg_bits = em28xx_write_reg_bits;	
+	dev->usb_interface = ifnum;
+
+	/* compute alternate max packet sizes */
+	uif = udev->actconfig->interface[ifnum];
+
+	dev->num_alt = uif->num_altsetting;
+	dev->uif = uif;
+
+	em28xx_info("Alternate settings: %i\n", dev->num_alt);
+	dev->alt_max_pkt_size = kmalloc((dev->num_alt+1)*sizeof(int),
+			GFP_KERNEL);
+	if (dev->alt_max_pkt_size == NULL) {
+		em28xx_errdev("out of memory!\n");
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < dev->num_alt ; i++) {
+		u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[is_em2875?0:1].desc.
+							wMaxPacketSize);
+
+		dev->alt_max_pkt_size[i] =
+		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		em28xx_info("Alternate setting %i, max size= %i\n", i,
+							dev->alt_max_pkt_size[i]);
+	}
+
+	if ((card[nr] >= 0) && (card[nr] < em28xx_bcount))
+		model = card[nr];
+
+	em28xx_generic_probe(&model, udev, dev);
+
+	/* allocate device struct */
+	retval = em28xx_init_dev(&dev, udev, nr, model);
+	if (retval)
+		return retval;
+
+	em28xx_info("Found %s\n", em28xx_boards[model].name);
+
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+	return 0;
+}
+
+/*
+ * em28xx_usb_disconnect()
+ * called when the device gets diconencted
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void em28xx_usb_disconnect(struct usb_interface *interface)
+{
+	struct em28xx *dev = usb_get_intfdata(interface);
+	struct list_head *pos;
+	struct em28xx_ops *ops = 0;
+
+	/* might be racy */
+	dev->state |= DEV_DISCONNECTED;
+
+	usb_set_intfdata(interface, NULL);
+
+	/* TODO: kernel versions <=2.6.21 */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	if (delayed_work_pending(&dev->request_module_wk))
+		cancel_rearming_delayed_work(&dev->request_module_wk);
+#endif
+
+	if (!dev)
+		return;
+
+	down_write(&em28xx_disconnect);
+
+	em28xx_card_disconnect(dev);
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	if (!list_empty(&em28xx_extension_devlist)) {
+		list_for_each(pos, &em28xx_extension_devlist) {
+			ops = list_entry(pos, struct em28xx_ops, next);
+			ops->fini(dev);
+		}
+	}
+	mutex_unlock(&em28xx_extension_devlist_lock);
+
+	wake_up_interruptible_all(&dev->open);
+
+	if (dev->users) {
+		dev->state |= DEV_MISCONFIGURED;
+		em28xx_uninit_isoc(dev);
+		wake_up_interruptible(&dev->wait_frame);
+		wake_up_interruptible(&dev->video_wait_stream);
+		if (dev->dev_modes&EM28XX_VBI) {
+			wake_up_interruptible(&dev->wait_vbi_frame);
+			wake_up_interruptible(&dev->vbi_wait_stream);
+		}
+	} else {
+		em28xx_release_resources(dev);
+		kfree(dev->alt_max_pkt_size);
+		tuner_chip_detach(dev->tuner);
+		kfree(dev);
+	}
+
+	up_write(&em28xx_disconnect);
+}
+
+static struct usb_driver em28xx_usb_driver = {
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 15)
+	.owner = THIS_MODULE,
+#endif
+	.name = "em28xx",
+	.probe = em28xx_usb_probe,
+	.disconnect = em28xx_usb_disconnect,
+	.id_table = em28xx_id_table,
+};
+
+int em28xx_register_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+	struct list_head *list;
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	list_add_tail(&ops->next, &em28xx_extension_devlist);
+	
+	/* TODO: defer the work put the module request into a linked list
+	   lock the device when the corresponding node got closed and load
+	   the module:
+	 
+	    problem here to consider is if module autoloading is not enabled
+	    to make it more safe it would be good to lock the driver and unlock
+	    it when the corresponding extension got unlocked
+
+	    better ideas are welcome of course
+	 
+	 */
+
+	list_for_each(list, &em28xx_devlist) {
+		dev = list_entry(list, struct em28xx, devlist);
+		if (dev &&
+		    (dev->state & DEV_DISCONNECTED) == 0 &&
+		    (dev->state & DEV_MISCONFIGURED) == 0 &&
+		    dev->radio_user == 0 &&
+		    dev->video_user == 0 &&
+		    dev->vbi_user == 0 && 
+		    dev->fe_user == 0 &&
+		    dev->dev_modes & ops->id) {
+			dev->em28xx_acquire(dev, EM28XX_LOCK, 1);
+
+			ops->init(dev);
+			printk("Em28xx: Initialized (%s) extension\n",ops->name);
+
+			dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+		}
+	}
+
+	mutex_unlock(&em28xx_extension_devlist_lock);
+	return 0;
+}
+
+void em28xx_unregister_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+	struct list_head *list;
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	list_for_each(list, &em28xx_devlist) {
+		dev = list_entry(list, struct em28xx, devlist);
+		if (dev)
+			ops->fini(dev);
+	}
+	printk(KERN_INFO"Em28xx: Removed (%s) extension\n", ops->name);
+	list_del(&ops->next);
+	mutex_unlock(&em28xx_extension_devlist_lock);
+}
+
+
+static int __init em28xx_module_init(void)
+{
+	int result;
+
+	printk(KERN_INFO DRIVER_NAME " v4l2 driver version %d.%d.%d loaded\n",
+	       (EM28XX_VERSION_CODE >> 16) & 0xff,
+	       (EM28XX_VERSION_CODE >> 8) & 0xff, EM28XX_VERSION_CODE & 0xff);
+#ifdef SNAPSHOT
+	printk(KERN_INFO DRIVER_NAME " snapshot date %04d-%02d-%02d\n",
+	       SNAPSHOT / 10000, (SNAPSHOT / 100) % 100, SNAPSHOT % 100);
+#endif
+
+	em28xx_wq = create_singlethread_workqueue("em28xx-worker");
+	if (em28xx_wq == NULL)
+		return -ENOMEM;
+
+	/* register this driver with the USB subsystem */
+	result = usb_register(&em28xx_usb_driver);
+	if (result)
+		em28xx_err(DRIVER_NAME
+			   " usb_register failed. Error number %d.\n", result);
+
+	return result;
+}
+
+static void __exit em28xx_module_exit(void)
+{
+	/* deregister this driver with the USB subsystem */
+	usb_deregister(&em28xx_usb_driver);
+	destroy_workqueue(em28xx_wq);
+}
+
+module_init(em28xx_module_init);
+module_exit(em28xx_module_exit);
+EXPORT_SYMBOL(em28xx_register_extension);
+EXPORT_SYMBOL(em28xx_unregister_extension);
+
diff --git a/drivers/media/video/empia/em28xx-webcam.c b/drivers/media/video/empia/em28xx-webcam.c
new file mode 100644
index 0000000..228c04e
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-webcam.c
@@ -0,0 +1,375 @@
+/*
+   em28xx-vy.c - driver for em28xx based webcams
+
+   Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
+
+   I got a few specs but none of them were clear enough to get anything
+   work. Finally I end up with writing usbreplay and stepping through
+   the sniffed logfiles...
+
+   all this is quite device specific (em2820 specific) so I decided to
+   not write an I2C client no other device will behave like this one
+
+   Thanks to Walter Grom from MT (www.mt.com) for implementing
+   the reversed specs
+
+   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/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <media/v4l2-common.h>
+
+#include "em28xx.h"
+
+#define VY_FEATURE_REGISTER_1       0x02 /* Gain control en, gain control
+					    values */
+#define VY_FEATURE_REGISTER_2       0x04 /* WBM, Shutter, Mirror */
+#define VY_FEATURE_REGISTER_3       0x7c /* edge enhance */
+#define VY_FEATURE_REGISTER_4       0x7b /* edge enhance en */
+#define VY_FEATURE_REGISTER_5       0x7e /* edge enhance again? */
+#define VY_FEATURE_REGISTER_6       0x13 /* BLC ?? */
+#define VY_FEATURE_REGISTER_7	    0x1d /* gain control values */
+#define VY_FEATURE_REGISTER_8	    0x42 /* RGain */
+#define VY_FEATURE_REGISTER_9       0x43 /* BGain */
+
+#define V4L2_VY_WBM_MASK                 0x03
+#define V4L2_VY_SHUTTER_MASK             0x78
+#define V4L2_VY_MIRROR_MASK              0x80
+#define V4L2_VY_GAIN_CONTROL_EN_MASK     0x02
+#define V4L2_VY_GAIN_CONTROL_MASK        0xfe
+#define V4L2_VY_EDGE_ENHANCE_EN_MASK     0x03
+#define V4L2_VY_EDGE_ENHANCE_MASK        0x1f
+#define V4L2_VY_BLC_MASK                 0x00 /* FIXME */
+#define V4L2_VY_RGAIN_MASK               0xFF
+#define V4L2_VY_BGAIN_MASK               0xFF
+
+/* videology specific data */
+struct v4l2_queryctrl em28xx_vy_ctrl[] = {
+	{
+		.id = V4L2_VY_WBM,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "whiteblc",
+		.minimum = 0x0,
+		.maximum = 0x3,
+		.step = 0x1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_SHUTTER,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "shuttermode",
+		.minimum = 0x0,
+		.maximum = 0xf,
+		.step = 1,
+		.default_value = 0xf,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_MIRROR,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "mirror",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_GAIN_CONTROL_EN,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "gain_en",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_GAIN_CONTROL,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "gainctrl",
+		.minimum = 0x0,
+		.maximum = 0x7f,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_EDGE_ENHANCE_EN,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "edge_en",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_EDGE_ENHANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "edgectrl",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_BLC, /* wonder what this really is? */
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "blc",
+		.minimum = 0x0,
+		.maximum = 0x40,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_RGAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "rgain",
+		.minimum = 0x0,
+		.maximum = 0xff,
+		.step = 1,
+		.default_value = 0x3b,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_BGAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "bgain",
+		.minimum = 0x0,
+		.maximum = 0xff,
+		.step = 1,
+		.default_value = 0x74,
+		.flags = 0,
+	}
+};
+
+
+static int em28xx_vy_readmap(struct em28xx *dev, u8 reg, int mask)
+{
+	int value;
+
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg, 1);
+	value = em28xx_read_reg_req(dev, 0x2, 0xa2);
+	value &= mask;
+	return value;
+}
+
+
+/* --------------------------------------------- */
+int em28xx_vy_write(struct em28xx *dev, u8 reg_index, u8 value, u8 mask)
+{
+	u8 msg[2];
+	u8 val;
+	u8 oldval;
+	u8 state = 1; /* 1 == OK */
+
+	/* write index of desired register to adress 0xa2 */
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg_index, 1);
+	/* read old register value from eeprom */
+	oldval = em28xx_read_reg_req(dev, 0x2, 0xa2);
+	/* write again index of desired register to adress 0xa2 */
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg_index, 1);
+
+	/* calculate new register value */
+	msg[0] = reg_index;
+	msg[1] = (oldval & ~mask)|(value&mask);
+	/* write new register value to eeprom */
+	dev->em28xx_write_regs_req(dev, 0x02, 0xa0, msg, 2);
+
+	/* instruct microcontroller to write values into the eeprom */
+	msg[0] = 0x11;
+	msg[1] = reg_index;
+	dev->em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2);
+	dev->em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1);
+	val = em28xx_read_reg_req(dev, 0x2, 0x68);
+	if (val == 0x05)
+		; /* everything's fine */
+	else
+		state = 0;
+
+	/* write new register value to eeprom again */
+	msg[0] = reg_index;
+	msg[1] = (oldval & ~mask)|(value&mask);
+	dev->em28xx_write_regs_req(dev, 0x02, 0xa2, msg, 2);
+
+	/* instruct microcontroller for verification */
+	msg[0] = 0x21;
+	msg[1] = reg_index;
+	em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2);
+	em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1);
+	val = em28xx_read_reg_req(dev, 0x2, 0x68);
+	if (val == 0x05)
+		; /* everything's fine */
+	else
+		state = 0;
+
+	if (state == 0)
+		return oldval;  /* error */
+	else
+		return value;
+}
+
+/* embedded QUERYCTRL */
+int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl)
+{
+	struct v4l2_queryctrl *qc = qctrl;
+	int i;
+	for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) {
+		if (qc->id && qc->id == em28xx_vy_ctrl[i].id) {
+			memcpy(qc, &(em28xx_vy_ctrl[i]),
+					sizeof(*qc));
+			return 0;
+		}
+	}
+	return -EINVAL; /* no ctrl found here */
+}
+
+/* VIDIOC_G_CTRL */
+int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_VY_WBM:
+		ctrl->value = em28xx_vy_readmap(dev,
+				VY_FEATURE_REGISTER_2,
+				V4L2_VY_WBM_MASK);
+		break;
+	case V4L2_VY_SHUTTER:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_2,
+					V4L2_VY_SHUTTER_MASK)) >> 3;
+		break;
+	case V4L2_VY_MIRROR:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_2,
+					V4L2_VY_MIRROR_MASK)) >> 7;
+		break;
+	case V4L2_VY_GAIN_CONTROL_EN:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_1,
+					V4L2_VY_GAIN_CONTROL_EN_MASK)) >> 1;
+		break;
+	case V4L2_VY_GAIN_CONTROL:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_7,
+					V4L2_VY_GAIN_CONTROL_MASK)) >> 1;
+		break;
+	case V4L2_VY_EDGE_ENHANCE_EN:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_4,
+					V4L2_VY_EDGE_ENHANCE_EN_MASK) & 0x01) ?
+					0 : 1;
+		break;
+	case V4L2_VY_EDGE_ENHANCE:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_3,
+					       V4L2_VY_EDGE_ENHANCE_MASK);
+		break;
+	case V4L2_VY_BLC:
+#if 0
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_6,
+				V4L2_VY_BLC_MASK);
+#endif
+		printk(KERN_INFO"em28xx-vy.c V4L2_VY_BLC: not implemented\n");
+		break;
+	case V4L2_VY_RGAIN:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_8,
+					       V4L2_VY_RGAIN_MASK);
+		break;
+	case V4L2_VY_BGAIN:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_9,
+					       V4L2_VY_BGAIN_MASK);
+		break;
+	default:
+		printk(KERN_INFO"em28xx-vy.c: unknown command!\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* VIDIOC_S_CTRL */
+int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) {
+		if (ctrl->id == em28xx_vy_ctrl[i].id) {
+			if (em28xx_vy_ctrl[i].minimum >= ctrl->value &&
+					em28xx_vy_ctrl[i].maximum <=
+					ctrl->value) {
+				printk(KERN_INFO"em28xx-vy.c: value out of "
+						"range\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	switch (ctrl->id) {
+	case V4L2_VY_WBM:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)ctrl->value, V4L2_VY_WBM_MASK);
+		break;
+	case V4L2_VY_SHUTTER:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)(ctrl->value)<<3,
+				V4L2_VY_SHUTTER_MASK);
+		break;
+	case V4L2_VY_MIRROR:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)(ctrl->value)<<7,
+				V4L2_VY_MIRROR_MASK);
+		break;
+	case V4L2_VY_GAIN_CONTROL_EN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_1,
+				(u8)(ctrl->value)<<1,
+				V4L2_VY_GAIN_CONTROL_EN_MASK);
+		break;
+	case V4L2_VY_GAIN_CONTROL:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_7,
+				(u8)(ctrl->value)<<1,
+				V4L2_VY_GAIN_CONTROL_MASK);
+		break;
+	case V4L2_VY_EDGE_ENHANCE_EN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_4,
+				(u8)(~(((ctrl->value)<<1) | (ctrl->value))),
+				V4L2_VY_EDGE_ENHANCE_EN_MASK);
+		break;
+	case V4L2_VY_EDGE_ENHANCE:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_3,
+				(u8)(ctrl->value),
+				V4L2_VY_EDGE_ENHANCE_MASK);
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_5,
+				(u8)(ctrl->value),
+				V4L2_VY_EDGE_ENHANCE_MASK);
+		break;
+	case V4L2_VY_RGAIN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_8,
+				(u8)(ctrl->value),
+				V4L2_VY_RGAIN_MASK);
+		break;
+	case V4L2_VY_BGAIN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_9,
+				(u8)(ctrl->value),
+				V4L2_VY_BGAIN_MASK);
+		break;
+	case V4L2_VY_BLC:
+		printk(KERN_INFO"em28xx-vy.c V4l2_VY_BLC not implemented\n");
+		break;
+	default:
+		printk(KERN_INFO"em28xx-vy.c: unknown command!\n");
+	}
+	return 0;
+}
+
+
diff --git a/drivers/media/video/empia/em28xx.h b/drivers/media/video/empia/em28xx.h
new file mode 100644
index 0000000..069dd5a
--- /dev/null
+++ b/drivers/media/video/empia/em28xx.h
@@ -0,0 +1,1153 @@
+/*
+   em28xx.h - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 Markus Rechberger <mrechberger@gmail.com>
+		      Ludovico Cavedon <cavedon@sssup.it>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+
+   Based on the em2800 driver from Sascha Sommer <saschasommer@freenet.de>
+
+   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.
+ */
+
+#ifndef _EM28XX_H
+#define _EM28XX_H
+
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/dvb/frontend.h>
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_net.h"
+#include "dvb_frontend.h"
+#include <linux/soundcard.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "xc3028/xc3028_control.h"
+#include "cx25843/em28xx-cx25843.h"
+#include "media/tuner.h"
+#include "em28xx-aad.h"
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+#include <linux/mutex.h>
+#endif
+#include <media/ir-kbd-i2c.h>
+
+/* Boards supported by driver */
+
+#define EM2800_BOARD_GENERIC			  0
+#define EM2820_BOARD_GENERIC			  1
+#define EM2821_BOARD_GENERIC                      2
+#define EM2870_BOARD_GENERIC                      3
+#define EM2881_BOARD_GENERIC                      4
+#define EM2860_BOARD_GENERIC 			  5
+#define EM2861_BOARD_GENERIC 			  6
+#define EM2820_BOARD_TERRATEC_CINERGY_250	  7
+#define EM2820_BOARD_PINNACLE_USB_2		  8
+#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2        9
+#define EM2820_BOARD_MSI_VOX_USB_2                10
+#define EM2800_BOARD_TERRATEC_CINERGY_200         11
+#define EM2800_BOARD_LEADTEK_WINFAST_USBII        12
+#define EM2800_BOARD_KWORLD_USB2800               13
+#define EM2820_BOARD_PINNACLE_DVC_90		  14
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900	  15
+#define EM2880_BOARD_TERRATEC_HYBRID_XS		  16
+#define EM2880_BOARD_TERRATEC_HYBRID_XS_FR	  17
+#define EM2820_BOARD_KWORLD_PVRTV2800RF		  18
+#define EM2880_BOARD_TERRATEC_PRODIGY_XS	  19
+#define EM2820_BOARD_VIDEOLOGY_20K14XUSB	  20
+#define EM2821_BOARD_USBGEAR_VD204                21
+#define EM2870_BOARD_TERRATEC_XS                  22
+#define EM2870_BOARD_PINNACLE_PCTV_DVB            23
+#define EM2881_BOARD_DNT_DA2_HYBRID               24
+#define EM2881_BOARD_PINNACLE_HYBRID_PRO          25
+#define EM2820_BOARD_HERCULES_SMART_TV_USB2	  26
+#define EM2870_BOARD_COMPRO_VIDEOMATE             27
+#define EM2880_BOARD_KWORLD_DVB_310U              28
+#define EM2821_BOARD_PROLINK_PLAYTV_USB2          29
+#define EM2870_BOARD_TERRATEC_XS_MT2060           30
+#define EM2880_BOARD_MSI_DIGIVOX_AD               31
+#define EM2820_BOARD_DLINK_USB_TV                 32
+#define EM2820_BOARD_GADMEI_UTV310                33
+#define EM2870_BOARD_KWORLD_355U		  34
+#define EM2821_BOARD_SUPERCOMP_USB_2              35
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2	  36
+#define EM2860_BOARD_GADMEI_UTV330		  37
+#define EM2800_BOARD_VGEAR_POCKETTV               38
+#define EM2870_BOARD_KWORLD_350U		  39
+#define EM2882_BOARD_TERRATEC_HYBRID_XS		  40
+#define EM2820_BOARD_PINNACLE_DVC_100		  41
+#define EM2861_BOARD_YAKUMO_MOVIE_MIXER           43
+#define EM2750_BOARD_DLCW_130                     44
+#define EM2750_BOARD_GENERIC			  42
+#define EM2883_BOARD_GENERIC                      45
+#define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950      46
+#define EM2883_BOARD_PINNACLE_PCTV_HD_PRO	  47
+#define EM2882_BOARD_PINNACLE_HYBRID_PRO          48
+#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2     49
+#define EM2860_BOARD_NETGMBH_CAM		  50
+#define EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE 51
+#define EM2880_BOARD_MSI_DIGIVOX_AD_II            52
+#define EM2860_BOARD_TYPHOON_DVD_MAKER            53
+#define EM2820_BOARD_PINNACLE_USB_2_FM1216ME      54
+#define EM2751_BOARD_EMPIA_SAMPLE		  55
+#define EM2880_BOARD_KWORLD_DVB_305U              56
+#define EM2861_BOARD_KWORLD_PVRTV_300U 		  57
+#define EM2883_BOARD_KWORLD_HYBRID_A316 	  58
+#define EM2860_BOARD_TERRATEC_HYBRID_XS		  59
+#define EM2861_BOARD_PLEXTOR_PX_TV100U            60
+#define EM2883_BOARD_TERRATEC_HYBRID_XS_FM        61
+#define EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2     62
+#define EM2883_BOARD_EMPIA_HYBRID_ATSC            63
+#define EM2863_BOARD_EMPIA_GENERIC                64
+#define EM2883_BOARD_KWORLD_HYBRID_F306		  65
+#define EM2888_BOARD_KWORLD_HYBRID_E329		  66
+#define EM2883_BOARD_KWORLD_HYBRID_E323		  67
+#define EM2883_BOARD_KWORLD_A340		  68
+#define EM2888_BOARD_LINCOLN_TV_FM		  69
+#define EM2888_BOARD_DVB_TC_HYBRID		  70
+#define EM2888_BOARD_EMPIA_HYBRID                 71
+#define EM2883_BOARD_ATI_TVWONDER600		  72
+#define EM2875_BOARD_SAMPLE_ISDBT                 73
+#define EM2879_BOARD_SAMPLE_DMB                   74
+#define EM2860_BOARD_KAIOMY_TVNPC_U2              75
+#define EM2861_BOARD_POLLIN_USB_R1                76
+#define EM2883_BOARD_EQUINUX_TUBESTICK_ATSC       77
+#define EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H     78
+#define EM2820_BOARD_COMPRO_VIDEO_MATE            79
+
+#ifndef TUNER_XCEIVE_XC5000
+#define TUNER_XCEIVE_XC5000 10000
+#endif
+
+#ifndef TUNER_XCEIVE_XC3028
+#define TUNER_XCEIVE_XC3028 10001
+#endif
+
+#ifndef TUNER_MT2060
+#define TUNER_MT2060 10002
+#endif
+
+#ifndef TUNER_QT1010
+#define TUNER_QT1010 10003
+#endif
+
+#ifndef TUNER_PHILIPS_TDA18271
+#define TUNER_PHILIPS_TDA18271 10004
+#endif
+
+#ifndef TUNER_ADIMTV102
+#define TUNER_ADIMTV102 10005
+#endif
+
+
+#define UNSET -1
+
+/* maximum number of em28xx boards */
+/* TODO: we should distinct here between
+   * DVB-T
+   * analogue only
+   * hybrid devices
+   1 analogue tuner requires up to 170 mbit
+   2 analogue tuners will work in alt 3 mode, but the default
+     is alt 7 at the moment (it's possible to override
+     with the module parameter alt=3), though it should be
+     implemented properly.
+     2 devices take up to 340 mbit which should work.
+     (3 tuners would be 510 mbit which is too much for
+     one usb controller)
+
+   DVB-T tuner only take around 15 mbit, so we should be able
+   to support more than the limit below.
+
+   I think a controller based bandwidth table would do here
+   and guarantee that the initialized devices will work
+*/
+#define EM28XX_MAXBOARDS 3 /* FIXME: should be bigger */
+
+/* maximum number of frames that can be queued */
+#define EM28XX_NUM_FRAMES 5
+/* number of frames that get used for v4l2_read() */
+#define EM28XX_NUM_READ_FRAMES 2
+
+/* number of buffers for isoc transfers */
+#define EM28XX_NUM_BUFS 5
+#define EM2880_DVB_NUM_BUFS 5
+#define EM28XX_AUDIO_BUFS 3
+#define EM28XX_NUM_AUDIO_PACKETS 64
+#define EM28XX_AUDIO_MAX_PACKET_SIZE 196 /* static value */
+
+
+/* number of packets for each buffer
+   windows requests only 40 packets .. so we better do the same
+   this is what I found out for all alternate numbers there!
+ */
+#define EM28XX_NUM_PACKETS 64
+
+/* default alternate; 0 means choose the best */
+#define EM28XX_PINOUT 0
+
+#define EM28XX_INTERLACED_DEFAULT 1
+
+/*
+#define (use usbview if you want to get the other alternate number infos)
+#define
+#define alternate number 2
+#define 			Endpoint Address: 82
+			Direction: in
+			Attribute: 1
+			Type: Isoc
+			Max Packet Size: 1448
+			Interval: 125us
+
+  alternate number 7
+
+			Endpoint Address: 82
+			Direction: in
+			Attribute: 1
+			Type: Isoc
+			Max Packet Size: 3072
+			Interval: 125us
+*/
+
+/* time to wait when stopping the isoc transfer */
+#define EM28XX_URB_TIMEOUT \
+	msecs_to_jiffies(EM28XX_NUM_BUFS * EM28XX_NUM_PACKETS)
+
+/* time in msecs to wait for i2c writes to finish */
+#define EM2800_I2C_WRITE_TIMEOUT 20
+
+enum em28xx_fe_bandwidth {
+	EM28XX_BANDWIDTH_8_MHZ,
+	EM28XX_BANDWIDTH_7_MHZ,
+	EM28XX_BANDWIDTH_6_MHZ,
+	EM28XX_BANDWIDTH_AUTO
+};
+
+extern struct list_head em28xx_devlist;
+
+/* the various frame states */
+enum em28xx_frame_state {
+	F_UNUSED = 0,
+	F_QUEUED,
+	F_GRABBING,
+	F_DONE,
+	F_ERROR,
+};
+
+/* stream states */
+enum em28xx_stream_state {
+	STREAM_OFF,
+	STREAM_ON,
+	STREAM_INTERRUPT,
+};
+
+/* frames */
+struct em28xx_frame_t {
+	void *bufmem;
+	struct v4l2_buffer buf;
+	enum em28xx_frame_state state;
+	struct list_head frame;
+	unsigned long vma_use_count;
+	int top_field;
+	int fieldbytesused;
+};
+
+/* io methods */
+enum em28xx_io_method {
+	IO_NONE,
+	IO_READ,
+	IO_MMAP,
+};
+
+/* Colorspace Output */
+
+/* To be discussed */
+
+#ifndef V4L2_PIX_FMT_YUV211
+#define V4L2_PIX_FMT_YUV211	v4l2_fourcc('Y','2','1','1')
+#endif
+
+#ifndef V4L2_PIX_FMT_YUY1
+#define V4L2_PIX_FMT_YUY1	v4l2_fourcc('Y','U','Y','1')
+#endif
+
+#ifndef V4L2_PIX_FMT_Y21P
+#define V4L2_PIX_FMT_Y21P	v4l2_fourcc('Y','2','1','P')
+#endif
+
+struct em28xx_output_fmt {
+	struct v4l2_fmtdesc fmt;
+	u8 config;
+};
+
+/* inputs */
+
+#define MAX_EM28XX_INPUT 4
+#define MAX_EM28XX_TVNORMS 10
+#define MAX_EM28XX_DVBNORMS 5
+#define MAX_EM28XX_FMNORMS 2
+#define MAX_EM28XX_ATSCNORMS 2
+#define MAX_EM28XX_QAMNORMS 2
+
+enum enum28xx_itype {
+	EM28XX_VMUX_COMPOSITE1 = 1,
+	EM28XX_VMUX_COMPOSITE2,
+	EM28XX_VMUX_COMPOSITE3,
+	EM28XX_VMUX_COMPOSITE4,
+	EM28XX_VMUX_SVIDEO,
+	EM28XX_VMUX_TELEVISION,
+	EM28XX_VMUX_CABLE,
+	EM28XX_VMUX_DVB,
+	EM28XX_VMUX_DEBUG,
+	EM28XX_AMUX_RADIO,
+};
+
+enum enum28xx_mixchannel {
+	EM28XX_MIX_NOTOUCH = 0,
+	EM28XX_MIX_LINE_IN = 1,
+	EM28XX_MIX_VIDEO = 2,
+};
+
+struct em28xx_input {
+	enum enum28xx_itype type;
+	unsigned int vmux;
+	unsigned int amux;
+	enum enum28xx_mixchannel amix;
+};
+
+#define INPUT(nr) (&em28xx_boards[dev->model].input[nr])
+
+enum em28xx_decoder {
+	EM28XX_TVP5150,
+	EM28XX_SAA7113,
+	EM28XX_SAA7114,
+	EM28XX_CX25843,
+};
+
+struct em28xx;
+
+/* 0x0 - undef */
+enum empia_type {
+	EM2800 = 1,
+	EM2820,
+	EM2840,
+	EM2750,
+	EM2751,
+	EM2860,
+	EM2875,
+	EM2880,
+	EM2881,
+	EM2882,
+	EM2883,
+	EM2888,
+	EM2889
+};
+
+#define EM28XX_VIDEO    0x01
+#define EM28XX_DVBT     0x02
+#define EM28XX_DVBC     0x04
+#define EM28XX_ATSC     0x08
+#define EM28XX_DMB      0x10
+#define EM28XX_ISDB     0x20
+#define EM28XX_VBI      0x40
+#define EM28XX_REMOTE   0x80
+#define EM28XX_AUDIO    0x100 /* vendor specific audiodriver */
+#define EM28XX_AUDIO2   0x200 /* vendor specific audiodriver, different interface number */
+#define EM28XX_RADIO    0x400
+#define EM28XX_LOCK	0x800
+
+#define EM28XX_CAPTURE_STREAM_EN 1
+
+/* used by the audio driver */
+#define EM28XX_ENABLE_AUDIO 1
+#define EM28XX_DISABLE_AUDIO 2
+
+/* tvnorms */
+struct em28xx_tvnorm {
+	char *name;
+	XC3028_TV_MODE *tv_mode;
+	XC3028_CHANNEL_MAP *channelmap;
+	int index; /* xc5000 configuration */
+
+	/* digital tv */
+	enum em28xx_fe_bandwidth bandwidth;
+	/* analog tv */
+	v4l2_std_id id;
+	u32 vbi_sample_rate;
+	u16 vbi_samples_per_line;
+	u16 vbi_lines;
+	u16 vbi_offset;
+	u16 vbi_start_0;
+	u16 vbi_start_1;
+	u8 vbi_count_0;
+	u8 vbi_count_1;
+
+	u8 vbi_h_start;
+	u8 vbi_v_start;
+	u8 vbi_w;
+	u8 vbi_h;
+};
+
+/* gpio definitions */
+#define EM28XX_GPIO_EN 0x1
+
+#define EM28XX_GOP0    0x10
+#define EM28XX_GOP1    0x11
+#define EM28XX_GOP2    0x12
+#define EM28XX_GOP3    0x13
+
+#define EM28XX_GPIO0   0x0
+#define EM28XX_GPIO1   0x1
+#define EM28XX_GPIO2   0x2
+#define EM28XX_GPIO3   0x3
+#define EM28XX_GPIO4   0x4
+#define EM28XX_GPIO5   0x5
+#define EM28XX_GPIO6   0x6
+#define EM28XX_GPIO7   0x7
+
+/* helpers */
+#define _BIT_VAL(reg, val, reset) (reg  | 0x80 | (reset?1<<6:0) | (val<<5))
+
+/* internal gpio controls */
+#define EM28XX_TS1_ON         1
+#define EM28XX_ANALOG_ON      2
+#define EM28XX_LED1_ON        3
+#define EM28XX_XC3028_SECAM   4
+#define EM28XX_MODESWITCH     5
+#define EM28XX_DECODER_SLEEP  6
+#define EM28XX_LED2_ON        7
+#define EM28XX_RF             8
+#define EM28XX_DVB1_ON        9
+#define EM28XX_DVB2_ON        10
+#define EM28XX_TUNER1_ON      11
+#define EM28XX_TUNER2_ON      12
+#define EM28XX_DEMOD1_RESET   13
+#define EM28XX_TUNER1_RESET   14
+#define EM28XX_DECODER_RESET  15
+#define EM28XX_DEMOD2_RESET   16
+#define EM28XX_TUNER2_RESET   17
+#define EM28XX_I2C_BUS        18
+
+/* internal gpio control arguments */
+#define EM28XX_REG_ON       1 
+#define EM28XX_REG_OFF      2
+
+struct em28xx_gpio {
+	u16 d1_reset;
+	u16 d2_reset;
+	u16 t1_reset;
+	u16 t2_reset;
+	u16 dc_reset;
+	u16 a_on;
+	u16 l1_on;
+	u16 l2_on;
+	u16 xc3028_sec;
+	u16 m_switch;
+	u16 d_sleep;
+	u16 ts1_on;
+	u16 ts2_on;
+	u16 t1_on;
+	u16 t2_on;
+	u16 rf;
+	u16 dvbs_lnb;
+	u16 dvbs_v;
+};
+
+struct em28xx_board {
+	char *name;
+	int vchannels;
+	v4l2_std_id norm;
+	int tuner_type;
+	int tuner_addr;
+
+	/* i2c flags */
+	unsigned int em_type;
+	/* null terminated list of used i2c addresses */
+	/* used to autodetect some em2800 devices without eeprom */
+	const u8 *i2c_devs;
+	u8 tda9887_conf;
+
+	u8 has_tuner:1;
+	u8 has_inttuner:1;
+	u8 has_msp34xx:1;
+	u8 has_radio:1;
+	u8 powersaving:1;
+
+	u8 manual_gpio:1;
+	u8 ir_i2c:1; /* remote is i2c based */
+
+	struct em28xx_gpio gpio_regs;
+
+	u16 dev_modes;
+
+	enum em28xx_decoder decoder;
+
+	struct em28xx_tvnorm  tvnorms[MAX_EM28XX_TVNORMS];
+	struct em28xx_tvnorm  dvbnorms[MAX_EM28XX_DVBNORMS];
+	struct em28xx_tvnorm  fmnorms[MAX_EM28XX_FMNORMS];
+	struct em28xx_tvnorm  atscnorms[MAX_EM28XX_ATSCNORMS];
+	struct em28xx_tvnorm  qamnorms[MAX_EM28XX_QAMNORMS];
+
+	int (*ctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*gctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*qctrl)(struct v4l2_queryctrl *qctrl);
+
+	IR_KEYTAB_TYPE *ir_keytab;
+	int (*ir_getkey)(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+
+	struct em28xx_input       input[MAX_EM28XX_INPUT];
+};
+
+struct em28xx_eeprom {
+	u32 id;			/* 0x9567eb1a */
+	u16 vendor_ID;
+	u16 product_ID;
+
+	u16 chip_conf;
+
+	u16 board_conf;
+
+	u16 string1, string2, string3;
+
+	u8 string_idx_table;
+};
+
+/* device states */
+enum em28xx_dev_state {
+	DEV_INITIALIZED = 0x01,
+	DEV_DISCONNECTED = 0x02,
+	DEV_MISCONFIGURED = 0x04,
+};
+
+
+/* digital main device struct */
+
+enum mtype {
+	EM28XX_ZL10353,
+	EM28XX_MT352,
+	EM28XX_DRX3975D,
+	EM28XX_LGDT330X,
+};
+
+struct em2880_dvb {
+	char *name;
+	struct dvb_demux demux;
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+	struct mutex sem;
+#else
+	struct semaphore sem;
+#endif
+	int streaming;
+	enum mtype mod_type;
+	struct dvb_adapter adapter;
+	struct dvb_frontend        *frontend;
+	struct dvb_device *fedev;
+	struct dmxdev dmxdev;
+	struct em28xx *em28xx_dev; /* please get rid of it lateron */
+	struct dvb_net dvbnet;
+	struct usb_device *udev;	/* the usb device */
+	char *transfer_buffer[EM2880_DVB_NUM_BUFS];     /* transfer buffers for isoc transfer */
+	struct urb *urb[EM2880_DVB_NUM_BUFS];   /* urb for isoc transfers */
+	int (*demod_init)(struct dvb_frontend *fe);
+	int bw_index; /* bandwidth index */
+	int (*init_override)(struct dvb_frontend *fe);
+	u16 dtv_packetsize; 
+};
+
+struct em28xx_audio {
+	char name[50];
+	char *transfer_buffer[EM28XX_AUDIO_BUFS];
+	struct urb *urb[EM28XX_AUDIO_BUFS];
+	struct usb_device *udev;
+	unsigned int capture_transfer_done;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
+	snd_pcm_substream_t        *capture_pcm_substream;
+#else
+	struct snd_pcm_substream   *capture_pcm_substream;
+#endif
+
+	unsigned int hwptr_done_capture;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
+	snd_card_t                 *sndcard;
+#else
+	struct snd_card            *sndcard;
+#endif
+
+	int users;
+	unsigned int shutdown:1;
+	spinlock_t slock; /* for protecting the alsa buffer */
+	enum em28xx_stream_state capture_stream;
+	wait_queue_head_t audio_wait_stream, open;
+	u16 max_pck;
+	u8 alt_max;
+	/* states */
+	enum em28xx_dev_state state;
+	u8 alt;		/* alternate */
+	struct usb_interface usb_dev;
+};
+
+
+struct em2880_ir {
+	u8      old;
+	u8      sequence[4];
+	IR_KEYTAB_TYPE		*keymap;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct work;
+#else
+	struct delayed_work work;
+#endif
+
+	struct input_dev	*input;
+	struct em28xx		*dev;
+	int                     keypressed;
+	struct timer_list       timer;
+	u32                     keycode;
+
+	u32			oldval;
+	u32			oldtimeoutval;
+	u32			key;
+	u8			btn;
+
+	u8                      name[50];
+	u8			phys[50];
+	u8			released:1;
+	u8			state;
+	struct mutex state_lock;
+#define EM28XX_REMOTE_IDLE      0
+#define EM28XX_REMOTE_POLLING   1
+#define EM28XX_REMOTE_INTERRUPT 2
+	int (*get_key)(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+	wait_queue_head_t	remote_loop;
+};
+
+struct em28xx_fh {
+	struct em28xx *dev;
+	unsigned int reader:1;
+	int type;
+};
+
+
+/* main device struct */
+struct em28xx {
+	/* generic device properties */
+	char name[30];		/* name (including minor) of the device */
+	int model;		/* index in the device_data struct */
+	int devno;		/* marks the number of this device */
+	unsigned int em_type;
+	u8 video_inputs;	/* number of video inputs */
+	struct list_head	devlist;
+	u8 has_tuner:1;
+	u8 has_inttuner:1;
+	u8 has_msp34xx:1;
+	u8 has_tda9887:1;
+	u8 has_vbi:1;
+	u8 device_mode:1; /* EM28XX_VIDEO | EM28XX_DVB  */
+	u16 dev_modes;
+	u8 manual_gpio:1;
+	u8 powersaving:1;
+
+	u8 modules_requested;
+#define EM28XX_MODULES_REQUESTED 0
+#define EM28XX_MODULES_PENDING   1
+	wait_queue_head_t mod_request;
+
+	void (*request_modules)(struct em28xx *dev);
+
+	u8 vbi_interlaced:1;
+
+	u8 vbi_frame_done:1;
+
+	u8 usb_interface;
+
+	u8 audio_user;
+	u8 video_user;
+	u8 radio_user;
+	u8 fe_user;
+	u8 vbi_user;
+	u8 mode_lock;
+
+
+	int mode;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct delayed_work request_module_wk;
+#endif
+
+	struct em28xx_board *board;
+	struct usb_interface *uif;
+	struct em28xx_output_fmt *outfmt;
+
+	u32 i2s_speed;		/* I2S speed for audio digital stream */
+
+	enum em28xx_decoder decoder;
+
+	int tuner_type;		/* type of the tuner */
+	u8 tuner_addr;		/* tuner address */
+	int tda9887_conf;
+
+	struct tuner_module *tuner;
+	/* i2c i/o */
+	struct i2c_adapter i2c_adap;
+	struct i2c_client i2c_client;
+
+	/* dvb */
+	struct em2880_dvb *dvb_dev;
+	struct em28xx_audio *adev;
+	struct em2880_ir *ir_em2880;
+
+	/* video for linux */
+	u8 users;		/* user count for exclusive use */
+
+	struct video_device *rdev;	/* video for linux device struct */
+	struct video_device *vdev;	/* video for linux device struct */
+	struct video_device *vbi_dev;
+
+	struct em28xx_tvnorm *tvnorm;	/* selected tv norm */
+	struct em28xx_tvnorm *dvbnorm;
+	struct em28xx_tvnorm *atscnorm;
+	struct em28xx_tvnorm *qamnorm;
+	struct em28xx_tvnorm *fmnorm;
+	unsigned int tv_std;
+
+	unsigned long rctl_freq;		/* selected radio frequency */
+	unsigned long vctl_freq;		/* selected video frequency */
+	unsigned long dctl_freq;		/* selected dvb-t/atsc frequency */
+	u8 ctl_input;	/* selected input */
+	u8 ctl_ainput;	/* selected audio input */
+	enum enum28xx_mixchannel ctl_amix;	/* slected audio mixer channel */
+	u8 mute;
+	int volume;
+	/* frame properties */
+	struct em28xx_frame_t frame[EM28XX_NUM_FRAMES];	/* list of frames */
+	struct em28xx_frame_t vbi_frame[EM28XX_NUM_FRAMES];	/* list of frames */
+	u8 num_frames;		/* number of frames currently in use */
+	u8 vbi_num_frames;
+	u32 frame_count;	/* total number of transfered frames */
+	u32 vbi_frame_count;	/* total number of transfered frames */
+	struct em28xx_frame_t *frame_current;	/* the frame that is being filled */
+	struct em28xx_frame_t *vbi_frame_current;	/* the frame that is being filled */
+	u16 width;		/* current frame width */
+	u16 height;		/* current frame height */
+	u32 frame_size;		/* current frame size */
+	u32 field_size;		/* current field size */
+	u16 vbi_frame_size;
+	u16 vbi_field_size;
+	u16 bytesperline;
+	u16 vbi_bytesperline;
+	u16 hscale;		/* horizontal scale factor (see datasheet) */
+	u16 vscale;		/* vertical scale factor (see datasheet) */
+	u8 interlaced:1;	/* 1=interlace fileds, 0=just top fileds */
+	int type;
+
+	u8 reader:1;
+	u8 vbi_reader:1;
+	u8 video_reader:1;
+
+	/* states */
+	enum em28xx_dev_state state;
+
+	enum em28xx_stream_state stream;
+	enum em28xx_stream_state video_stream;
+	enum em28xx_stream_state vbi_stream;
+
+	enum em28xx_io_method io;
+
+	enum em28xx_io_method video_io;
+	enum em28xx_io_method vbi_io;
+
+	/* locks */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+	struct mutex lock, fileop_lock, vbi_fileop_lock, input_lock;
+#else
+	struct semaphore lock, fileop_lock, vbi_fileop_lock, input_lock;
+#endif
+	spinlock_t queue_lock;
+	spinlock_t vbi_queue_lock;
+
+	struct list_head inqueue, outqueue, vbi_inqueue, vbi_outqueue;
+	wait_queue_head_t open, wait_frame, wait_vbi_frame, vbi_wait_stream;
+	wait_queue_head_t video_wait_stream;
+	u32 video_bytesread;
+	u16 vbi_bytesread;
+	u16 vbi_dropbytes;
+
+	unsigned char eedata[256];
+
+	/* usb transfer */
+	struct usb_device *udev;	/* the usb device */
+	u8 alt;		/* alternate */
+	u16 max_pkt_size;	/* max packet size of isoc transaction */
+	u8 num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	struct urb *urb[EM28XX_NUM_BUFS];	/* urb for isoc transfers */
+	char *transfer_buffer[EM28XX_NUM_BUFS];	/* transfer buffers for isoc transfer */
+
+	/* helper funcs that call usb_control_msg */
+
+	int (*em28xx_acquire)(struct em28xx *dev, int mode, int lock);
+	int (*em28xx_callback)(void *priv, int ptr, int mode);
+
+	int (*em28xx_write_regs) (struct em28xx *dev, u16 reg, char *buf,
+				  int len);
+	int (*em28xx_read_reg) (struct em28xx *dev, u16 reg);
+	int (*em28xx_read_reg_req_len) (struct em28xx *dev, u8 req, u16 reg,
+					char *buf, int len);
+	int (*em28xx_write_regs_req) (struct em28xx *dev, u8 req, u16 reg,
+				      char *buf, int len);
+	int (*em28xx_read_reg_req) (struct em28xx *dev, u8 req, u16 reg);
+	int (*em28xx_write_reg_bits)(struct em28xx *dev, u16 reg, u8 val,
+			u8 bitmask);
+
+	int (*em28xx_ctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*em28xx_gctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*em28xx_qctrl)(struct v4l2_queryctrl *qctrl);
+
+	int (*em28xx_gpio_control)(void *priv, unsigned int command, void *ptr);
+        int (*em28xx_aad_control)(void *priv, unsigned int command, void *ptr);
+
+        struct em28xx_aad_info *aad;
+
+};
+
+struct em28xx_ops {
+	struct list_head next;
+	char *name;
+	int id;
+	int (*init)(struct em28xx *);
+	int (*fini)(struct em28xx *);
+};
+
+/* Provided by em28xx-i2c.c */
+int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr);
+void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg);
+int em28xx_i2c_register(struct em28xx *dev);
+int em28xx_i2c_unregister(struct em28xx *dev);
+
+/* Provided by em28xx-input.c */
+
+void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir);
+
+/* Provided by em28xx-core.c */
+
+u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type);
+void em28xx_queue_unusedframes(struct em28xx *dev, int type);
+void em28xx_release_buffers(struct em28xx *dev, int type);
+
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+			    char *buf, int len);
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg);
+int em28xx_read_reg(struct em28xx *dev, u16 reg);
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+			  int len);
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len);
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+			  u8 bitmask);
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val);
+int em28xx_audio_analog_set(struct em28xx *dev);
+int em28xx_colorlevels_set_default(struct em28xx *dev);
+int em28xx_capture_start(struct em28xx *dev, int start);
+int em28xx_outfmt_set_yuv422(struct em28xx *dev);
+int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin,
+			   u8 ymax);
+int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
+			    u16 width, u16 height);
+int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v);
+int em28xx_resolution_set(struct em28xx *dev);
+int em28xx_init_isoc(struct em28xx *dev);
+void em28xx_uninit_isoc(struct em28xx *dev);
+int em28xx_set_alternate(struct em28xx *dev);
+int em28xx_register_extension(struct em28xx_ops *dev);
+void em28xx_unregister_extension(struct em28xx_ops *dev);
+int em2880_ir_detach(struct em28xx *dev);
+int em2880_ir_attach(struct em28xx *dev);
+int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em28xx_set_vbi(struct em28xx *dev, int enable);
+
+
+extern struct workqueue_struct *em28xx_wq;
+
+
+/* Provided by em28xx-cards.c */
+extern int em2800_variant_detect(struct usb_device* udev, int model);
+extern int em28xx_card_setup(struct em28xx *dev);
+extern void em28xx_card_disconnect(struct em28xx *dev);
+extern struct em28xx_board em28xx_boards[];
+extern struct usb_device_id em28xx_id_table[];
+extern const unsigned int em28xx_bcount;
+extern int em28xx_gpio_control(void *priv, unsigned int command, void *ptr);
+extern int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr);
+extern int em28xx_qt1010_reset_control(void *priv, int command, void *ptr);
+
+/* Videology specific functions */
+
+/* white balance mode */
+#define V4L2_VY_WBM                 (V4L2_CID_PRIVATE_BASE+0)
+/* shutter speed */
+#define V4L2_VY_SHUTTER             (V4L2_CID_PRIVATE_BASE+1)
+/* mirror mode */
+#define V4L2_VY_MIRROR              (V4L2_CID_PRIVATE_BASE+2)
+/* gain control on/off */
+#define V4L2_VY_GAIN_CONTROL_EN     (V4L2_CID_PRIVATE_BASE+3)
+/* gain control values 0x00 - 0x7f */
+#define V4L2_VY_GAIN_CONTROL        (V4L2_CID_PRIVATE_BASE+4)
+/* enable edge enhance */
+#define V4L2_VY_EDGE_ENHANCE_EN     (V4L2_CID_PRIVATE_BASE+5)
+/* edge enhance values 0x00 - 0x1f */
+#define V4L2_VY_EDGE_ENHANCE        (V4L2_CID_PRIVATE_BASE+6)
+/* BLC (???) values 0x00 (off) - 0x40 */
+#define V4L2_VY_BLC                 (V4L2_CID_PRIVATE_BASE+7)
+/* RGain value    0x00-0xff */
+#define V4L2_VY_RGAIN               (V4L2_CID_PRIVATE_BASE+8)
+/* BGain value    0x00-0xff */
+#define V4L2_VY_BGAIN               (V4L2_CID_PRIVATE_BASE+9)
+
+
+int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl);
+int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl);
+int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl);
+
+/* em28xx registers */
+#define R06_I2C_CLK_REG         0x06
+#define R0A_CHIPID_REG		0x0a
+#define R0C_USBSUSP_REG		0x0c
+
+#define R0E_AUDIOSRC_REG	0x0e
+#define R0F_XCLK_REG  		0x0f
+
+#define R10_VINMODE_REG        	0x10
+#define R11_VINCTRL_REG        	0x11
+#define R12_VINENABLE_REG      	0x12
+
+#define R14_GAMMA_REG  		0x14
+#define R15_RGAIN_REG  		0x15
+#define R16_GGAIN_REG  		0x16
+#define R17_BGAIN_REG  		0x17
+#define R18_ROFFSET_REG        	0x18
+#define R19_GOFFSET_REG        	0x19
+#define R1A_BOFFSET_REG        	0x1a
+
+#define R1B_OFLOW_REG  		0x1b
+#define R1C_HSTART_REG 		0x1c
+#define R1D_VSTART_REG 		0x1d
+#define R1E_CWIDTH_REG 		0x1e
+#define R1F_CHEIGHT_REG        	0x1f
+
+#define R20_YGAIN_REG  		0x20
+#define R21_YOFFSET_REG        	0x21
+#define R22_UVGAIN_REG 		0x22
+#define R23_UOFFSET_REG        	0x23
+#define R24_VOFFSET_REG        	0x24
+#define R25_SHARPNESS_REG      	0x25
+
+#define R26_COMPR_REG  		0x26
+#define R27_OUTFMT_REG 		0x27
+
+#define R28_XMIN_REG   		0x28
+#define R29_XMAX_REG   		0x29
+#define R2A_YMIN_REG   		0x2a
+#define R2B_YMAX_REG   		0x2b
+
+#define R30_HSCALELOW_REG      	0x30
+#define R31_HSCALEHIGH_REG     	0x31
+#define R32_VSCALELOW_REG      	0x32
+#define R33_VSCALEHIGH_REG     	0x33
+
+#define R40_AC97LSB_REG        	0x40
+#define R41_AC97MSB_REG        	0x41
+#define R42_AC97ADDR_REG       	0x42
+#define R43_AC97BUSY_REG       	0x43
+
+/* em202 registers */
+#define R02_MASTER_AC97     	0x02
+#define R10_LINE_IN_AC97        0x10
+#define R14_VIDEO_AC97 		0x14
+
+/* em2800 registers */
+#define EM2800_AUDIOSRC_REG 	0x08
+
+/* register settings */
+#define EM2800_AUDIO_SRC_TUNER  0x0d
+#define EM2800_AUDIO_SRC_LINE   0x0c
+#define EM28XX_AUDIO_SRC_TUNER	0xc0
+#define EM28XX_AUDIO_SRC_LINE	0x80
+
+/* printk macros */
+
+#define em28xx_err(fmt, arg...) do {\
+	printk(KERN_ERR fmt , ##arg); } while (0)
+
+#define em28xx_errdev(fmt, arg...) do {\
+	printk(KERN_ERR "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+
+#define em28xx_info(fmt, arg...) do {\
+	printk(KERN_INFO "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+#define em28xx_warn(fmt, arg...) do {\
+	printk(KERN_WARNING "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+
+void em28xx_config_i2c(struct em28xx *dev);
+int em28xx_config(struct em28xx *dev);
+
+static inline int em28xx_audio_source(struct em28xx *dev, int input)
+{
+	if (dev->em_type == EM2800) {
+		u8 tmp = EM2800_AUDIO_SRC_TUNER;
+		if (input == EM28XX_AUDIO_SRC_LINE)
+			tmp = EM2800_AUDIO_SRC_LINE;
+		em28xx_write_regs(dev, EM2800_AUDIOSRC_REG, &tmp, 1);
+	}
+	return em28xx_write_reg_bits(dev, R0E_AUDIOSRC_REG, input, 0xc0);
+}
+
+/* FIXME: return something sane here */
+
+#define EM28XX_MUTED   1
+#define EM28XX_UNMUTED 0
+
+static inline int em28xx_audio_usb_mute(struct em28xx *dev, int mute)
+{
+	switch (dev->em_type) {
+	case EM2750:
+		em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1);
+		break;
+	default:
+		em28xx_write_reg_bits(dev, R0F_XCLK_REG, mute ? 0x00 : 0x80, 0x80);
+	}
+	return 0;
+}
+
+static inline int em28xx_audio_analog_setup(struct em28xx *dev)
+{
+	/* unmute video mixer with default volume level */
+	return em28xx_write_ac97(dev, R14_VIDEO_AC97, "\x08\x08");
+}
+
+static inline int em28xx_audio_set_mixer(struct em28xx *dev, enum  enum28xx_mixchannel chan)
+{
+	int ret = 0;
+
+	if (chan == EM28XX_MIX_NOTOUCH)
+		return ret;
+	if ((ret = em28xx_write_ac97(dev, R10_LINE_IN_AC97,
+					chan == EM28XX_MIX_LINE_IN ?
+					"\x08\x08" : "\x08\x88")))
+		return ret;
+	if ((ret = em28xx_write_ac97(dev, R14_VIDEO_AC97, chan == EM28XX_MIX_VIDEO ?
+					"\x08\x08":"\x08\x88")))
+		return ret;
+	return ret;
+}
+
+static inline int em28xx_compression_disable(struct em28xx *dev)
+{
+	/* side effect of disabling scaler and mixer */
+	return em28xx_write_regs(dev, R26_COMPR_REG, "\x00", 1);
+}
+
+static inline int em28xx_contrast_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R20_YGAIN_REG) & 0x1f;
+}
+
+static inline int em28xx_brightness_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R21_YOFFSET_REG);
+}
+
+static inline int em28xx_saturation_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R22_UVGAIN_REG) & 0x1f;
+}
+
+static inline int em28xx_u_balance_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R23_UOFFSET_REG);
+}
+
+static inline int em28xx_v_balance_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R24_VOFFSET_REG);
+}
+
+static inline int em28xx_gamma_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R14_GAMMA_REG) & 0x3f;
+}
+
+static inline int em28xx_contrast_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R20_YGAIN_REG, &tmp, 1);
+}
+
+static inline int em28xx_brightness_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R21_YOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_saturation_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R22_UVGAIN_REG, &tmp, 1);
+}
+
+static inline int em28xx_u_balance_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R23_UOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_v_balance_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R24_VOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_gamma_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R14_GAMMA_REG, &tmp, 1);
+}
+
+/*FIXME: especially em2800 devices have a too small framebuffer
+  for 720/YUY2 frames */
+static inline unsigned int norm_maxw(struct em28xx *dev)
+{
+	switch (dev->model) {
+		default: return 720;
+	}
+}
+
+static inline unsigned int norm_maxh(struct em28xx *dev)
+{
+	switch (dev->model) {
+		default: return (dev->tvnorm->id & V4L2_STD_625_50) ? 576 : 480;
+	}
+}
+
+#endif

^ permalink raw reply related	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2008-11-30  6:23 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <d9def9db0810221359h5118b8d2pd6d2b3f4f95496ce@mail.gmail.com>
     [not found] ` <20081024153509.0f51d676@pedra.chehab.org>
2008-10-24 20:15   ` [PATCH 1/7] Adding empia base driver Markus Rechberger
2008-10-24 20:20     ` Markus Rechberger
2008-11-01 14:05 Hans Verkuil
  -- strict thread matches above, loose matches on Subject: below --
2008-10-22 21:14 Markus Rechberger
2008-10-22 22:09 ` Greg KH
2008-10-22 22:24   ` Markus Rechberger
2008-10-22 22:26     ` Markus Rechberger
2008-10-22 22:27     ` Greg KH
2008-10-22 22:35       ` Markus Rechberger
2008-10-22 22:49         ` Greg KH
2008-10-23  8:53           ` el es
2008-11-26 19:12     ` Aidan Thornton
2008-11-27  4:25       ` Markus Rechberger
2008-11-27  9:33         ` Pekka Enberg
2008-11-28 15:48           ` Devin Heitmueller
2008-11-28 20:09           ` Greg KH
2008-11-30  6:23             ` Markus Rechberger
2008-10-23  9:29   ` Alan Cox
2008-10-23 11:10     ` Markus Rechberger
2008-11-01 13:59 ` Hans Verkuil
2008-11-02  4:27   ` Mauro Carvalho Chehab
     [not found]     ` <a2aa6e3a0811072150t535e802cge3375a7b88ee6287@mail.gmail.com>
2008-11-08 10:15       ` Mauro Carvalho Chehab
2008-11-08 10:22         ` Markus Rechberger
2008-11-08 10:37           ` Mauro Carvalho Chehab
2008-11-08 10:42             ` Markus Rechberger
2008-11-08 10:46               ` Markus Rechberger
2008-11-08 10:56               ` Mauro Carvalho Chehab
2008-11-08 11:02                 ` Markus Rechberger
2008-11-26 20:36   ` Aidan Thornton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).