Re: [PATCH V2 5/5] [media] netup_unidvb: NetUP Universal DVB-S/S2/T/T2/C PCI-E card driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Em Wed, 15 Apr 2015 13:07:50 +0300
serjk@xxxxxxxx escreveu:

> From: Kozlov Sergey <serjk@xxxxxxxx>
> 
> Add NetUP Dual Universal CI PCIe board driver.
> The board has
>     - two CI slots
>     - two I2C adapters
>     - SPI master bus for accessing flash memory containing
>       FPGA firmware
> 
> Changes in version 2:
>     - rename driver directory from netup to netup_unidvb
>     - rename MAINTAINERS entry
>     - fix coding style
>     - use dynamic debug instead of module-specifig debug parameters
>     - remove unneccecary msleep in the I2C driver code
>     - use videobuf2 API instead of videobuf
> 
> Signed-off-by: Kozlov Sergey <serjk@xxxxxxxx>

This one looked ok.

I'll mark the hole series as "changes requested" at patchwork, as
it doesn't make sense to apply just this patch without the previous
ones. So, resubmit this one at the version 3.

Thanks!
Mauro

> ---
>  MAINTAINERS                                        |    9 +
>  drivers/media/pci/Kconfig                          |    1 +
>  drivers/media/pci/Makefile                         |    3 +-
>  drivers/media/pci/netup_unidvb/Kconfig             |   12 +
>  drivers/media/pci/netup_unidvb/Makefile            |    9 +
>  drivers/media/pci/netup_unidvb/netup_unidvb.h      |  133 +++
>  drivers/media/pci/netup_unidvb/netup_unidvb_ci.c   |  248 +++++
>  drivers/media/pci/netup_unidvb/netup_unidvb_core.c | 1002 ++++++++++++++++++++
>  drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c  |  381 ++++++++
>  drivers/media/pci/netup_unidvb/netup_unidvb_spi.c  |  252 +++++
>  10 files changed, 2049 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/media/pci/netup_unidvb/Kconfig
>  create mode 100644 drivers/media/pci/netup_unidvb/Makefile
>  create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb.h
>  create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_ci.c
>  create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_core.c
>  create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c
>  create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 968a044..bf6f3cb 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6305,6 +6305,15 @@ T:	git git://linuxtv.org/media_tree.git
>  S:	Supported
>  F:	drivers/media/dvb-frontends/lnbh25*
>  
> +MEDIA DRIVERS FOR NETUP PCI UNIVERSAL DVB devices
> +M:	Sergey Kozlov <serjk@xxxxxxxx>
> +L:	linux-media@xxxxxxxxxxxxxxx
> +W:	http://linuxtv.org/
> +W:	http://netup.tv/
> +T:	git git://linuxtv.org/media_tree.git
> +S:	Supported
> +F:	drivers/media/pci/netup_unidvb/*
> +
>  MEDIA INPUT INFRASTRUCTURE (V4L/DVB)
>  M:	Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxxxx>
>  P:	LinuxTV.org Project
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index 218144a..4bc9188 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -47,6 +47,7 @@ source "drivers/media/pci/mantis/Kconfig"
>  source "drivers/media/pci/ngene/Kconfig"
>  source "drivers/media/pci/ddbridge/Kconfig"
>  source "drivers/media/pci/smipcie/Kconfig"
> +source "drivers/media/pci/netup_unidvb/Kconfig"
>  endif
>  
>  endif #MEDIA_PCI_SUPPORT
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index 0baf0d2..515cf0f 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -12,7 +12,8 @@ obj-y        +=	ttpci/		\
>  		ngene/		\
>  		ddbridge/	\
>  		saa7146/	\
> -		smipcie/
> +		smipcie/	\
> +		netup_unidvb/
>  
>  obj-$(CONFIG_VIDEO_IVTV) += ivtv/
>  obj-$(CONFIG_VIDEO_ZORAN) += zoran/
> diff --git a/drivers/media/pci/netup_unidvb/Kconfig b/drivers/media/pci/netup_unidvb/Kconfig
> new file mode 100644
> index 0000000..f277b0b
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/Kconfig
> @@ -0,0 +1,12 @@
> +config DVB_NETUP_UNIDVB
> +	tristate "NetUP Universal DVB card support"
> +	depends on DVB_CORE && VIDEO_DEV && PCI && I2C && SPI_MASTER
> +    select VIDEOBUF2_DVB
> +    select VIDEOBUF2_VMALLOC
> +	select DVB_HORUS3A if MEDIA_SUBDRV_AUTOSELECT
> +	select DVB_ASCOT2E if MEDIA_SUBDRV_AUTOSELECT
> +	select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT
> +	select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT
> +	---help---
> +	  Support for NetUP PCI express Universal DVB card.
> +
> diff --git a/drivers/media/pci/netup_unidvb/Makefile b/drivers/media/pci/netup_unidvb/Makefile
> new file mode 100644
> index 0000000..ee6ae05
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/Makefile
> @@ -0,0 +1,9 @@
> +netup-unidvb-objs += netup_unidvb_core.o
> +netup-unidvb-objs += netup_unidvb_i2c.o
> +netup-unidvb-objs += netup_unidvb_ci.o
> +netup-unidvb-objs += netup_unidvb_spi.o
> +
> +obj-$(CONFIG_DVB_NETUP_UNIDVB) += netup-unidvb.o
> +
> +ccflags-y += -Idrivers/media/dvb-core
> +ccflags-y += -Idrivers/media/dvb-frontends
> diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb.h b/drivers/media/pci/netup_unidvb/netup_unidvb.h
> new file mode 100644
> index 0000000..fa95110
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/netup_unidvb.h
> @@ -0,0 +1,133 @@
> +/*
> + * netup_unidvb.h
> + *
> + * Data type definitions for NetUP Universal Dual DVB-CI
> + *
> + * Copyright (C) 2014 NetUP Inc.
> + * Copyright (C) 2014 Sergey Kozlov <serjk@xxxxxxxx>
> + * Copyright (C) 2014 Abylay Ospan <aospan@xxxxxxxx>
> + *
> + * 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.
> + */
> +
> +#include <linux/pci.h>
> +#include <linux/i2c.h>
> +#include <linux/workqueue.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-dvb.h>
> +#include <dvb_ca_en50221.h>
> +
> +#define NETUP_UNIDVB_NAME	"netup_unidvb"
> +#define NETUP_UNIDVB_VERSION	"0.0.1"
> +#define NETUP_VENDOR_ID		0x1b55
> +#define NETUP_PCI_DEV_REVISION  0x2
> +
> +/* IRQ-related regisers */
> +#define REG_ISR			0x4890
> +#define REG_ISR_MASKED		0x4892
> +#define REG_IMASK_SET		0x4894
> +#define REG_IMASK_CLEAR		0x4896
> +/* REG_ISR register bits */
> +#define NETUP_UNIDVB_IRQ_SPI	(1 << 0)
> +#define NETUP_UNIDVB_IRQ_I2C0	(1 << 1)
> +#define NETUP_UNIDVB_IRQ_I2C1	(1 << 2)
> +#define NETUP_UNIDVB_IRQ_FRA0	(1 << 4)
> +#define NETUP_UNIDVB_IRQ_FRA1	(1 << 5)
> +#define NETUP_UNIDVB_IRQ_FRB0	(1 << 6)
> +#define NETUP_UNIDVB_IRQ_FRB1	(1 << 7)
> +#define NETUP_UNIDVB_IRQ_DMA1	(1 << 8)
> +#define NETUP_UNIDVB_IRQ_DMA2	(1 << 9)
> +#define NETUP_UNIDVB_IRQ_CI	(1 << 10)
> +#define NETUP_UNIDVB_IRQ_CAM0	(1 << 11)
> +#define NETUP_UNIDVB_IRQ_CAM1	(1 << 12)
> +
> +struct netup_dma {
> +	u8			num;
> +	spinlock_t		lock;
> +	struct netup_unidvb_dev	*ndev;
> +	struct netup_dma_regs	*regs;
> +	u32			ring_buffer_size;
> +	u8			*addr_virt;
> +	dma_addr_t		addr_phys;
> +	u64			addr_last;
> +	u32			high_addr;
> +	u32			data_offset;
> +	u32			data_size;
> +	struct list_head	free_buffers;
> +	struct work_struct	work;
> +	struct timer_list	timeout;
> +};
> +
> +enum netup_i2c_state {
> +	STATE_DONE,
> +	STATE_WAIT,
> +	STATE_WANT_READ,
> +	STATE_WANT_WRITE,
> +	STATE_ERROR
> +};
> +
> +struct netup_i2c_regs;
> +
> +struct netup_i2c {
> +	spinlock_t			lock;
> +	wait_queue_head_t		wq;
> +	struct i2c_adapter		adap;
> +	struct netup_unidvb_dev		*dev;
> +	struct netup_i2c_regs		*regs;
> +	struct i2c_msg			*msg;
> +	enum netup_i2c_state		state;
> +	u32				xmit_size;
> +};
> +
> +struct netup_ci_state {
> +	struct dvb_ca_en50221		ca;
> +	u8 __iomem			*membase8_config;
> +	u8 __iomem			*membase8_io;
> +	struct netup_unidvb_dev		*dev;
> +	int status;
> +	int nr;
> +};
> +
> +struct netup_spi;
> +
> +struct netup_unidvb_dev {
> +	struct pci_dev			*pci_dev;
> +	int				pci_bus;
> +	int				pci_slot;
> +	int				pci_func;
> +	int				board_num;
> +	int				old_fw;
> +	u32 __iomem			*lmmio0;
> +	u8 __iomem			*bmmio0;
> +	u32 __iomem			*lmmio1;
> +	u8 __iomem			*bmmio1;
> +	u8				*dma_virt;
> +	dma_addr_t			dma_phys;
> +	u32				dma_size;
> +	struct vb2_dvb_frontends	frontends[2];
> +	struct netup_i2c		i2c[2];
> +	struct workqueue_struct		*wq;
> +	struct netup_dma		dma[2];
> +	struct netup_ci_state		ci[2];
> +	struct netup_spi		*spi;
> +};
> +
> +int netup_i2c_register(struct netup_unidvb_dev *ndev);
> +void netup_i2c_unregister(struct netup_unidvb_dev *ndev);
> +irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev);
> +irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c);
> +irqreturn_t netup_spi_interrupt(struct netup_spi *spi);
> +int netup_unidvb_ci_register(struct netup_unidvb_dev *dev,
> +			     int num, struct pci_dev *pci_dev);
> +void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num);
> +int netup_spi_init(struct netup_unidvb_dev *ndev);
> +void netup_spi_release(struct netup_unidvb_dev *ndev);
> diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c
> new file mode 100644
> index 0000000..751b51b
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c
> @@ -0,0 +1,248 @@
> +/*
> + * netup_unidvb_ci.c
> + *
> + * DVB CAM support for NetUP Universal Dual DVB-CI
> + *
> + * Copyright (C) 2014 NetUP Inc.
> + * Copyright (C) 2014 Sergey Kozlov <serjk@xxxxxxxx>
> + * Copyright (C) 2014 Abylay Ospan <aospan@xxxxxxxx>
> + *
> + * 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.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/kmod.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include "netup_unidvb.h"
> +
> +/* CI slot 0 base address */
> +#define CAM0_CONFIG		0x0
> +#define CAM0_IO			0x8000
> +#define CAM0_MEM		0x10000
> +#define CAM0_SZ			32
> +/* CI slot 1 base address */
> +#define CAM1_CONFIG		0x20000
> +#define CAM1_IO			0x28000
> +#define CAM1_MEM		0x30000
> +#define CAM1_SZ			32
> +/* ctrlstat registers */
> +#define CAM_CTRLSTAT_READ_SET	0x4980
> +#define CAM_CTRLSTAT_CLR	0x4982
> +/* register bits */
> +#define BIT_CAM_STCHG		(1<<0)
> +#define BIT_CAM_PRESENT		(1<<1)
> +#define BIT_CAM_RESET		(1<<2)
> +#define BIT_CAM_BYPASS		(1<<3)
> +#define BIT_CAM_READY		(1<<4)
> +#define BIT_CAM_ERROR		(1<<5)
> +#define BIT_CAM_OVERCURR	(1<<6)
> +/* BIT_CAM_BYPASS bit shift for SLOT 1 */
> +#define CAM1_SHIFT 8
> +
> +irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev)
> +{
> +	writew(0x101, ndev->bmmio0 + CAM_CTRLSTAT_CLR);
> +	return IRQ_HANDLED;
> +}
> +
> +static int netup_unidvb_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221,
> +				       int slot)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +	u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
> +
> +	dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x\n",
> +		__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
> +	if (slot != 0)
> +		return -EINVAL;
> +	/* pass data to CAM module */
> +	writew(BIT_CAM_BYPASS << shift, dev->bmmio0 + CAM_CTRLSTAT_CLR);
> +	dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x done\n",
> +		__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
> +	return 0;
> +}
> +
> +static int netup_unidvb_ci_slot_shutdown(struct dvb_ca_en50221 *en50221,
> +					 int slot)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +
> +	dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__);
> +	return 0;
> +}
> +
> +static int netup_unidvb_ci_slot_reset(struct dvb_ca_en50221 *en50221,
> +				      int slot)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +	unsigned long timeout = 0;
> +	u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
> +	u16 ci_stat = 0;
> +	int reset_counter = 3;
> +
> +	dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n",
> +		__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
> +reset:
> +	timeout = jiffies + msecs_to_jiffies(5000);
> +	/* start reset */
> +	writew(BIT_CAM_RESET << shift, dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
> +	dev_dbg(&dev->pci_dev->dev, "%s(): waiting for reset\n", __func__);
> +	/* wait until reset done */
> +	while (time_before(jiffies, timeout)) {
> +		ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
> +		if (ci_stat & (BIT_CAM_READY << shift))
> +			break;
> +		udelay(1000);
> +	}
> +	if (!(ci_stat & (BIT_CAM_READY << shift)) && reset_counter > 0) {
> +		dev_dbg(&dev->pci_dev->dev,
> +			"%s(): CAMP reset timeout! Will try again..\n",
> +			 __func__);
> +		reset_counter--;
> +		goto reset;
> +	}
> +	return 0;
> +}
> +
> +static int netup_unidvb_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
> +					    int slot, int open)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +	u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
> +	u16 ci_stat = 0;
> +
> +	dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n",
> +		__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
> +	ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
> +	if (ci_stat & (BIT_CAM_READY << shift)) {
> +		state->status = DVB_CA_EN50221_POLL_CAM_PRESENT |
> +			DVB_CA_EN50221_POLL_CAM_READY;
> +	} else if (ci_stat & (BIT_CAM_PRESENT << shift)) {
> +		state->status = DVB_CA_EN50221_POLL_CAM_PRESENT;
> +	} else {
> +		state->status = 0;
> +	}
> +	return state->status;
> +}
> +
> +static int netup_unidvb_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221,
> +					      int slot, int addr)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +	u8 val = state->membase8_config[addr];
> +
> +	dev_dbg(&dev->pci_dev->dev,
> +		"%s(): addr=0x%x val=0x%x\n", __func__, addr, val);
> +	return val;
> +}
> +
> +static int netup_unidvb_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221,
> +					       int slot, int addr, u8 data)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +
> +	dev_dbg(&dev->pci_dev->dev,
> +		"%s(): addr=0x%x data=0x%x\n", __func__, addr, data);
> +	state->membase8_config[addr] = data;
> +	return 0;
> +}
> +
> +static int netup_unidvb_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221,
> +					int slot, u8 addr)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +	u8 val = state->membase8_io[addr];
> +
> +	dev_dbg(&dev->pci_dev->dev,
> +		"%s(): addr=0x%x val=0x%x\n", __func__, addr, val);
> +	return val;
> +}
> +
> +static int netup_unidvb_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221,
> +					 int slot, u8 addr, u8 data)
> +{
> +	struct netup_ci_state *state = en50221->data;
> +	struct netup_unidvb_dev *dev = state->dev;
> +
> +	dev_dbg(&dev->pci_dev->dev,
> +		"%s(): addr=0x%x data=0x%x\n", __func__, addr, data);
> +	state->membase8_io[addr] = data;
> +	return 0;
> +}
> +
> +int netup_unidvb_ci_register(struct netup_unidvb_dev *dev,
> +			     int num, struct pci_dev *pci_dev)
> +{
> +	int result;
> +	struct netup_ci_state *state;
> +
> +	if (num < 0 || num > 1) {
> +		dev_err(&pci_dev->dev, "%s(): invalid CI adapter %d\n",
> +			__func__, num);
> +		return -EINVAL;
> +	}
> +	state = &dev->ci[num];
> +	state->nr = num;
> +	state->membase8_config = dev->bmmio1 +
> +		((num == 0) ? CAM0_CONFIG : CAM1_CONFIG);
> +	state->membase8_io = dev->bmmio1 +
> +		((num == 0) ? CAM0_IO : CAM1_IO);
> +	state->dev = dev;
> +	state->ca.owner = THIS_MODULE;
> +	state->ca.read_attribute_mem = netup_unidvb_ci_read_attribute_mem;
> +	state->ca.write_attribute_mem = netup_unidvb_ci_write_attribute_mem;
> +	state->ca.read_cam_control = netup_unidvb_ci_read_cam_ctl;
> +	state->ca.write_cam_control = netup_unidvb_ci_write_cam_ctl;
> +	state->ca.slot_reset = netup_unidvb_ci_slot_reset;
> +	state->ca.slot_shutdown = netup_unidvb_ci_slot_shutdown;
> +	state->ca.slot_ts_enable = netup_unidvb_ci_slot_ts_ctl;
> +	state->ca.poll_slot_status = netup_unidvb_poll_ci_slot_status;
> +	state->ca.data = state;
> +	result = dvb_ca_en50221_init(&dev->frontends[num].adapter,
> +		&state->ca, 0, 1);
> +	if (result < 0) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): dvb_ca_en50221_init result %d\n",
> +			__func__, result);
> +		return result;
> +	}
> +	writew(NETUP_UNIDVB_IRQ_CI, (u16 *)(dev->bmmio0 + REG_IMASK_SET));
> +	dev_info(&pci_dev->dev,
> +		"%s(): CI adapter %d init done\n", __func__, num);
> +	return 0;
> +}
> +
> +void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num)
> +{
> +	struct netup_ci_state *state;
> +
> +	dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__);
> +	if (num < 0 || num > 1) {
> +		dev_err(&dev->pci_dev->dev, "%s(): invalid CI adapter %d\n",
> +				__func__, num);
> +		return;
> +	}
> +	state = &dev->ci[num];
> +	dvb_ca_en50221_release(&state->ca);
> +}
> +
> diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_core.c b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c
> new file mode 100644
> index 0000000..181dd6a
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c
> @@ -0,0 +1,1002 @@
> +/*
> + * netup_unidvb_core.c
> + *
> + * Main module for NetUP Universal Dual DVB-CI
> + *
> + * Copyright (C) 2014 NetUP Inc.
> + * Copyright (C) 2014 Sergey Kozlov <serjk@xxxxxxxx>
> + * Copyright (C) 2014 Abylay Ospan <aospan@xxxxxxxx>
> + *
> + * 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.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/kmod.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/list.h>
> +#include <media/videobuf2-vmalloc.h>
> +
> +#include "netup_unidvb.h"
> +#include "cxd2841er.h"
> +#include "horus3a.h"
> +#include "ascot2e.h"
> +#include "lnbh25.h"
> +
> +static int spi_enable;
> +module_param(spi_enable, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
> +
> +MODULE_DESCRIPTION("Driver for NetUP Dual Universal DVB CI PCIe card");
> +MODULE_AUTHOR("info@xxxxxxxx");
> +MODULE_VERSION(NETUP_UNIDVB_VERSION);
> +MODULE_LICENSE("GPL");
> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
> +
> +/* Avalon-MM PCI-E registers */
> +#define	AVL_PCIE_IENR		0x50
> +#define AVL_PCIE_ISR		0x40
> +#define AVL_IRQ_ENABLE		0x80
> +#define AVL_IRQ_ASSERTED	0x80
> +/* GPIO registers */
> +#define GPIO_REG_IO		0x4880
> +#define GPIO_REG_IO_TOGGLE	0x4882
> +#define GPIO_REG_IO_SET		0x4884
> +#define GPIO_REG_IO_CLEAR	0x4886
> +/* GPIO bits */
> +#define GPIO_FEA_RESET		(1 << 0)
> +#define GPIO_FEB_RESET		(1 << 1)
> +#define GPIO_RFA_CTL		(1 << 2)
> +#define GPIO_RFB_CTL		(1 << 3)
> +#define GPIO_FEA_TU_RESET	(1 << 4)
> +#define GPIO_FEB_TU_RESET	(1 << 5)
> +/* DMA base address */
> +#define NETUP_DMA0_ADDR		0x4900
> +#define NETUP_DMA1_ADDR		0x4940
> +/* 8 DMA blocks * 128 packets * 188 bytes*/
> +#define NETUP_DMA_BLOCKS_COUNT	8
> +#define NETUP_DMA_PACKETS_COUNT	128
> +/* DMA status bits */
> +#define BIT_DMA_RUN		1
> +#define BIT_DMA_ERROR		2
> +#define BIT_DMA_IRQ		0x200
> +
> +/**
> + * struct netup_dma_regs - the map of DMA module registers
> + * @ctrlstat_set:	Control register, write to set control bits
> + * @ctrlstat_clear:	Control register, write to clear control bits
> + * @start_addr_lo:	DMA ring buffer start address, lower part
> + * @start_addr_hi:	DMA ring buffer start address, higher part
> + * @size:		DMA ring buffer size register
> +			Bits [0-7]:	DMA packet size, 188 bytes
> +			Bits [16-23]:	packets count in block, 128 packets
> +			Bits [24-31]:	blocks count, 8 blocks
> + * @timeout:		DMA timeout in units of 8ns
> +			For example, value of 375000000 equals to 3 sec
> + * @curr_addr_lo:	Current ring buffer head address, lower part
> + * @curr_addr_hi:	Current ring buffer head address, higher part
> + * @stat_pkt_received:	Statistic register, not tested
> + * @stat_pkt_accepted:	Statistic register, not tested
> + * @stat_pkt_overruns:	Statistic register, not tested
> + * @stat_pkt_underruns:	Statistic register, not tested
> + * @stat_fifo_overruns:	Statistic register, not tested
> + */
> +struct netup_dma_regs {
> +	__le32	ctrlstat_set;
> +	__le32	ctrlstat_clear;
> +	__le32	start_addr_lo;
> +	__le32	start_addr_hi;
> +	__le32	size;
> +	__le32	timeout;
> +	__le32	curr_addr_lo;
> +	__le32	curr_addr_hi;
> +	__le32	stat_pkt_received;
> +	__le32	stat_pkt_accepted;
> +	__le32	stat_pkt_overruns;
> +	__le32	stat_pkt_underruns;
> +	__le32	stat_fifo_overruns;
> +} __packed __aligned(1);
> +
> +struct netup_unidvb_buffer {
> +	struct vb2_buffer	vb;
> +	struct list_head	list;
> +	u32			size;
> +};
> +
> +static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc);
> +static void netup_unidvb_queue_cleanup(struct netup_dma *dma);
> +
> +static struct cxd2841er_config demod_config = {
> +	.i2c_addr = 0xc8
> +};
> +
> +static struct horus3a_config horus3a_conf = {
> +	.i2c_address = 0xc0,
> +	.xtal_freq_mhz = 16,
> +	.set_tuner_callback = netup_unidvb_tuner_ctrl
> +};
> +
> +static struct ascot2e_config ascot2e_conf = {
> +	.i2c_address = 0xc2,
> +	.set_tuner_callback = netup_unidvb_tuner_ctrl
> +};
> +
> +static struct lnbh25_config lnbh25_conf = {
> +	.i2c_address = 0x10,
> +	.data2_config = LNBH25_TEN | LNBH25_EXTM
> +};
> +
> +static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc)
> +{
> +	u8 reg, mask;
> +	struct netup_dma *dma = priv;
> +	struct netup_unidvb_dev *ndev;
> +
> +	if (!priv)
> +		return -EINVAL;
> +	ndev = dma->ndev;
> +	dev_dbg(&ndev->pci_dev->dev, "%s(): num %d is_dvb_tc %d\n",
> +		__func__, dma->num, is_dvb_tc);
> +	reg = readb(ndev->bmmio0 + GPIO_REG_IO);
> +	mask = (dma->num == 0) ? GPIO_RFA_CTL : GPIO_RFB_CTL;
> +	if (!is_dvb_tc)
> +		reg |= mask;
> +	else
> +		reg &= ~mask;
> +	writeb(reg, ndev->bmmio0 + GPIO_REG_IO);
> +	return 0;
> +}
> +
> +static void netup_unidvb_dev_enable(struct netup_unidvb_dev *ndev)
> +{
> +	u16 gpio_reg;
> +
> +	/* enable PCI-E interrupts */
> +	writel(AVL_IRQ_ENABLE, ndev->bmmio0 + AVL_PCIE_IENR);
> +	/* unreset frontends bits[0:1] */
> +	writeb(0x00, ndev->bmmio0 + GPIO_REG_IO);
> +	msleep(100);
> +	gpio_reg =
> +		GPIO_FEA_RESET | GPIO_FEB_RESET |
> +		GPIO_FEA_TU_RESET | GPIO_FEB_TU_RESET |
> +		GPIO_RFA_CTL | GPIO_RFB_CTL;
> +	writeb(gpio_reg, ndev->bmmio0 + GPIO_REG_IO);
> +	dev_dbg(&ndev->pci_dev->dev,
> +		"%s(): AVL_PCIE_IENR 0x%x GPIO_REG_IO 0x%x\n",
> +		__func__, readl(ndev->bmmio0 + AVL_PCIE_IENR),
> +		(int)readb(ndev->bmmio0 + GPIO_REG_IO));
> +
> +}
> +
> +static void netup_unidvb_dma_enable(struct netup_dma *dma, int enable)
> +{
> +	u32 irq_mask = (dma->num == 0 ?
> +		NETUP_UNIDVB_IRQ_DMA1 : NETUP_UNIDVB_IRQ_DMA2);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev,
> +		"%s(): DMA%d enable %d\n", __func__, dma->num, enable);
> +	if (enable) {
> +		writel(BIT_DMA_RUN, &dma->regs->ctrlstat_set);
> +		writew(irq_mask,
> +			(u16 *)(dma->ndev->bmmio0 + REG_IMASK_SET));
> +	} else {
> +		writel(BIT_DMA_RUN, &dma->regs->ctrlstat_clear);
> +		writew(irq_mask,
> +			(u16 *)(dma->ndev->bmmio0 + REG_IMASK_CLEAR));
> +	}
> +}
> +
> +static irqreturn_t netup_dma_interrupt(struct netup_dma *dma)
> +{
> +	u64 addr_curr;
> +	u32 size;
> +	unsigned long flags;
> +	struct device *dev = &dma->ndev->pci_dev->dev;
> +
> +	spin_lock_irqsave(&dma->lock, flags);
> +	addr_curr = ((u64)readl(&dma->regs->curr_addr_hi) << 32) |
> +		(u64)readl(&dma->regs->curr_addr_lo) | dma->high_addr;
> +	/* clear IRQ */
> +	writel(BIT_DMA_IRQ, &dma->regs->ctrlstat_clear);
> +	/* sanity check */
> +	if (addr_curr < dma->addr_phys ||
> +			addr_curr > dma->addr_phys +  dma->ring_buffer_size) {
> +		if (addr_curr != 0) {
> +			dev_err(dev,
> +				"%s(): addr 0x%llx not from 0x%llx:0x%llx\n",
> +				__func__, addr_curr, (u64)dma->addr_phys,
> +				(u64)(dma->addr_phys + dma->ring_buffer_size));
> +		}
> +		goto irq_handled;
> +	}
> +	size = (addr_curr >= dma->addr_last) ?
> +		(u32)(addr_curr - dma->addr_last) :
> +		(u32)(dma->ring_buffer_size - (dma->addr_last - addr_curr));
> +	if (dma->data_size != 0) {
> +		printk_ratelimited("%s(): lost interrupt, data size %d\n",
> +			__func__, dma->data_size);
> +		dma->data_size += size;
> +	}
> +	if (dma->data_size == 0 || dma->data_size > dma->ring_buffer_size) {
> +		dma->data_size = size;
> +		dma->data_offset = (u32)(dma->addr_last - dma->addr_phys);
> +	}
> +	dma->addr_last = addr_curr;
> +	queue_work(dma->ndev->wq, &dma->work);
> +irq_handled:
> +	spin_unlock_irqrestore(&dma->lock, flags);
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t netup_unidvb_isr(int irq, void *dev_id)
> +{
> +	struct pci_dev *pci_dev = (struct pci_dev *)dev_id;
> +	struct netup_unidvb_dev *ndev = pci_get_drvdata(pci_dev);
> +	u32 reg40, reg_isr;
> +	irqreturn_t iret = IRQ_NONE;
> +
> +	/* disable interrupts */
> +	writel(0, ndev->bmmio0 + AVL_PCIE_IENR);
> +	/* check IRQ source */
> +	reg40 = readl(ndev->bmmio0 + AVL_PCIE_ISR);
> +	if ((reg40 & AVL_IRQ_ASSERTED) != 0) {
> +		/* IRQ is being signaled */
> +		reg_isr = readw(ndev->bmmio0 + REG_ISR);
> +		if (reg_isr & NETUP_UNIDVB_IRQ_I2C0) {
> +			iret = netup_i2c_interrupt(&ndev->i2c[0]);
> +		} else if (reg_isr & NETUP_UNIDVB_IRQ_I2C1) {
> +			iret = netup_i2c_interrupt(&ndev->i2c[1]);
> +		} else if (reg_isr & NETUP_UNIDVB_IRQ_SPI) {
> +			iret = netup_spi_interrupt(ndev->spi);
> +		} else if (reg_isr & NETUP_UNIDVB_IRQ_DMA1) {
> +			iret = netup_dma_interrupt(&ndev->dma[0]);
> +		} else if (reg_isr & NETUP_UNIDVB_IRQ_DMA2) {
> +			iret = netup_dma_interrupt(&ndev->dma[1]);
> +		} else if (reg_isr & NETUP_UNIDVB_IRQ_CI) {
> +			iret = netup_ci_interrupt(ndev);
> +		} else {
> +			dev_err(&pci_dev->dev,
> +				"%s(): unknown interrupt 0x%x\n",
> +				__func__, reg_isr);
> +		}
> +	}
> +	/* re-enable interrupts */
> +	writel(AVL_IRQ_ENABLE, ndev->bmmio0 + AVL_PCIE_IENR);
> +	return iret;
> +}
> +
> +static int netup_unidvb_queue_setup(struct vb2_queue *vq,
> +				    const struct v4l2_format *fmt,
> +				    unsigned int *nbuffers,
> +				    unsigned int *nplanes,
> +				    unsigned int sizes[],
> +				    void *alloc_ctxs[])
> +{
> +	struct netup_dma *dma = vb2_get_drv_priv(vq);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__);
> +
> +	*nplanes = 1;
> +	if (vq->num_buffers + *nbuffers < VIDEO_MAX_FRAME)
> +		*nbuffers = VIDEO_MAX_FRAME - vq->num_buffers;
> +	sizes[0] = PAGE_ALIGN(NETUP_DMA_PACKETS_COUNT * 188);
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s() nbuffers=%d sizes[0]=%d\n",
> +		__func__, *nbuffers, sizes[0]);
> +	return 0;
> +}
> +
> +static int netup_unidvb_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct netup_dma *dma = vb2_get_drv_priv(vb->vb2_queue);
> +	struct netup_unidvb_buffer *buf = container_of(vb,
> +				struct netup_unidvb_buffer, vb);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s(): buf 0x%p\n", __func__, buf);
> +	buf->size = 0;
> +	return 0;
> +}
> +
> +static void netup_unidvb_buf_queue(struct vb2_buffer *vb)
> +{
> +	unsigned long flags;
> +	struct netup_dma *dma = vb2_get_drv_priv(vb->vb2_queue);
> +	struct netup_unidvb_buffer *buf = container_of(vb,
> +				struct netup_unidvb_buffer, vb);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s(): %p\n", __func__, buf);
> +	spin_lock_irqsave(&dma->lock, flags);
> +	list_add_tail(&buf->list, &dma->free_buffers);
> +	spin_unlock_irqrestore(&dma->lock, flags);
> +	mod_timer(&dma->timeout, jiffies + msecs_to_jiffies(1000));
> +}
> +
> +static int netup_unidvb_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +	struct netup_dma *dma = vb2_get_drv_priv(q);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__);
> +	netup_unidvb_dma_enable(dma, 1);
> +	return 0;
> +}
> +
> +static void netup_unidvb_stop_streaming(struct vb2_queue *q)
> +{
> +	struct netup_dma *dma = vb2_get_drv_priv(q);
> +
> +	dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__);
> +	netup_unidvb_dma_enable(dma, 0);
> +	netup_unidvb_queue_cleanup(dma);
> +}
> +
> +static struct vb2_ops dvb_qops = {
> +	.queue_setup		= netup_unidvb_queue_setup,
> +	.buf_prepare		= netup_unidvb_buf_prepare,
> +	.buf_queue		= netup_unidvb_buf_queue,
> +	.start_streaming	= netup_unidvb_start_streaming,
> +	.stop_streaming		= netup_unidvb_stop_streaming,
> +};
> +
> +static int netup_unidvb_queue_init(struct netup_dma *dma,
> +				   struct vb2_queue *vb_queue)
> +{
> +	int res;
> +
> +	/* Init videobuf2 queue structure */
> +	vb_queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	vb_queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
> +	vb_queue->drv_priv = dma;
> +	vb_queue->buf_struct_size = sizeof(struct netup_unidvb_buffer);
> +	vb_queue->ops = &dvb_qops;
> +	vb_queue->mem_ops = &vb2_vmalloc_memops;
> +	vb_queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	res = vb2_queue_init(vb_queue);
> +	if (res != 0) {
> +		dev_err(&dma->ndev->pci_dev->dev,
> +			"%s(): vb2_queue_init failed (%d)\n", __func__, res);
> +	}
> +	return res;
> +}
> +
> +static int netup_unidvb_dvb_init(struct netup_unidvb_dev *ndev,
> +				 int num)
> +{
> +	struct vb2_dvb_frontend *fe0, *fe1, *fe2;
> +
> +	if (num < 0 || num > 1) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to init DVB bus %d\n", __func__, num);
> +		return -ENODEV;
> +	}
> +	mutex_init(&ndev->frontends[num].lock);
> +	INIT_LIST_HEAD(&ndev->frontends[num].felist);
> +	if (vb2_dvb_alloc_frontend(&ndev->frontends[num], 1) == NULL ||
> +		vb2_dvb_alloc_frontend(
> +			&ndev->frontends[num], 2) == NULL ||
> +		vb2_dvb_alloc_frontend(
> +			&ndev->frontends[num], 3) == NULL) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to to alllocate vb2_dvb_frontend\n",
> +			__func__);
> +		return -ENOMEM;
> +	}
> +	fe0 = vb2_dvb_get_frontend(&ndev->frontends[num], 1);
> +	fe1 = vb2_dvb_get_frontend(&ndev->frontends[num], 2);
> +	fe2 = vb2_dvb_get_frontend(&ndev->frontends[num], 3);
> +	if (fe0 == NULL || fe1 == NULL || fe2 == NULL) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): frontends has not been allocated\n", __func__);
> +		return -EINVAL;
> +	}
> +	netup_unidvb_queue_init(&ndev->dma[num], &fe0->dvb.dvbq);
> +	netup_unidvb_queue_init(&ndev->dma[num], &fe1->dvb.dvbq);
> +	netup_unidvb_queue_init(&ndev->dma[num], &fe2->dvb.dvbq);
> +	fe0->dvb.name = "netup_fe0";
> +	fe1->dvb.name = "netup_fe1";
> +	fe2->dvb.name = "netup_fe2";
> +	fe0->dvb.frontend = dvb_attach(cxd2841er_attach_s,
> +		&demod_config, &ndev->i2c[num].adap);
> +	if (fe0->dvb.frontend == NULL) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-S/S2 frontend\n",
> +			__func__);
> +		goto frontend_detach;
> +	}
> +	horus3a_conf.set_tuner_priv = &ndev->dma[num];
> +	if (!dvb_attach(horus3a_attach, fe0->dvb.frontend,
> +			&horus3a_conf, &ndev->i2c[num].adap)) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-S/S2 tuner frontend\n",
> +			__func__);
> +		goto frontend_detach;
> +	}
> +	if (!dvb_attach(lnbh25_attach, fe0->dvb.frontend,
> +			&lnbh25_conf, &ndev->i2c[num].adap)) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach SEC frontend\n", __func__);
> +		goto frontend_detach;
> +	}
> +	/* DVB-T/T2 frontend */
> +	fe1->dvb.frontend = dvb_attach(cxd2841er_attach_t,
> +		&demod_config, &ndev->i2c[num].adap);
> +	if (fe1->dvb.frontend == NULL) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-T frontend\n", __func__);
> +		goto frontend_detach;
> +	}
> +	fe1->dvb.frontend->id = 1;
> +	ascot2e_conf.set_tuner_priv = &ndev->dma[num];
> +	if (!dvb_attach(ascot2e_attach, fe1->dvb.frontend,
> +			&ascot2e_conf, &ndev->i2c[num].adap)) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-T tuner frontend\n",
> +			__func__);
> +		goto frontend_detach;
> +	}
> +	/* DVB-C/C2 frontend */
> +	fe2->dvb.frontend = dvb_attach(cxd2841er_attach_c,
> +				&demod_config, &ndev->i2c[num].adap);
> +	if (fe2->dvb.frontend == NULL) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-C frontend\n", __func__);
> +		goto frontend_detach;
> +	}
> +	fe2->dvb.frontend->id = 2;
> +	if (!dvb_attach(ascot2e_attach, fe2->dvb.frontend,
> +			&ascot2e_conf, &ndev->i2c[num].adap)) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to attach DVB-T/C tuner frontend\n",
> +			__func__);
> +		goto frontend_detach;
> +	}
> +
> +	if (vb2_dvb_register_bus(&ndev->frontends[num],
> +			THIS_MODULE, NULL,
> +			&ndev->pci_dev->dev, adapter_nr, 1)) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): unable to register DVB bus %d\n",
> +			__func__, num);
> +		goto frontend_detach;
> +	}
> +	dev_info(&ndev->pci_dev->dev, "DVB init done, num=%d\n", num);
> +	return 0;
> +frontend_detach:
> +	vb2_dvb_dealloc_frontends(&ndev->frontends[num]);
> +	return -EINVAL;
> +}
> +
> +static void netup_unidvb_dvb_fini(struct netup_unidvb_dev *ndev, int num)
> +{
> +	if (num < 0 || num > 1) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): unable to unregister DVB bus %d\n",
> +			__func__, num);
> +		return;
> +	}
> +	vb2_dvb_unregister_bus(&ndev->frontends[num]);
> +	dev_info(&ndev->pci_dev->dev,
> +		"%s(): DVB bus %d unregistered\n", __func__, num);
> +}
> +
> +static int netup_unidvb_dvb_setup(struct netup_unidvb_dev *ndev)
> +{
> +	int res;
> +
> +	res = netup_unidvb_dvb_init(ndev, 0);
> +	if (res)
> +		return res;
> +	res = netup_unidvb_dvb_init(ndev, 1);
> +	if (res) {
> +		netup_unidvb_dvb_fini(ndev, 0);
> +		return res;
> +	}
> +	return 0;
> +}
> +
> +static int netup_unidvb_ring_copy(struct netup_dma *dma,
> +				  struct netup_unidvb_buffer *buf)
> +{
> +	u32 copy_bytes, ring_bytes;
> +	u32 buff_bytes = NETUP_DMA_PACKETS_COUNT * 188 - buf->size;
> +	u8 *p = vb2_plane_vaddr(&buf->vb, 0);
> +	struct netup_unidvb_dev *ndev = dma->ndev;
> +
> +	if (p == NULL) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): buffer is NULL\n", __func__);
> +		return -EINVAL;
> +	}
> +	p += buf->size;
> +	if (dma->data_offset + dma->data_size > dma->ring_buffer_size) {
> +		ring_bytes = dma->ring_buffer_size - dma->data_offset;
> +		copy_bytes = (ring_bytes > buff_bytes) ?
> +			buff_bytes : ring_bytes;
> +		memcpy_fromio(p, dma->addr_virt + dma->data_offset, copy_bytes);
> +		p += copy_bytes;
> +		buf->size += copy_bytes;
> +		buff_bytes -= copy_bytes;
> +		dma->data_size -= copy_bytes;
> +		dma->data_offset += copy_bytes;
> +		if (dma->data_offset == dma->ring_buffer_size)
> +			dma->data_offset = 0;
> +	}
> +	if (buff_bytes > 0) {
> +		ring_bytes = dma->data_size;
> +		copy_bytes = (ring_bytes > buff_bytes) ?
> +				buff_bytes : ring_bytes;
> +		memcpy_fromio(p, dma->addr_virt + dma->data_offset, copy_bytes);
> +		buf->size += copy_bytes;
> +		dma->data_size -= copy_bytes;
> +		dma->data_offset += copy_bytes;
> +		if (dma->data_offset == dma->ring_buffer_size)
> +			dma->data_offset = 0;
> +	}
> +	return 0;
> +}
> +
> +static void netup_unidvb_dma_worker(struct work_struct *work)
> +{
> +	struct netup_dma *dma = container_of(work, struct netup_dma, work);
> +	struct netup_unidvb_dev *ndev = dma->ndev;
> +	struct netup_unidvb_buffer *buf;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dma->lock, flags);
> +	if (dma->data_size == 0) {
> +		dev_dbg(&ndev->pci_dev->dev,
> +			"%s(): data_size == 0\n", __func__);
> +		goto work_done;
> +	}
> +	while (dma->data_size > 0) {
> +		if (list_empty(&dma->free_buffers)) {
> +			dev_dbg(&ndev->pci_dev->dev,
> +				"%s(): no free buffers\n", __func__);
> +			goto work_done;
> +		}
> +		buf = list_first_entry(&dma->free_buffers,
> +			struct netup_unidvb_buffer, list);
> +		if (buf->size >= NETUP_DMA_PACKETS_COUNT * 188) {
> +			dev_dbg(&ndev->pci_dev->dev,
> +				"%s(): buffer overflow, size %d\n",
> +				__func__, buf->size);
> +			goto work_done;
> +		}
> +		if (netup_unidvb_ring_copy(dma, buf))
> +			goto work_done;
> +		if (buf->size == NETUP_DMA_PACKETS_COUNT * 188) {
> +			list_del(&buf->list);
> +			dev_dbg(&ndev->pci_dev->dev,
> +				"%s(): buffer %p done, size %d\n",
> +				__func__, buf, buf->size);
> +			v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
> +			vb2_set_plane_payload(&buf->vb, 0, buf->size);
> +			vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
> +		}
> +	}
> +work_done:
> +	dma->data_size = 0;
> +	spin_unlock_irqrestore(&dma->lock, flags);
> +}
> +
> +static void netup_unidvb_queue_cleanup(struct netup_dma *dma)
> +{
> +	struct netup_unidvb_buffer *buf;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dma->lock, flags);
> +	while (!list_empty(&dma->free_buffers)) {
> +		buf = list_first_entry(&dma->free_buffers,
> +			struct netup_unidvb_buffer, list);
> +		list_del(&buf->list);
> +		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
> +	}
> +	spin_unlock_irqrestore(&dma->lock, flags);
> +}
> +
> +static void netup_unidvb_dma_timeout(unsigned long data)
> +{
> +	struct netup_dma *dma = (struct netup_dma *)data;
> +	struct netup_unidvb_dev *ndev = dma->ndev;
> +
> +	dev_dbg(&ndev->pci_dev->dev, "%s()\n", __func__);
> +	netup_unidvb_queue_cleanup(dma);
> +}
> +
> +static int netup_unidvb_dma_init(struct netup_unidvb_dev *ndev, int num)
> +{
> +	struct netup_dma *dma;
> +	struct device *dev = &ndev->pci_dev->dev;
> +
> +	if (num < 0 || num > 1) {
> +		dev_err(dev, "%s(): unable to register DMA%d\n",
> +			__func__, num);
> +		return -ENODEV;
> +	}
> +	dma = &ndev->dma[num];
> +	dev_info(dev, "%s(): starting DMA%d\n", __func__, num);
> +	dma->num = num;
> +	dma->ndev = ndev;
> +	spin_lock_init(&dma->lock);
> +	INIT_WORK(&dma->work, netup_unidvb_dma_worker);
> +	INIT_LIST_HEAD(&dma->free_buffers);
> +	dma->timeout.function = netup_unidvb_dma_timeout;
> +	dma->timeout.data = (unsigned long)dma;
> +	init_timer(&dma->timeout);
> +	dma->ring_buffer_size = ndev->dma_size / 2;
> +	dma->addr_virt = ndev->dma_virt + dma->ring_buffer_size * num;
> +	dma->addr_phys = (dma_addr_t)((u64)ndev->dma_phys +
> +		dma->ring_buffer_size * num);
> +	dev_info(dev, "%s(): DMA%d buffer virt/phys 0x%p/0x%llx size %d\n",
> +		__func__, num, dma->addr_virt,
> +		(unsigned long long)dma->addr_phys,
> +		dma->ring_buffer_size);
> +	memset_io(dma->addr_virt, 0, dma->ring_buffer_size);
> +	dma->addr_last = dma->addr_phys;
> +	dma->high_addr = (u32)(dma->addr_phys & 0xC0000000);
> +	dma->regs = (struct netup_dma_regs *)(num == 0 ?
> +		ndev->bmmio0 + NETUP_DMA0_ADDR :
> +		ndev->bmmio0 + NETUP_DMA1_ADDR);
> +	writel((NETUP_DMA_BLOCKS_COUNT << 24) |
> +		(NETUP_DMA_PACKETS_COUNT << 8) | 188, &dma->regs->size);
> +	writel((u32)(dma->addr_phys & 0x3FFFFFFF), &dma->regs->start_addr_lo);
> +	writel(0, &dma->regs->start_addr_hi);
> +	writel(dma->high_addr, ndev->bmmio0 + 0x1000);
> +	writel(375000000, &dma->regs->timeout);
> +	msleep(1000);
> +	writel(BIT_DMA_IRQ, &dma->regs->ctrlstat_clear);
> +	return 0;
> +}
> +
> +static void netup_unidvb_dma_fini(struct netup_unidvb_dev *ndev, int num)
> +{
> +	struct netup_dma *dma;
> +
> +	if (num < 0 || num > 1)
> +		return;
> +	dev_dbg(&ndev->pci_dev->dev, "%s(): num %d\n", __func__, num);
> +	dma = &ndev->dma[num];
> +	netup_unidvb_dma_enable(dma, 0);
> +	msleep(50);
> +	cancel_work_sync(&dma->work);
> +	del_timer(&dma->timeout);
> +}
> +
> +static int netup_unidvb_dma_setup(struct netup_unidvb_dev *ndev)
> +{
> +	int res;
> +
> +	res = netup_unidvb_dma_init(ndev, 0);
> +	if (res)
> +		return res;
> +	res = netup_unidvb_dma_init(ndev, 1);
> +	if (res) {
> +		netup_unidvb_dma_fini(ndev, 0);
> +		return res;
> +	}
> +	netup_unidvb_dma_enable(&ndev->dma[0], 0);
> +	netup_unidvb_dma_enable(&ndev->dma[1], 0);
> +	return 0;
> +}
> +
> +static int netup_unidvb_ci_setup(struct netup_unidvb_dev *ndev,
> +				 struct pci_dev *pci_dev)
> +{
> +	int res;
> +
> +	writew(NETUP_UNIDVB_IRQ_CI, ndev->bmmio0 + REG_IMASK_SET);
> +	res = netup_unidvb_ci_register(ndev, 0, pci_dev);
> +	if (res)
> +		return res;
> +	res = netup_unidvb_ci_register(ndev, 1, pci_dev);
> +	if (res)
> +		netup_unidvb_ci_unregister(ndev, 0);
> +	return res;
> +}
> +
> +static int netup_unidvb_request_mmio(struct pci_dev *pci_dev)
> +{
> +	if (!request_mem_region(pci_resource_start(pci_dev, 0),
> +			pci_resource_len(pci_dev, 0), NETUP_UNIDVB_NAME)) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to request MMIO bar 0 at 0x%llx\n",
> +			__func__,
> +			(unsigned long long)pci_resource_start(pci_dev, 0));
> +		return -EBUSY;
> +	}
> +	if (!request_mem_region(pci_resource_start(pci_dev, 1),
> +			pci_resource_len(pci_dev, 1), NETUP_UNIDVB_NAME)) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to request MMIO bar 1 at 0x%llx\n",
> +			__func__,
> +			(unsigned long long)pci_resource_start(pci_dev, 1));
> +		release_mem_region(pci_resource_start(pci_dev, 0),
> +			pci_resource_len(pci_dev, 0));
> +		return -EBUSY;
> +	}
> +	return 0;
> +}
> +
> +static int netup_unidvb_request_modules(struct device *dev)
> +{
> +	static const char * const modules[] = {
> +		"lnbh25", "ascot2e", "horus3a", "cxd2841er", NULL
> +	};
> +	const char * const *curr_mod = modules;
> +	int err;
> +
> +	while (*curr_mod != NULL) {
> +		err = request_module(*curr_mod);
> +		if (err) {
> +			dev_warn(dev, "request_module(%s) failed: %d\n",
> +				*curr_mod, err);
> +		}
> +		++curr_mod;
> +	}
> +	return 0;
> +}
> +
> +static int netup_unidvb_initdev(struct pci_dev *pci_dev,
> +				const struct pci_device_id *pci_id)
> +{
> +	u8 board_revision;
> +	u16 board_vendor;
> +	struct netup_unidvb_dev *ndev;
> +	int old_firmware = 0;
> +
> +	netup_unidvb_request_modules(&pci_dev->dev);
> +
> +	/* Check card revision */
> +	if (pci_dev->revision != NETUP_PCI_DEV_REVISION) {
> +		dev_err(&pci_dev->dev,
> +			"netup_unidvb: expected card revision %d, got %d\n",
> +			NETUP_PCI_DEV_REVISION, pci_dev->revision);
> +		dev_err(&pci_dev->dev,
> +			"Please upgrade firmware!\n");
> +		dev_err(&pci_dev->dev,
> +			"Instructions on http://www.netup.tv\n";);
> +		old_firmware = 1;
> +		spi_enable = 1;
> +	}
> +
> +	/* allocate device context */
> +	ndev = kzalloc(sizeof(*ndev), GFP_KERNEL);
> +
> +	if (!ndev)
> +		goto dev_alloc_err;
> +	memset(ndev, 0, sizeof(*ndev));
> +	ndev->old_fw = old_firmware;
> +	ndev->wq = create_singlethread_workqueue(NETUP_UNIDVB_NAME);
> +	if (!ndev->wq) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to create workqueue\n", __func__);
> +		goto wq_create_err;
> +	}
> +	ndev->pci_dev = pci_dev;
> +	ndev->pci_bus = pci_dev->bus->number;
> +	ndev->pci_slot = PCI_SLOT(pci_dev->devfn);
> +	ndev->pci_func = PCI_FUNC(pci_dev->devfn);
> +	ndev->board_num = ndev->pci_bus*10 + ndev->pci_slot;
> +	pci_set_drvdata(pci_dev, ndev);
> +	/* PCI init */
> +	dev_info(&pci_dev->dev, "%s(): PCI device (%d). Bus:0x%x Slot:0x%x\n",
> +		__func__, ndev->board_num, ndev->pci_bus, ndev->pci_slot);
> +
> +	if (pci_enable_device(pci_dev)) {
> +		dev_err(&pci_dev->dev, "%s(): pci_enable_device failed\n",
> +			__func__);
> +		goto pci_enable_err;
> +	}
> +	/* read PCI info */
> +	pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &board_revision);
> +	pci_read_config_word(pci_dev, PCI_VENDOR_ID, &board_vendor);
> +	if (board_vendor != NETUP_VENDOR_ID) {
> +		dev_err(&pci_dev->dev, "%s(): unknown board vendor 0x%x",
> +			__func__, board_vendor);
> +		goto pci_detect_err;
> +	}
> +	dev_info(&pci_dev->dev,
> +		"%s(): board vendor 0x%x, revision 0x%x\n",
> +		__func__, board_vendor, board_revision);
> +	pci_set_master(pci_dev);
> +	if (!pci_dma_supported(pci_dev, 0xffffffff)) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): 32bit PCI DMA is not supported\n", __func__);
> +		goto pci_detect_err;
> +	}
> +	dev_info(&pci_dev->dev, "%s(): using 32bit PCI DMA\n", __func__);
> +	/* Clear "no snoop" and "relaxed ordering" bits, use default MRRS. */
> +	pcie_capability_clear_and_set_word(pci_dev, PCI_EXP_DEVCTL,
> +		PCI_EXP_DEVCTL_READRQ | PCI_EXP_DEVCTL_RELAX_EN |
> +		PCI_EXP_DEVCTL_NOSNOOP_EN, 0);
> +	/* Adjust PCIe completion timeout. */
> +	pcie_capability_clear_and_set_word(pci_dev,
> +		PCI_EXP_DEVCTL2, 0xf, 0x2);
> +
> +	if (netup_unidvb_request_mmio(pci_dev)) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to request MMIO regions\n", __func__);
> +		goto pci_detect_err;
> +	}
> +	ndev->lmmio0 = ioremap(pci_resource_start(pci_dev, 0),
> +		pci_resource_len(pci_dev, 0));
> +	if (!ndev->lmmio0) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to remap MMIO bar 0\n", __func__);
> +		goto pci_bar0_error;
> +	}
> +	ndev->lmmio1 = ioremap(pci_resource_start(pci_dev, 1),
> +		pci_resource_len(pci_dev, 1));
> +	if (!ndev->lmmio1) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): unable to remap MMIO bar 1\n", __func__);
> +		goto pci_bar1_error;
> +	}
> +	ndev->bmmio0 = (u8 __iomem *)ndev->lmmio0;
> +	ndev->bmmio1 = (u8 __iomem *)ndev->lmmio1;
> +	dev_info(&pci_dev->dev,
> +		"%s(): PCI MMIO at 0x%p (%d); 0x%p (%d); IRQ %d",
> +		__func__,
> +		ndev->lmmio0, (u32)pci_resource_len(pci_dev, 0),
> +		ndev->lmmio1, (u32)pci_resource_len(pci_dev, 1),
> +		pci_dev->irq);
> +	if (request_irq(pci_dev->irq, netup_unidvb_isr,
> +			IRQF_SHARED | IRQF_DISABLED,
> +			"netup_unidvb", pci_dev) < 0) {
> +		dev_err(&pci_dev->dev,
> +			"%s(): can't get IRQ %d\n", __func__, pci_dev->irq);
> +		goto irq_request_err;
> +	}
> +	ndev->dma_size = 2 * 188 *
> +		NETUP_DMA_BLOCKS_COUNT * NETUP_DMA_PACKETS_COUNT;
> +	ndev->dma_virt = dma_alloc_coherent(&pci_dev->dev,
> +		ndev->dma_size, &ndev->dma_phys, GFP_KERNEL);
> +	if (!ndev->dma_virt) {
> +		dev_err(&pci_dev->dev, "%s(): unable to allocate DMA buffer\n",
> +			__func__);
> +		goto dma_alloc_err;
> +	}
> +	netup_unidvb_dev_enable(ndev);
> +	if (spi_enable && netup_spi_init(ndev)) {
> +		dev_warn(&pci_dev->dev,
> +			"netup_unidvb: SPI flash setup failed\n");
> +		goto spi_setup_err;
> +	}
> +	if (old_firmware) {
> +		dev_err(&pci_dev->dev,
> +			"netup_unidvb: card initialization was incomplete\n");
> +		return 0;
> +	}
> +	if (netup_i2c_register(ndev)) {
> +		dev_err(&pci_dev->dev, "netup_unidvb: I2C setup failed\n");
> +		goto i2c_setup_err;
> +	}
> +	/* enable I2C IRQs */
> +	writew(NETUP_UNIDVB_IRQ_I2C0 | NETUP_UNIDVB_IRQ_I2C1,
> +		ndev->bmmio0 + REG_IMASK_SET);
> +	usleep_range(5000, 10000);
> +	if (netup_unidvb_dvb_setup(ndev)) {
> +		dev_err(&pci_dev->dev, "netup_unidvb: DVB setup failed\n");
> +		goto dvb_setup_err;
> +	}
> +	if (netup_unidvb_ci_setup(ndev, pci_dev)) {
> +		dev_err(&pci_dev->dev, "netup_unidvb: CI setup failed\n");
> +		goto ci_setup_err;
> +	}
> +	if (netup_unidvb_dma_setup(ndev)) {
> +		dev_err(&pci_dev->dev, "netup_unidvb: DMA setup failed\n");
> +		goto dma_setup_err;
> +	}
> +	dev_info(&pci_dev->dev,
> +		"netup_unidvb: device has been initialized\n");
> +	return 0;
> +dma_setup_err:
> +	netup_unidvb_ci_unregister(ndev, 0);
> +	netup_unidvb_ci_unregister(ndev, 1);
> +ci_setup_err:
> +	netup_unidvb_dvb_fini(ndev, 0);
> +	netup_unidvb_dvb_fini(ndev, 1);
> +dvb_setup_err:
> +	netup_i2c_unregister(ndev);
> +i2c_setup_err:
> +	if (ndev->spi)
> +		netup_spi_release(ndev);
> +spi_setup_err:
> +	dma_free_coherent(&pci_dev->dev, ndev->dma_size,
> +			ndev->dma_virt, ndev->dma_phys);
> +dma_alloc_err:
> +	free_irq(pci_dev->irq, pci_dev);
> +irq_request_err:
> +	iounmap(ndev->lmmio1);
> +pci_bar1_error:
> +	iounmap(ndev->lmmio0);
> +pci_bar0_error:
> +	release_mem_region(pci_resource_start(pci_dev, 0),
> +		pci_resource_len(pci_dev, 0));
> +	release_mem_region(pci_resource_start(pci_dev, 1),
> +		pci_resource_len(pci_dev, 1));
> +pci_detect_err:
> +	pci_disable_device(pci_dev);
> +pci_enable_err:
> +	pci_set_drvdata(pci_dev, NULL);
> +	destroy_workqueue(ndev->wq);
> +wq_create_err:
> +	kfree(ndev);
> +dev_alloc_err:
> +	dev_err(&pci_dev->dev,
> +		"%s(): failed to initizalize device\n", __func__);
> +	return -EIO;
> +}
> +
> +static void netup_unidvb_finidev(struct pci_dev *pci_dev)
> +{
> +	struct netup_unidvb_dev *ndev = pci_get_drvdata(pci_dev);
> +
> +	dev_info(&pci_dev->dev, "%s(): trying to stop device\n", __func__);
> +	if (!ndev->old_fw) {
> +		netup_unidvb_dma_fini(ndev, 0);
> +		netup_unidvb_dma_fini(ndev, 1);
> +		netup_unidvb_ci_unregister(ndev, 0);
> +		netup_unidvb_ci_unregister(ndev, 1);
> +		netup_unidvb_dvb_fini(ndev, 0);
> +		netup_unidvb_dvb_fini(ndev, 1);
> +		netup_i2c_unregister(ndev);
> +	}
> +	if (ndev->spi)
> +		netup_spi_release(ndev);
> +	writew(0xffff, ndev->bmmio0 + REG_IMASK_CLEAR);
> +	dma_free_coherent(&ndev->pci_dev->dev, ndev->dma_size,
> +			ndev->dma_virt, ndev->dma_phys);
> +	free_irq(pci_dev->irq, pci_dev);
> +	iounmap(ndev->lmmio0);
> +	iounmap(ndev->lmmio1);
> +	release_mem_region(pci_resource_start(pci_dev, 0),
> +		pci_resource_len(pci_dev, 0));
> +	release_mem_region(pci_resource_start(pci_dev, 1),
> +		pci_resource_len(pci_dev, 1));
> +	pci_disable_device(pci_dev);
> +	pci_set_drvdata(pci_dev, NULL);
> +	destroy_workqueue(ndev->wq);
> +	kfree(ndev);
> +	dev_info(&pci_dev->dev,
> +		"%s(): device has been successfully stopped\n", __func__);
> +}
> +
> +
> +static struct pci_device_id netup_unidvb_pci_tbl[] = {
> +	{ PCI_DEVICE(0x1b55, 0x18f6) },
> +	{ 0, }
> +};
> +MODULE_DEVICE_TABLE(pci, netup_unidvb_pci_tbl);
> +
> +static struct pci_driver netup_unidvb_pci_driver = {
> +	.name     = "netup_unidvb",
> +	.id_table = netup_unidvb_pci_tbl,
> +	.probe    = netup_unidvb_initdev,
> +	.remove   = netup_unidvb_finidev,
> +	.suspend  = NULL,
> +	.resume   = NULL,
> +};
> +
> +static int __init netup_unidvb_init(void)
> +{
> +	return pci_register_driver(&netup_unidvb_pci_driver);
> +}
> +
> +static void __exit netup_unidvb_fini(void)
> +{
> +	pci_unregister_driver(&netup_unidvb_pci_driver);
> +}
> +
> +module_init(netup_unidvb_init);
> +module_exit(netup_unidvb_fini);
> diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c b/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c
> new file mode 100644
> index 0000000..eaaa2d0
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c
> @@ -0,0 +1,381 @@
> +/*
> + * netup_unidvb_i2c.c
> + *
> + * Internal I2C bus driver for NetUP Universal Dual DVB-CI
> + *
> + * Copyright (C) 2014 NetUP Inc.
> + * Copyright (C) 2014 Sergey Kozlov <serjk@xxxxxxxx>
> + * Copyright (C) 2014 Abylay Ospan <aospan@xxxxxxxx>
> + *
> + * 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/init.h>
> +#include <linux/delay.h>
> +#include "netup_unidvb.h"
> +
> +#define NETUP_I2C_BUS0_ADDR		0x4800
> +#define NETUP_I2C_BUS1_ADDR		0x4840
> +#define NETUP_I2C_TIMEOUT		1000
> +
> +/* twi_ctrl0_stat reg bits */
> +#define TWI_IRQEN_COMPL	0x1
> +#define TWI_IRQEN_ANACK 0x2
> +#define TWI_IRQEN_DNACK 0x4
> +#define TWI_IRQ_COMPL	(TWI_IRQEN_COMPL << 8)
> +#define TWI_IRQ_ANACK	(TWI_IRQEN_ANACK << 8)
> +#define TWI_IRQ_DNACK	(TWI_IRQEN_DNACK << 8)
> +#define TWI_IRQ_TX	0x800
> +#define TWI_IRQ_RX	0x1000
> +#define TWI_IRQEN	(TWI_IRQEN_COMPL | TWI_IRQEN_ANACK | TWI_IRQEN_DNACK)
> +/* twi_addr_ctrl1 reg bits*/
> +#define TWI_TRANSFER	0x100
> +#define TWI_NOSTOP	0x200
> +#define TWI_SOFT_RESET	0x2000
> +/* twi_clkdiv reg value */
> +#define TWI_CLKDIV	156
> +/* fifo_stat_ctrl reg bits */
> +#define FIFO_IRQEN	0x8000
> +#define FIFO_RESET	0x4000
> +/* FIFO size */
> +#define FIFO_SIZE	16
> +
> +struct netup_i2c_fifo_regs {
> +	union {
> +		__u8	data8;
> +		__le16	data16;
> +		__le32	data32;
> +	};
> +	__u8		padding[4];
> +	__le16		stat_ctrl;
> +} __packed __aligned(1);
> +
> +struct netup_i2c_regs {
> +	__le16				clkdiv;
> +	__le16				twi_ctrl0_stat;
> +	__le16				twi_addr_ctrl1;
> +	__le16				length;
> +	__u8				padding1[8];
> +	struct netup_i2c_fifo_regs	tx_fifo;
> +	__u8				padding2[6];
> +	struct netup_i2c_fifo_regs	rx_fifo;
> +} __packed __aligned(1);
> +
> +irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c)
> +{
> +	u16 reg, tmp;
> +	unsigned long flags;
> +	irqreturn_t iret = IRQ_HANDLED;
> +
> +	spin_lock_irqsave(&i2c->lock, flags);
> +	reg = readw(&i2c->regs->twi_ctrl0_stat);
> +	writew(reg & ~TWI_IRQEN, &i2c->regs->twi_ctrl0_stat);
> +	dev_dbg(i2c->adap.dev.parent,
> +		"%s(): twi_ctrl0_state 0x%x\n", __func__, reg);
> +	if ((reg & TWI_IRQEN_COMPL) != 0 && (reg & TWI_IRQ_COMPL)) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): TWI_IRQEN_COMPL\n", __func__);
> +		i2c->state = STATE_DONE;
> +		goto irq_ok;
> +	}
> +	if ((reg & TWI_IRQEN_ANACK) != 0 && (reg & TWI_IRQ_ANACK)) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): TWI_IRQEN_ANACK\n", __func__);
> +		i2c->state = STATE_ERROR;
> +		goto irq_ok;
> +	}
> +	if ((reg & TWI_IRQEN_DNACK) != 0 && (reg & TWI_IRQ_DNACK)) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): TWI_IRQEN_DNACK\n", __func__);
> +		i2c->state = STATE_ERROR;
> +		goto irq_ok;
> +	}
> +	if ((reg & TWI_IRQ_RX) != 0) {
> +		tmp = readw(&i2c->regs->rx_fifo.stat_ctrl);
> +		writew(tmp & ~FIFO_IRQEN, &i2c->regs->rx_fifo.stat_ctrl);
> +		i2c->state = STATE_WANT_READ;
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): want read\n", __func__);
> +		goto irq_ok;
> +	}
> +	if ((reg & TWI_IRQ_TX) != 0) {
> +		tmp = readw(&i2c->regs->tx_fifo.stat_ctrl);
> +		writew(tmp & ~FIFO_IRQEN, &i2c->regs->tx_fifo.stat_ctrl);
> +		i2c->state = STATE_WANT_WRITE;
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): want write\n", __func__);
> +		goto irq_ok;
> +	}
> +	dev_warn(&i2c->adap.dev, "%s(): not mine interrupt\n", __func__);
> +	iret = IRQ_NONE;
> +irq_ok:
> +	spin_unlock_irqrestore(&i2c->lock, flags);
> +	if (iret == IRQ_HANDLED)
> +		wake_up(&i2c->wq);
> +	return iret;
> +}
> +
> +static void netup_i2c_reset(struct netup_i2c *i2c)
> +{
> +	dev_dbg(i2c->adap.dev.parent, "%s()\n", __func__);
> +	i2c->state = STATE_DONE;
> +	writew(TWI_SOFT_RESET, &i2c->regs->twi_addr_ctrl1);
> +	writew(TWI_CLKDIV, &i2c->regs->clkdiv);
> +	writew(FIFO_RESET, &i2c->regs->tx_fifo.stat_ctrl);
> +	writew(FIFO_RESET, &i2c->regs->rx_fifo.stat_ctrl);
> +	writew(0x800, &i2c->regs->tx_fifo.stat_ctrl);
> +	writew(0x800, &i2c->regs->rx_fifo.stat_ctrl);
> +}
> +
> +static void netup_i2c_fifo_tx(struct netup_i2c *i2c)
> +{
> +	u8 data;
> +	u32 fifo_space = FIFO_SIZE -
> +		(readw(&i2c->regs->tx_fifo.stat_ctrl) & 0x3f);
> +	u32 msg_length = i2c->msg->len - i2c->xmit_size;
> +
> +	msg_length = (msg_length < fifo_space ? msg_length : fifo_space);
> +	while (msg_length--) {
> +		data = i2c->msg->buf[i2c->xmit_size++];
> +		writeb(data, &i2c->regs->tx_fifo.data8);
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): write 0x%02x\n", __func__, data);
> +	}
> +	if (i2c->xmit_size < i2c->msg->len) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): TX IRQ enabled\n", __func__);
> +		writew(readw(&i2c->regs->tx_fifo.stat_ctrl) | FIFO_IRQEN,
> +			&i2c->regs->tx_fifo.stat_ctrl);
> +	}
> +}
> +
> +static void netup_i2c_fifo_rx(struct netup_i2c *i2c)
> +{
> +	u8 data;
> +	u32 fifo_size = readw(&i2c->regs->rx_fifo.stat_ctrl) & 0x3f;
> +
> +	dev_dbg(i2c->adap.dev.parent,
> +		"%s(): RX fifo size %d\n", __func__, fifo_size);
> +	while (fifo_size--) {
> +		data = readb(&i2c->regs->rx_fifo.data8);
> +		if ((i2c->msg->flags & I2C_M_RD) != 0 &&
> +					i2c->xmit_size < i2c->msg->len) {
> +			i2c->msg->buf[i2c->xmit_size++] = data;
> +			dev_dbg(i2c->adap.dev.parent,
> +				"%s(): read 0x%02x\n", __func__, data);
> +		}
> +	}
> +	if (i2c->xmit_size < i2c->msg->len) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): RX IRQ enabled\n", __func__);
> +		writew(readw(&i2c->regs->rx_fifo.stat_ctrl) | FIFO_IRQEN,
> +			&i2c->regs->rx_fifo.stat_ctrl);
> +	}
> +}
> +
> +static void netup_i2c_start_xfer(struct netup_i2c *i2c)
> +{
> +	u16 rdflag = ((i2c->msg->flags & I2C_M_RD) ? 1 : 0);
> +	u16 reg = readw(&i2c->regs->twi_ctrl0_stat);
> +
> +	writew(TWI_IRQEN | reg, &i2c->regs->twi_ctrl0_stat);
> +	writew(i2c->msg->len, &i2c->regs->length);
> +	writew(TWI_TRANSFER | (i2c->msg->addr << 1) | rdflag,
> +		&i2c->regs->twi_addr_ctrl1);
> +	dev_dbg(i2c->adap.dev.parent,
> +		"%s(): length %d twi_addr_ctrl1 0x%x twi_ctrl0_stat 0x%x\n",
> +		__func__, readw(&i2c->regs->length),
> +		readw(&i2c->regs->twi_addr_ctrl1),
> +		readw(&i2c->regs->twi_ctrl0_stat));
> +	i2c->state = STATE_WAIT;
> +	i2c->xmit_size = 0;
> +	if (!rdflag)
> +		netup_i2c_fifo_tx(i2c);
> +	else
> +		writew(FIFO_IRQEN | readw(&i2c->regs->rx_fifo.stat_ctrl),
> +			&i2c->regs->rx_fifo.stat_ctrl);
> +}
> +
> +static int netup_i2c_xfer(struct i2c_adapter *adap,
> +			  struct i2c_msg *msgs, int num)
> +{
> +	unsigned long flags;
> +	int i, trans_done, res = num;
> +	struct netup_i2c *i2c = i2c_get_adapdata(adap);
> +	u16 reg;
> +
> +	if (num <= 0) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): num == %d\n", __func__, num);
> +		return -EINVAL;
> +	}
> +	spin_lock_irqsave(&i2c->lock, flags);
> +	if (i2c->state != STATE_DONE) {
> +		dev_dbg(i2c->adap.dev.parent,
> +			"%s(): i2c->state == %d, resetting I2C\n",
> +			__func__, i2c->state);
> +		netup_i2c_reset(i2c);
> +	}
> +	dev_dbg(i2c->adap.dev.parent, "%s() num %d\n", __func__, num);
> +	for (i = 0; i < num; i++) {
> +		i2c->msg = &msgs[i];
> +		netup_i2c_start_xfer(i2c);
> +		trans_done = 0;
> +		while (!trans_done) {
> +			spin_unlock_irqrestore(&i2c->lock, flags);
> +			if (wait_event_timeout(i2c->wq,
> +					i2c->state != STATE_WAIT,
> +					msecs_to_jiffies(NETUP_I2C_TIMEOUT))) {
> +				spin_lock_irqsave(&i2c->lock, flags);
> +				switch (i2c->state) {
> +				case STATE_WANT_READ:
> +					netup_i2c_fifo_rx(i2c);
> +					break;
> +				case STATE_WANT_WRITE:
> +					netup_i2c_fifo_tx(i2c);
> +					break;
> +				case STATE_DONE:
> +					if ((i2c->msg->flags & I2C_M_RD) != 0 &&
> +						i2c->xmit_size != i2c->msg->len)
> +						netup_i2c_fifo_rx(i2c);
> +					dev_dbg(i2c->adap.dev.parent,
> +						"%s(): msg %d OK\n",
> +						__func__, i);
> +					trans_done = 1;
> +					break;
> +				case STATE_ERROR:
> +					res = -EIO;
> +					dev_dbg(i2c->adap.dev.parent,
> +						"%s(): error state\n",
> +						__func__);
> +					goto done;
> +				default:
> +					dev_dbg(i2c->adap.dev.parent,
> +						"%s(): invalid state %d\n",
> +						__func__, i2c->state);
> +					res = -EINVAL;
> +					goto done;
> +				}
> +				if (!trans_done) {
> +					i2c->state = STATE_WAIT;
> +					reg = readw(
> +						&i2c->regs->twi_ctrl0_stat);
> +					writew(TWI_IRQEN | reg,
> +						&i2c->regs->twi_ctrl0_stat);
> +				}
> +				spin_unlock_irqrestore(&i2c->lock, flags);
> +			} else {
> +				spin_lock_irqsave(&i2c->lock, flags);
> +				dev_dbg(i2c->adap.dev.parent,
> +					"%s(): wait timeout\n", __func__);
> +				res = -ETIMEDOUT;
> +				goto done;
> +			}
> +			spin_lock_irqsave(&i2c->lock, flags);
> +		}
> +	}
> +done:
> +	spin_unlock_irqrestore(&i2c->lock, flags);
> +	dev_dbg(i2c->adap.dev.parent, "%s(): result %d\n", __func__, res);
> +	return res;
> +}
> +
> +static u32 netup_i2c_func(struct i2c_adapter *adap)
> +{
> +	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm netup_i2c_algorithm = {
> +	.master_xfer	= netup_i2c_xfer,
> +	.functionality	= netup_i2c_func,
> +};
> +
> +static struct i2c_adapter netup_i2c_adapter = {
> +	.owner		= THIS_MODULE,
> +	.name		= NETUP_UNIDVB_NAME,
> +	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
> +	.algo		= &netup_i2c_algorithm,
> +};
> +
> +static int netup_i2c_init(struct netup_unidvb_dev *ndev, int bus_num)
> +{
> +	int ret;
> +	struct netup_i2c *i2c;
> +
> +	if (bus_num < 0 || bus_num > 1) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): invalid bus_num %d\n", __func__, bus_num);
> +		return -EINVAL;
> +	}
> +	i2c = &ndev->i2c[bus_num];
> +	spin_lock_init(&i2c->lock);
> +	init_waitqueue_head(&i2c->wq);
> +	i2c->regs = (struct netup_i2c_regs *)(ndev->bmmio0 +
> +		(bus_num == 0 ? NETUP_I2C_BUS0_ADDR : NETUP_I2C_BUS1_ADDR));
> +	netup_i2c_reset(i2c);
> +	i2c->adap = netup_i2c_adapter;
> +	i2c->adap.dev.parent = &ndev->pci_dev->dev;
> +	i2c_set_adapdata(&i2c->adap, i2c);
> +	ret = i2c_add_adapter(&i2c->adap);
> +	if (ret) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): failed to add I2C adapter\n", __func__);
> +		return ret;
> +	}
> +	dev_info(&ndev->pci_dev->dev,
> +		"%s(): registered I2C bus %d at 0x%x\n",
> +		__func__,
> +		bus_num, (bus_num == 0 ?
> +			NETUP_I2C_BUS0_ADDR :
> +			NETUP_I2C_BUS1_ADDR));
> +	return 0;
> +}
> +
> +static void netup_i2c_remove(struct netup_unidvb_dev *ndev, int bus_num)
> +{
> +	struct netup_i2c *i2c;
> +
> +	if (bus_num < 0 || bus_num > 1) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): invalid bus number %d\n", __func__, bus_num);
> +		return;
> +	}
> +	i2c = &ndev->i2c[bus_num];
> +	netup_i2c_reset(i2c);
> +	/* remove adapter */
> +	i2c_del_adapter(&i2c->adap);
> +	dev_info(&ndev->pci_dev->dev,
> +		"netup_i2c_remove: unregistered I2C bus %d\n", bus_num);
> +}
> +
> +int netup_i2c_register(struct netup_unidvb_dev *ndev)
> +{
> +	int ret;
> +
> +	ret = netup_i2c_init(ndev, 0);
> +	if (ret)
> +		return ret;
> +	ret = netup_i2c_init(ndev, 1);
> +	if (ret) {
> +		netup_i2c_remove(ndev, 0);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +void netup_i2c_unregister(struct netup_unidvb_dev *ndev)
> +{
> +	netup_i2c_remove(ndev, 0);
> +	netup_i2c_remove(ndev, 1);
> +}
> +
> diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
> new file mode 100644
> index 0000000..f55b327
> --- /dev/null
> +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
> @@ -0,0 +1,252 @@
> +/*
> + * netup_unidvb_spi.c
> + *
> + * Internal SPI driver for NetUP Universal Dual DVB-CI
> + *
> + * Copyright (C) 2014 NetUP Inc.
> + * Copyright (C) 2014 Sergey Kozlov <serjk@xxxxxxxx>
> + * Copyright (C) 2014 Abylay Ospan <aospan@xxxxxxxx>
> + *
> + * 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.
> + */
> +
> +#include "netup_unidvb.h"
> +#include <linux/spi/spi.h>
> +#include <linux/spi/flash.h>
> +#include <linux/mtd/partitions.h>
> +#include <mtd/mtd-abi.h>
> +
> +#define NETUP_SPI_CTRL_IRQ	0x1000
> +#define NETUP_SPI_CTRL_IMASK	0x2000
> +#define NETUP_SPI_CTRL_START	0x8000
> +#define NETUP_SPI_CTRL_LAST_CS	0x4000
> +
> +#define NETUP_SPI_TIMEOUT	6000
> +
> +enum netup_spi_state {
> +	SPI_STATE_START,
> +	SPI_STATE_DONE,
> +};
> +
> +struct netup_spi_regs {
> +	__u8	data[1024];
> +	__le16	control_stat;
> +	__le16	clock_divider;
> +} __packed __aligned(1);
> +
> +struct netup_spi {
> +	struct device			*dev;
> +	struct spi_master		*master;
> +	struct netup_spi_regs		*regs;
> +	u8 __iomem			*mmio;
> +	spinlock_t			lock;
> +	wait_queue_head_t		waitq;
> +	enum netup_spi_state		state;
> +};
> +
> +static char netup_spi_name[64] = "fpga";
> +
> +static struct mtd_partition netup_spi_flash_partitions = {
> +	.name = netup_spi_name,
> +	.size = 0x1000000, /* 16MB */
> +	.offset = 0,
> +	.mask_flags = MTD_CAP_ROM
> +};
> +
> +static struct flash_platform_data spi_flash_data = {
> +	.name = "netup0_m25p128",
> +	.parts = &netup_spi_flash_partitions,
> +	.nr_parts = 1,
> +};
> +
> +static struct spi_board_info netup_spi_board = {
> +	.modalias = "m25p128",
> +	.max_speed_hz = 11000000,
> +	.chip_select = 0,
> +	.mode = SPI_MODE_0,
> +	.platform_data = &spi_flash_data,
> +};
> +
> +irqreturn_t netup_spi_interrupt(struct netup_spi *spi)
> +{
> +	u16 reg;
> +	unsigned long flags;
> +
> +	if (!spi) {
> +		dev_dbg(&spi->master->dev,
> +			"%s(): SPI not initialized\n", __func__);
> +		return IRQ_NONE;
> +	}
> +	spin_lock_irqsave(&spi->lock, flags);
> +	reg = readw(&spi->regs->control_stat);
> +	if (!(reg & NETUP_SPI_CTRL_IRQ)) {
> +		spin_unlock_irqrestore(&spi->lock, flags);
> +		dev_dbg(&spi->master->dev,
> +			"%s(): not mine interrupt\n", __func__);
> +		return IRQ_NONE;
> +	}
> +	writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat);
> +	reg = readw(&spi->regs->control_stat);
> +	writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat);
> +	spi->state = SPI_STATE_DONE;
> +	wake_up(&spi->waitq);
> +	spin_unlock_irqrestore(&spi->lock, flags);
> +	dev_dbg(&spi->master->dev,
> +		"%s(): SPI interrupt handled\n", __func__);
> +	return IRQ_HANDLED;
> +}
> +
> +static int netup_spi_transfer(struct spi_master *master,
> +			      struct spi_message *msg)
> +{
> +	struct netup_spi *spi = spi_master_get_devdata(master);
> +	struct spi_transfer *t;
> +	int result = 0;
> +	u32 tr_size;
> +
> +	/* reset CS */
> +	writew(NETUP_SPI_CTRL_LAST_CS, &spi->regs->control_stat);
> +	writew(0, &spi->regs->control_stat);
> +	list_for_each_entry(t, &msg->transfers, transfer_list) {
> +		tr_size = t->len;
> +		while (tr_size) {
> +			u32 frag_offset = t->len - tr_size;
> +			u32 frag_size = (tr_size > sizeof(spi->regs->data)) ?
> +					sizeof(spi->regs->data) : tr_size;
> +			int frag_last = 0;
> +
> +			if (list_is_last(&t->transfer_list,
> +					&msg->transfers) &&
> +					frag_offset + frag_size == t->len) {
> +				frag_last = 1;
> +			}
> +			if (t->tx_buf) {
> +				memcpy_toio(spi->regs->data,
> +					t->tx_buf + frag_offset,
> +					frag_size);
> +			} else {
> +				memset_io(spi->regs->data,
> +					0, frag_size);
> +			}
> +			spi->state = SPI_STATE_START;
> +			writew((frag_size & 0x3ff) |
> +				NETUP_SPI_CTRL_IMASK |
> +				NETUP_SPI_CTRL_START |
> +				(frag_last ? NETUP_SPI_CTRL_LAST_CS : 0),
> +				&spi->regs->control_stat);
> +			dev_dbg(&spi->master->dev,
> +				"%s(): control_stat 0x%04x\n",
> +				__func__, readw(&spi->regs->control_stat));
> +			wait_event_timeout(spi->waitq,
> +				spi->state != SPI_STATE_START,
> +				msecs_to_jiffies(NETUP_SPI_TIMEOUT));
> +			if (spi->state == SPI_STATE_DONE) {
> +				if (t->rx_buf) {
> +					memcpy_fromio(t->rx_buf + frag_offset,
> +						spi->regs->data, frag_size);
> +				}
> +			} else {
> +				if (spi->state == SPI_STATE_START) {
> +					dev_dbg(&spi->master->dev,
> +						"%s(): transfer timeout\n",
> +						__func__);
> +				} else {
> +					dev_dbg(&spi->master->dev,
> +						"%s(): invalid state %d\n",
> +						__func__, spi->state);
> +				}
> +				result = -EIO;
> +				goto done;
> +			}
> +			tr_size -= frag_size;
> +			msg->actual_length += frag_size;
> +		}
> +	}
> +done:
> +	msg->status = result;
> +	spi_finalize_current_message(master);
> +	return result;
> +}
> +
> +static int netup_spi_setup(struct spi_device *spi)
> +{
> +	return 0;
> +}
> +
> +int netup_spi_init(struct netup_unidvb_dev *ndev)
> +{
> +	struct spi_master *master;
> +	struct netup_spi *nspi;
> +
> +	master = spi_alloc_master(&ndev->pci_dev->dev,
> +		sizeof(struct netup_spi));
> +	if (!master) {
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): unable to alloc SPI master\n", __func__);
> +		return -EINVAL;
> +	}
> +	nspi = spi_master_get_devdata(master);
> +	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
> +	master->bus_num = -1;
> +	master->num_chipselect = 1;
> +	master->transfer_one_message = netup_spi_transfer;
> +	master->setup = netup_spi_setup;
> +	spin_lock_init(&nspi->lock);
> +	init_waitqueue_head(&nspi->waitq);
> +	nspi->master = master;
> +	nspi->regs = (struct netup_spi_regs *)(ndev->bmmio0 + 0x4000);
> +	writew(2, &nspi->regs->clock_divider);
> +	writew(NETUP_UNIDVB_IRQ_SPI, ndev->bmmio0 + REG_IMASK_SET);
> +	ndev->spi = nspi;
> +	if (spi_register_master(master)) {
> +		ndev->spi = NULL;
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): unable to register SPI bus\n", __func__);
> +		return -EINVAL;
> +	}
> +	snprintf(netup_spi_name,
> +		sizeof(netup_spi_name),
> +		"fpga_%02x:%02x.%01x",
> +		ndev->pci_bus,
> +		ndev->pci_slot,
> +		ndev->pci_func);
> +	if (!spi_new_device(master, &netup_spi_board)) {
> +		ndev->spi = NULL;
> +		dev_err(&ndev->pci_dev->dev,
> +			"%s(): unable to create SPI device\n", __func__);
> +		return -EINVAL;
> +	}
> +	dev_dbg(&ndev->pci_dev->dev, "%s(): SPI init OK\n", __func__);
> +	return 0;
> +}
> +
> +void netup_spi_release(struct netup_unidvb_dev *ndev)
> +{
> +	u16 reg;
> +	unsigned long flags;
> +	struct netup_spi *spi = ndev->spi;
> +
> +	if (!spi) {
> +		dev_dbg(&spi->master->dev,
> +			"%s(): SPI not initialized\n", __func__);
> +		return;
> +	}
> +	spin_lock_irqsave(&spi->lock, flags);
> +	reg = readw(&spi->regs->control_stat);
> +	writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat);
> +	reg = readw(&spi->regs->control_stat);
> +	writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat);
> +	spin_unlock_irqrestore(&spi->lock, flags);
> +	spi_unregister_master(spi->master);
> +	ndev->spi = NULL;
> +}
> +
> +
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux