From: Буди Романто, AreMa Inc <knightri...@are.ma>

PCIE bridge driver for PT3 & PX-Q3PE
Please read cover letter for details.

Signed-off-by: Буди Романто, AreMa Inc <knightri...@are.ma>
---
 drivers/media/pci/Kconfig          |   2 +-
 drivers/media/pci/Makefile         |   2 +-
 drivers/media/pci/ptx/Kconfig      |  21 ++
 drivers/media/pci/ptx/Makefile     |   8 +
 drivers/media/pci/ptx/pt3_pci.c    | 425 +++++++++++++++++++++++++++
 drivers/media/pci/ptx/ptx_common.c | 252 ++++++++++++++++
 drivers/media/pci/ptx/ptx_common.h |  74 +++++
 drivers/media/pci/ptx/pxq3pe_pci.c | 585 +++++++++++++++++++++++++++++++++++++
 8 files changed, 1367 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/pci/ptx/Kconfig
 create mode 100644 drivers/media/pci/ptx/Makefile
 create mode 100644 drivers/media/pci/ptx/pt3_pci.c
 create mode 100644 drivers/media/pci/ptx/ptx_common.c
 create mode 100644 drivers/media/pci/ptx/ptx_common.h
 create mode 100644 drivers/media/pci/ptx/pxq3pe_pci.c

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 48a611b..9d63ad6 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -44,7 +44,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
-source "drivers/media/pci/pt3/Kconfig"
+source "drivers/media/pci/ptx/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 5f8aacb..984e37c 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,7 +7,7 @@ obj-y        += ttpci/          \
                pluto2/         \
                dm1105/         \
                pt1/            \
-               pt3/            \
+               ptx/            \
                mantis/         \
                ngene/          \
                ddbridge/       \
diff --git a/drivers/media/pci/ptx/Kconfig b/drivers/media/pci/ptx/Kconfig
new file mode 100644
index 0000000..792acfe
--- /dev/null
+++ b/drivers/media/pci/ptx/Kconfig
@@ -0,0 +1,21 @@
+config DVB_PT3
+       tristate "Earthsoft PT3 cards"
+       depends on DVB_CORE && PCI && I2C
+       select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+       select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+       select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+       help
+         Support for Earthsoft PT3 ISDB-S/T PCIe cards.
+
+         Say Y or M if you own such a device and want to use it.
+
+config DVB_PXQ3PE
+       tristate "PLEX PX-Q3PE cards"
+       depends on DVB_CORE && PCI && I2C
+       select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+       select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+       select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+       help
+         Support for PLEX PX-Q3PE ISDB-S/T PCIe cards.
+
+         Say Y or M if you own such a device and want to use it.
diff --git a/drivers/media/pci/ptx/Makefile b/drivers/media/pci/ptx/Makefile
new file mode 100644
index 0000000..b10ad8a
--- /dev/null
+++ b/drivers/media/pci/ptx/Makefile
@@ -0,0 +1,8 @@
+pt3-objs       := pt3_pci.o ptx_common.o
+pxq3pe-objs    := pxq3pe_pci.o ptx_common.o
+
+obj-$(CONFIG_DVB_PT3)          += pt3.o
+obj-$(CONFIG_DVB_PXQ3PE)       += pxq3pe.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends 
-Idrivers/media/tuners
+
diff --git a/drivers/media/pci/ptx/pt3_pci.c b/drivers/media/pci/ptx/pt3_pci.c
new file mode 100644
index 0000000..07431da
--- /dev/null
+++ b/drivers/media/pci/ptx/pt3_pci.c
@@ -0,0 +1,425 @@
+/*
+       DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV 
FPGA EP4CGX15BF14C8N
+
+       Copyright (C) Budi Rachmanto, AreMa Inc. <i...@are.ma>
+
+       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 "tc90522.h"
+#include "qm1d1c004x.h"
+#include "mxl301rf.h"
+#include "ptx_common.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id[] = {
+       {PCI_DEVICE(0x1172, 0x4c15)},
+       {},
+};
+MODULE_DEVICE_TABLE(pci, pt3_id);
+
+enum ePT3 {
+       PT3_REG_VERSION = 0x00, /*      R       Version         */
+       PT3_REG_BUS     = 0x04, /*      R       Bus             */
+       PT3_REG_SYS_W   = 0x08, /*      W       System          */
+       PT3_REG_SYS_R   = 0x0c, /*      R       System          */
+       PT3_REG_I2C_W   = 0x10, /*      W       I2C             */
+       PT3_REG_I2C_R   = 0x14, /*      R       I2C             */
+       PT3_REG_RAM_W   = 0x18, /*      W       RAM             */
+       PT3_REG_RAM_R   = 0x1c, /*      R       RAM             */
+       PT3_DMA_BASE    = 0x40, /* + 0x18*idx                   */
+       PT3_DMA_OFFSET  = 0x18,
+       PT3_DMA_DESC    = 0x00, /*      W       DMA descriptor  */
+       PT3_DMA_CTL     = 0x08, /*      W       DMA             */
+       PT3_TS_CTL      = 0x0c, /*      W       TS              */
+       PT3_STATUS      = 0x10, /*      R       DMA/FIFO/TS     */
+       PT3_TS_ERR      = 0x14, /*      R       TS              */
+
+       PT3_I2C_DATA_OFFSET     = 0x800,
+       PT3_I2C_START_ADDR      = 0x17fa,
+
+       PT3_PWR_OFF             = 0x00,
+       PT3_PWR_AMP_ON          = 0x04,
+       PT3_PWR_TUNER_ON        = 0x40,
+};
+
+struct pt3_card {
+       void __iomem    *bar_reg,
+                       *bar_mem;
+};
+
+struct pt3_dma {
+       dma_addr_t      adr;
+       u8              *dat;
+       u32             sz;
+};
+
+struct pt3_adap {
+       u32     ts_blk_idx,
+               ts_blk_cnt,
+               desc_pg_cnt;
+       void __iomem    *dma_base;
+       struct pt3_dma  *ts_info,
+                       *desc_info;
+};
+
+int pt3_i2c_flush(struct pt3_card *c, u32 start_addr)
+{
+       u32     val     = 0b0110,
+               i       = 999;
+
+       void i2c_wait(void)
+       {
+               while (1) {
+                       val = readl(c->bar_reg + PT3_REG_I2C_R);
+
+                       if (!(val & 1))                                         
/* sequence stopped */
+                               return;
+                       msleep_interruptible(0);
+               }
+       }
+
+       while ((val & 0b0110) && i--) {                                         
/* I2C bus is dirty */
+               i2c_wait();
+               writel(1 << 16 | start_addr, c->bar_reg + PT3_REG_I2C_W);       
/* 0x00010000 start sequence */
+               i2c_wait();
+       }
+       return val & 0b0110 ? -EIO : 0;                                         
/* ACK status */
+}
+
+int pt3_i2c_xfr(struct i2c_adapter *i2c, struct i2c_msg *msg, int sz)
+{
+       enum pt3_i2c_cmd {
+               I_END,
+               I_ADDRESS,
+               I_CLOCK_L,
+               I_CLOCK_H,
+               I_DATA_L,
+               I_DATA_H,
+               I_RESET,
+               I_SLEEP,
+               I_DATA_L_NOP    = 0x08,
+               I_DATA_H_NOP    = 0x0c,
+               I_DATA_H_READ   = 0x0d,
+               I_DATA_H_ACK0   = 0x0e,
+       };
+       struct ptx_card *card   = i2c_get_adapdata(i2c);
+       struct pt3_card *c      = card->priv;
+       u32     offset          = 0;
+       u8      buf;
+       bool    filled          = false;
+
+       void i2c_shoot(u8 dat)
+       {
+               if (filled) {
+                       buf |= dat << 4;
+                       writeb(buf, c->bar_mem + PT3_I2C_DATA_OFFSET + offset);
+                       offset++;
+               } else
+                       buf = dat;
+               filled ^= true;
+       }
+
+       void i2c_w(const u8 *dat, u32 size)
+       {
+               u32 i, j;
+
+               for (i = 0; i < size; i++) {
+                       for (j = 0; j < 8; j++)
+                               i2c_shoot((dat[i] >> (7 - j)) & 1 ? 
I_DATA_H_NOP : I_DATA_L_NOP);
+                       i2c_shoot(I_DATA_H_ACK0);
+               }
+       }
+
+       void i2c_r(u32 size)
+       {
+               u32 i, j;
+
+               for (i = 0; i < size; i++) {
+                       for (j = 0; j < 8; j++)
+                               i2c_shoot(I_DATA_H_READ);
+                       if (i == (size - 1))
+                               i2c_shoot(I_DATA_H_NOP);
+                       else
+                               i2c_shoot(I_DATA_L_NOP);
+               }
+       }
+       int i, j;
+
+       if (sz < 1 || sz > 3 || !msg || msg[0].flags)           /* always write 
first */
+               return -ENOTSUPP;
+       mutex_lock(&card->lock);
+       for (i = 0; i < sz; i++) {
+               u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+
+               /* start */
+               i2c_shoot(I_DATA_H);
+               i2c_shoot(I_CLOCK_H);
+               i2c_shoot(I_DATA_L);
+               i2c_shoot(I_CLOCK_L);
+               i2c_w(&byte, 1);
+               if (msg[i].flags == I2C_M_RD)
+                       i2c_r(msg[i].len);
+               else
+                       i2c_w(msg[i].buf, msg[i].len);
+       }
+
+       /* stop */
+       i2c_shoot(I_DATA_L);
+       i2c_shoot(I_CLOCK_H);
+       i2c_shoot(I_DATA_H);
+       i2c_shoot(I_END);
+       if (filled)
+               i2c_shoot(I_END);
+       if (pt3_i2c_flush(c, 0))
+               sz = -EIO;
+       else
+               for (i = 1; i < sz; i++)
+                       if (msg[i].flags == I2C_M_RD)
+                               for (j = 0; j < msg[i].len; j++)
+                                       msg[i].buf[j] = readb(c->bar_mem + 
PT3_I2C_DATA_OFFSET + j);
+       mutex_unlock(&card->lock);
+       return sz;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+       .functionality  = ptx_i2c_func,
+       .master_xfer    = pt3_i2c_xfr,
+};
+
+void pt3_lnb(struct ptx_card *card, bool lnb)
+{
+       struct pt3_card *c = card->priv;
+
+       writel(lnb ? 15 : 12, c->bar_reg + PT3_REG_SYS_W);
+}
+
+int pt3_power(struct dvb_frontend *fe, u8 pwr)
+{
+       struct i2c_client       *d      = fe->demodulator_priv;
+       u8              buf[]   = {0x1E, pwr | 0b10011001};
+       struct i2c_msg  msg[]   = {
+               {.addr = d->addr,       .flags = 0,     .buf = buf,     .len = 
2,},
+       };
+
+       return i2c_transfer(d->adapter, msg, 1) == 1 ? 0 : -EIO;
+}
+
+int pt3_dma_run(struct ptx_adap *adap, bool ON)
+{
+       struct pt3_adap *p      = adap->priv;
+       void __iomem    *base   = p->dma_base;
+       int             i       = 999;
+
+       if (ON) {
+               for (i = 0; i < p->ts_blk_cnt; i++)             /* 17 */
+                       *p->ts_info[i].dat      = PTX_TS_NOT_SYNC;
+               p->ts_blk_idx = 0;
+               writel(2, base + PT3_DMA_CTL);                  /* stop DMA */
+               writeq(p->desc_info->adr, base + PT3_DMA_DESC);
+               writel(1, base + PT3_DMA_CTL);                  /* start DMA */
+       } else {
+               writel(2, base + PT3_DMA_CTL);                  /* stop DMA */
+               while (i--) {
+                       if (!(readl(base + PT3_STATUS) & 1))
+                               break;
+                       msleep_interruptible(0);
+               }
+       }
+       return i ? 0 : -ETIMEDOUT;
+}
+
+int pt3_thread(void *dat)
+{
+       struct ptx_adap *adap   = dat;
+       struct pt3_adap *p      = adap->priv;
+       struct pt3_dma  *ts;
+
+       set_freezable();
+       while (!kthread_should_stop()) {
+               u32 next = (p->ts_blk_idx + 1) % p->ts_blk_cnt;
+
+               try_to_freeze();
+               ts = p->ts_info + next;
+               if (*ts->dat != PTX_TS_SYNC) {          /* wait until 1 TS 
block is full */
+                       schedule_timeout_interruptible(0);
+                       continue;
+               }
+               ts = p->ts_info + p->ts_blk_idx;
+               dvb_dmx_swfilter_packets(&adap->demux, ts->dat, ts->sz / 
PTX_TS_SIZE);
+               *ts->dat        = PTX_TS_NOT_SYNC;      /* mark as read */
+               p->ts_blk_idx   = next;
+       }
+       return 0;
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+       struct ptx_card *card   = pci_get_drvdata(pdev);
+       struct pt3_card *c;
+       struct ptx_adap *adap;
+       int             i;
+
+       if (!card)
+               return;
+       c       = card->priv;
+       adap    = card->adap;
+       for (i = 0; i < card->adapn; i++, adap++) {
+               struct pt3_adap *p      = adap->priv;
+               struct pt3_dma  *page;
+               u32             j;
+
+               pt3_dma_run(adap, false);
+               if (p->ts_info) {
+                       for (j = 0; j < p->ts_blk_cnt; j++) {
+                               page = &p->ts_info[j];
+                               if (page->dat)
+                                       pci_free_consistent(adap->card->pdev, 
page->sz, page->dat, page->adr);
+                       }
+                       kfree(p->ts_info);
+               }
+               if (p->desc_info) {
+                       for (j = 0; j < p->desc_pg_cnt; j++) {
+                               page = &p->desc_info[j];
+                               if (page->dat)
+                                       pci_free_consistent(adap->card->pdev, 
page->sz, page->dat, page->adr);
+                       }
+                       kfree(p->desc_info);
+               }
+               if (adap->fe) {
+                       ptx_sleep(adap->fe);
+                       pt3_power(adap->fe, PT3_PWR_OFF);
+               }
+       }
+       ptx_unregister_adap(card);
+       if (c->bar_reg)
+               iounmap(c->bar_reg);
+       if (c->bar_mem)
+               iounmap(c->bar_mem);
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+       struct ptx_adap *adap;
+       struct pt3_card *c;
+       struct ptx_subdev_info  pt3_subdev_info[] = {
+               {SYS_ISDBS, 0b00010001, TC90522_MODNAME, 0x63, 
QM1D1C004X_MODNAME},
+               {SYS_ISDBS, 0b00010011, TC90522_MODNAME, 0x60, 
QM1D1C004X_MODNAME},
+               {SYS_ISDBT, 0b00010000, TC90522_MODNAME, 0x62, 
MXL301RF_MODNAME},
+               {SYS_ISDBT, 0b00010010, TC90522_MODNAME, 0x61, 
MXL301RF_MODNAME},
+       };
+       struct ptx_card *card   = ptx_alloc(pdev, KBUILD_MODNAME, 
ARRAY_SIZE(pt3_subdev_info),
+                                       sizeof(struct pt3_card), sizeof(struct 
pt3_adap), pt3_lnb);
+
+       bool dma_create(struct pt3_adap *p)
+       {
+               struct dma_desc {
+                       u64 page_addr;
+                       u32 page_size;
+                       u64 next_desc;
+               } __packed;             /* 20B */
+               enum {
+                       DESC_SZ         = sizeof(struct dma_desc),              
/* 20B  */
+                       DESC_MAX        = 4096 / DESC_SZ,                       
/* 204  */
+                       DESC_PAGE_SZ    = DESC_MAX * DESC_SZ,                   
/* 4080 */
+                       TS_PAGE_CNT     = PTX_TS_SIZE / 4,                      
/* 47   */
+                       TS_BLOCK_CNT    = 17,
+               };
+               struct pt3_dma  *descinfo;
+               struct dma_desc *prev           = NULL,
+                               *curr;
+               u32             i,
+                               j,
+                               desc_todo       = 0,
+                               desc_pg_idx     = 0;
+               u64             desc_addr;
+
+               p->ts_blk_cnt   = TS_BLOCK_CNT;                                 
                /* 17   */
+               p->desc_pg_cnt  = roundup(TS_PAGE_CNT * p->ts_blk_cnt, 
DESC_MAX);               /* 4    */
+               p->ts_info      = kcalloc(p->ts_blk_cnt, sizeof(struct 
pt3_dma), GFP_KERNEL);
+               p->desc_info    = kcalloc(p->desc_pg_cnt, sizeof(struct 
pt3_dma), GFP_KERNEL);
+               if (!p->ts_info || !p->desc_info)
+                       return false;
+               for (i = 0; i < p->desc_pg_cnt; i++) {                          
                /* 4    */
+                       p->desc_info[i].sz      = DESC_PAGE_SZ;                 
                /* 4080B, max 204 * 4 = 816 descs */
+                       p->desc_info[i].dat     = 
pci_alloc_consistent(card->pdev, p->desc_info[i].sz, &p->desc_info[i].adr);
+                       if (!p->desc_info[i].dat)
+                               return false;
+                       memset(p->desc_info[i].dat, 0, p->desc_info[i].sz);
+               }
+               for (i = 0; i < p->ts_blk_cnt; i++) {                           
                /* 17   */
+                       p->ts_info[i].sz        = DESC_PAGE_SZ * TS_PAGE_CNT;   
                /* 1020 pkts, 4080 * 47 = 191760B, total 3259920B */
+                       p->ts_info[i].dat       = 
pci_alloc_consistent(card->pdev, p->ts_info[i].sz, &p->ts_info[i].adr);
+                       if (!p->ts_info[i].dat)
+                               return false;
+                       for (j = 0; j < TS_PAGE_CNT; j++) {                     
                /* 47, total 47 * 17 = 799 pages */
+                               if (!desc_todo) {                               
                /* 20   */
+                                       descinfo        = p->desc_info + 
desc_pg_idx;           /* jump to next desc_pg */
+                                       curr            = (struct dma_desc 
*)descinfo->dat;
+                                       desc_addr       = descinfo->adr;
+                                       desc_todo       = DESC_MAX;             
                /* 204  */
+                                       desc_pg_idx++;
+                               }
+                               if (prev)
+                                       prev->next_desc = desc_addr;
+                               curr->page_addr = p->ts_info[i].adr + 
DESC_PAGE_SZ * j;
+                               curr->page_size = DESC_PAGE_SZ;
+                               curr->next_desc = p->desc_info->adr;            
                /* circular link */
+                               prev            = curr;
+                               curr++;
+                               desc_addr       += DESC_SZ;
+                               desc_todo--;
+                       }
+               }
+               return true;
+       }
+
+       u8      i;
+       int     ret     = !card || pci_read_config_byte(pdev, 
PCI_CLASS_REVISION, &i);
+
+       if (ret)
+               return ptx_abort(pdev, pt3_remove, ret, "PCI/DMA/memory error");
+       if (i != 1)
+               return ptx_abort(pdev, pt3_remove, -ENOTSUPP, "PCI Rev%d not 
supported", i);
+       pci_set_master(pdev);
+       c               = card->priv;
+       c->bar_reg      = pci_ioremap_bar(pdev, 0);
+       c->bar_mem      = pci_ioremap_bar(pdev, 2);
+       if (!c->bar_reg || !c->bar_mem)
+               return ptx_abort(pdev, pt3_remove, -EIO, "Failed 
pci_ioremap_bar");
+       ret = (readl(c->bar_reg + PT3_REG_VERSION) >> 8) & 0xFF00FF;
+       if (ret != 0x030004)
+               return ptx_abort(pdev, pt3_remove, -ENOTSUPP, "PT%d FPGA v%d 
not supported", ret >> 16, ret & 0xFF);
+       for (i = 0, adap = card->adap; i < card->adapn; i++, adap++) {
+               struct pt3_adap *p      = adap->priv;
+
+               p->dma_base     = c->bar_reg + PT3_DMA_BASE + PT3_DMA_OFFSET * 
i;
+               if (!dma_create(p))
+                       return ptx_abort(pdev, pt3_remove, -ENOMEM, "Failed 
dma_create");
+       }
+       adap--;
+       ret =   ptx_i2c_add_adapter(card, &pt3_i2c_algo)                        
        ||
+               pt3_i2c_flush(c, 0)                                             
        ||
+               ptx_register_adap(card, pt3_subdev_info, pt3_thread, 
pt3_dma_run)       ||
+               pt3_power(adap->fe, PT3_PWR_TUNER_ON)                           
        ||
+               pt3_i2c_flush(c, PT3_I2C_START_ADDR)                            
        ||
+               pt3_power(adap->fe, PT3_PWR_TUNER_ON | PT3_PWR_AMP_ON);
+       return  ret ?
+               ptx_abort(pdev, pt3_remove, ret, "Unable to register I2C/DVB 
adapter/frontend") :
+               0;
+}
+
+static struct pci_driver pt3_driver = {
+       .name           = KBUILD_MODNAME,
+       .id_table       = pt3_id,
+       .probe          = pt3_probe,
+       .remove         = pt3_remove,
+};
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/pci/ptx/ptx_common.c 
b/drivers/media/pci/ptx/ptx_common.c
new file mode 100644
index 0000000..90d697f
--- /dev/null
+++ b/drivers/media/pci/ptx/ptx_common.c
@@ -0,0 +1,252 @@
+/*
+       Common procedures for PT3, PX-Q3PE, and other DVB drivers
+
+       Copyright (C) Budi Rachmanto, AreMa Inc. <i...@are.ma>
+*/
+
+#include "ptx_common.h"
+
+void ptx_lnb(struct ptx_card *card)
+{
+       struct ptx_adap *adap;
+       int     i;
+       bool    lnb = false;
+
+       for (i = 0, adap = card->adap; adap->fe && i < card->adapn; i++, adap++)
+               if (adap->fe->dtv_property_cache.delivery_system == SYS_ISDBS 
&& adap->ON) {
+                       lnb = true;
+                       break;
+       }
+       if (card->lnbON != lnb) {
+               card->lnb(card, lnb);
+               card->lnbON = lnb;
+       }
+}
+
+int ptx_sleep(struct dvb_frontend *fe)
+{
+       struct ptx_adap *adap   = container_of(fe->dvb, struct ptx_adap, dvb);
+
+       adap->ON = false;
+       ptx_lnb(adap->card);
+       return adap->fe_sleep ? adap->fe_sleep(fe) : 0;
+}
+
+int ptx_wakeup(struct dvb_frontend *fe)
+{
+       struct ptx_adap *adap   = container_of(fe->dvb, struct ptx_adap, dvb);
+
+       adap->ON = true;
+       ptx_lnb(adap->card);
+       return adap->fe_wakeup ? adap->fe_wakeup(fe) : 0;
+}
+
+int ptx_stop_feed(struct dvb_demux_feed *feed)
+{
+       struct ptx_adap *adap   = container_of(feed->demux, struct ptx_adap, 
demux);
+
+       adap->card->dma(adap, false);
+       if (adap->kthread)
+               kthread_stop(adap->kthread);
+       return 0;
+}
+
+int ptx_start_feed(struct dvb_demux_feed *feed)
+{
+       struct ptx_adap *adap   = container_of(feed->demux, struct ptx_adap, 
demux);
+
+       if (adap->card->thread)
+               adap->kthread = kthread_run(adap->card->thread, adap, 
"%s_%d%c", adap->dvb.name, adap->dvb.num,
+                                       
adap->fe->dtv_property_cache.delivery_system == SYS_ISDBS ? 's' : 't');
+       return IS_ERR(adap->kthread) ? PTR_ERR(adap->kthread) : 
adap->card->dma(adap, true);
+}
+
+struct ptx_card *ptx_alloc(struct pci_dev *pdev, u8 *name, u8 adapn, u32 
sz_card_priv, u32 sz_adap_priv,
+                       void (*lnb)(struct ptx_card *, bool))
+{
+       u8 i;
+       struct ptx_card *card = kzalloc(sizeof(struct ptx_card) + sz_card_priv 
+ adapn *
+                                       (sizeof(struct ptx_adap) + 
sz_adap_priv), GFP_KERNEL);
+       if (!card)
+               return NULL;
+       card->priv      = sz_card_priv ? &card[1] : NULL;
+       card->adap      = (struct ptx_adap *)((u8 *)&card[1] + sz_card_priv);
+       card->pdev      = pdev;
+       card->adapn     = adapn;
+       card->name      = name;
+       card->lnbON     = true;
+       card->lnb       = lnb;
+       for (i = 0; i < card->adapn; i++) {
+               struct ptx_adap *p = &card->adap[i];
+
+               p->card = card;
+               p->priv = sz_adap_priv ? (u8 *)&card->adap[card->adapn] + i * 
sz_adap_priv : NULL;
+       }
+       if (pci_enable_device(pdev)                                     ||
+               pci_set_dma_mask(pdev, DMA_BIT_MASK(32))                ||
+               pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32))     ||
+               pci_request_regions(pdev, name)) {
+               kfree(card);
+               return NULL;
+       }
+       pci_set_drvdata(pdev, card);
+       return card;
+}
+
+int ptx_i2c_add_adapter(struct ptx_card *card, const struct i2c_algorithm 
*algo)
+{
+       struct i2c_adapter *i2c = &card->i2c;
+
+       i2c->algo       = algo;
+       i2c->dev.parent = &card->pdev->dev;
+       strcpy(i2c->name, card->name);
+       i2c_set_adapdata(i2c, card);
+       mutex_init(&card->lock);
+       return  i2c_add_adapter(i2c);
+}
+
+void ptx_unregister_subdev(struct i2c_client *c)
+{
+       if (!c)
+               return;
+       if (c->dev.driver)
+               module_put(c->dev.driver->owner);
+       i2c_unregister_device(c);
+}
+
+struct i2c_client *ptx_register_subdev(struct i2c_adapter *i2c, struct 
dvb_frontend *fe, u16 adr, char *name)
+{
+       struct i2c_client       *c;
+       struct i2c_board_info   info = {
+               .platform_data  = fe,
+               .addr           = adr,
+       };
+
+       strlcpy(info.type, name, I2C_NAME_SIZE);
+       request_module("%s", info.type);
+       c = i2c_new_device(i2c, &info);
+       if (!c)
+               return NULL;
+       if (c->dev.driver && try_module_get(c->dev.driver->owner))
+               return c;
+       ptx_unregister_subdev(c);
+       return NULL;
+}
+
+void ptx_unregister_fe(struct dvb_frontend *fe)
+{
+       if (!fe)
+               return;
+       if (fe->frontend_priv)
+               dvb_unregister_frontend(fe);
+       ptx_unregister_subdev(fe->tuner_priv);
+       ptx_unregister_subdev(fe->demodulator_priv);
+       kfree(fe);
+}
+
+struct dvb_frontend *ptx_register_fe(struct i2c_adapter *i2c, struct 
dvb_adapter *dvb, const struct ptx_subdev_info *info)
+{
+       struct dvb_frontend *fe = kzalloc(sizeof(struct dvb_frontend), 
GFP_KERNEL);
+
+       if (!fe)
+               return  NULL;
+       fe->demodulator_priv    = ptx_register_subdev(i2c, fe, 
info->demod_addr, info->demod_name);
+       fe->tuner_priv          = ptx_register_subdev(i2c, fe, 
info->tuner_addr, info->tuner_name);
+       if (info->type)
+               fe->ops.delsys[0] = info->type;
+       if (!fe->demodulator_priv || !fe->tuner_priv || (dvb && 
dvb_register_frontend(dvb, fe))) {
+               ptx_unregister_fe(fe);
+               return  NULL;
+       }
+       return fe;
+}
+
+void ptx_unregister_adap(struct ptx_card *card)
+{
+       int             i       = card->adapn - 1;
+       struct ptx_adap *adap   = card->adap + i;
+
+       for (; i >= 0; i--, adap--) {
+               ptx_unregister_fe(adap->fe);
+               if (adap->demux.dmx.close)
+                       adap->demux.dmx.close(&adap->demux.dmx);
+               if (adap->dmxdev.filter)
+                       dvb_dmxdev_release(&adap->dmxdev);
+               if (adap->demux.cnt_storage)
+                       dvb_dmx_release(&adap->demux);
+               if (adap->dvb.name)
+                       dvb_unregister_adapter(&adap->dvb);
+       }
+       i2c_del_adapter(&card->i2c);
+       pci_release_regions(card->pdev);
+       pci_set_drvdata(card->pdev, NULL);
+       pci_disable_device(card->pdev);
+       kfree(card);
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adap_no);
+int ptx_register_adap(struct ptx_card *card, const struct ptx_subdev_info 
*info,
+                       int (*thread)(void *), int (*dma)(struct ptx_adap *, 
bool))
+{
+       struct ptx_adap *adap;
+       u8      i;
+       int     err;
+
+       card->thread    = thread;
+       card->dma       = dma;
+       for (i = 0, adap = card->adap; i < card->adapn; i++, adap++) {
+               struct dvb_adapter      *dvb    = &adap->dvb;
+               struct dvb_demux        *demux  = &adap->demux;
+               struct dmxdev           *dmxdev = &adap->dmxdev;
+
+               if (dvb_register_adapter(dvb, card->name, THIS_MODULE, 
&card->pdev->dev, adap_no) < 0)
+                       return -ENFILE;
+               demux->dmx.capabilities = DMX_TS_FILTERING | 
DMX_SECTION_FILTERING;
+               demux->feednum          = 1;
+               demux->filternum        = 1;
+               demux->start_feed       = ptx_start_feed;
+               demux->stop_feed        = ptx_stop_feed;
+               if (dvb_dmx_init(demux) < 0)
+                       return -ENOMEM;
+               dmxdev->filternum       = 1;
+               dmxdev->demux           = &demux->dmx;
+               err                     = dvb_dmxdev_init(dmxdev, dvb);
+               if (err)
+                       return err;
+               adap->fe                = ptx_register_fe(&adap->card->i2c, 
&adap->dvb, &info[i]);
+               if (!adap->fe)
+                       return -ENOMEM;
+               adap->fe_sleep          = adap->fe->ops.sleep;
+               adap->fe_wakeup         = adap->fe->ops.init;
+               adap->fe->ops.sleep     = ptx_sleep;
+               adap->fe->ops.init      = ptx_wakeup;
+               ptx_sleep(adap->fe);
+       }
+       return 0;
+}
+
+int ptx_abort(struct pci_dev *pdev, void remover(struct pci_dev *), int err, 
char *fmt, ...)
+{
+       va_list ap;
+       char    *s = NULL;
+       int     slen;
+
+       va_start(ap, fmt);
+       slen    = vsnprintf(s, 0, fmt, ap) + 1;
+       s       = kzalloc(slen, GFP_ATOMIC);
+       if (s) {
+               vsnprintf(s, slen, fmt, ap);
+               dev_err(&pdev->dev, "%s", s);
+               kfree(s);
+       }
+       va_end(ap);
+       remover(pdev);
+       return err;
+}
+
+u32 ptx_i2c_func(struct i2c_adapter *i2c)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_NOSTART;
+}
+
+
diff --git a/drivers/media/pci/ptx/ptx_common.h 
b/drivers/media/pci/ptx/ptx_common.h
new file mode 100644
index 0000000..6aeb1b4
--- /dev/null
+++ b/drivers/media/pci/ptx/ptx_common.h
@@ -0,0 +1,74 @@
+/*
+ * Defs & procs for PT3 & PX-Q3PE DVB driver
+ *
+ * Copyright (C) Budi Rachmanto, AreMa Inc. <i...@are.ma>
+ *
+ * This program is distributed in 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.
+ */
+
+#ifndef        PTX_COMMON_H
+#define PTX_COMMON_H
+
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/pci.h>
+#include "dvb_demux.h"
+#include "dvb_frontend.h"
+#include "dmxdev.h"
+
+enum ePTX {
+       PTX_TS_SIZE     = 188,
+       PTX_TS_SYNC     = 0x47,
+       PTX_TS_NOT_SYNC = 0x74,
+};
+
+struct ptx_subdev_info {
+       enum fe_delivery_system type;
+       u8      demod_addr,     *demod_name,
+               tuner_addr,     *tuner_name;
+};
+
+struct ptx_card {
+       struct ptx_adap         *adap;
+       struct mutex            lock;
+       struct i2c_adapter      i2c;
+       struct pci_dev          *pdev;
+       u8      *name,
+               adapn;
+       bool    lnbON;
+       void    *priv,
+               (*lnb)(struct ptx_card *card, bool lnb);
+       int     (*thread)(void *dat),
+               (*dma)(struct ptx_adap *adap, bool ON);
+};
+
+struct ptx_adap {
+       struct ptx_card         *card;
+       bool                    ON;
+       struct dvb_adapter      dvb;
+       struct dvb_demux        demux;
+       struct dmxdev           dmxdev;
+       struct dvb_frontend     *fe;
+       struct task_struct      *kthread;
+       void                    *priv;
+       int     (*fe_sleep)(struct dvb_frontend *),
+               (*fe_wakeup)(struct dvb_frontend *);
+};
+
+struct ptx_card *ptx_alloc(struct pci_dev *pdev, u8 *name, u8 adapn, u32 
sz_card_priv, u32 sz_adap_priv,
+                       void (*lnb)(struct ptx_card *, bool));
+int ptx_sleep(struct dvb_frontend *fe);
+int ptx_wakeup(struct dvb_frontend *fe);
+int ptx_i2c_add_adapter(struct ptx_card *card, const struct i2c_algorithm 
*algo);
+void ptx_unregister_fe(struct dvb_frontend *fe);
+struct dvb_frontend *ptx_register_fe(struct i2c_adapter *i2c, struct 
dvb_adapter *dvb, const struct ptx_subdev_info *info);
+void ptx_unregister_adap(struct ptx_card *card);
+int ptx_register_adap(struct ptx_card *card, const struct ptx_subdev_info 
*info,
+                       int (*thread)(void *), int (*dma)(struct ptx_adap *, 
bool));
+int ptx_abort(struct pci_dev *pdev, void remover(struct pci_dev *), int err, 
char *fmt, ...);
+u32 ptx_i2c_func(struct i2c_adapter *i2c);
+
+#endif
diff --git a/drivers/media/pci/ptx/pxq3pe_pci.c 
b/drivers/media/pci/ptx/pxq3pe_pci.c
new file mode 100644
index 0000000..45590eb
--- /dev/null
+++ b/drivers/media/pci/ptx/pxq3pe_pci.c
@@ -0,0 +1,585 @@
+/*
+       DVB driver for PLEX PX-Q3PE ISDB-S/T PCIE receiver
+
+       Copyright (C) Budi Rachmanto, AreMa Inc. <i...@are.ma>
+
+       Main components:
+       ASIE5606X8      - controller
+       TC90522         - 2ch OFDM ISDB-T + 2ch 8PSK ISDB-S demodulator
+       TDA20142        - ISDB-S tuner
+       NM120           - ISDB-T tuner
+*/
+
+#include <linux/interrupt.h>
+#include "ptx_common.h"
+#include "tc90522.h"
+#include "tda2014x.h"
+#include "nm131.h"
+
+#define MOD_AUTH "Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>"
+MODULE_AUTHOR(MOD_AUTH);
+MODULE_DESCRIPTION("PLEX PX-Q3PE Driver");
+MODULE_LICENSE("GPL");
+
+static char    *auth   = MOD_AUTH;
+static int     ni,
+               nx,
+               idx[8]  = {},
+               xor[4]  = {};
+module_param(auth, charp, 0);
+module_param_array(idx, int, &ni, 0);
+module_param_array(xor, int, &nx, 0);
+
+static struct pci_device_id pxq3pe_id_table[] = {
+       {0x188B, 0x5220, 0x0B06, 0x0002, 0, 0, 0},
+       {}
+};
+MODULE_DEVICE_TABLE(pci, pxq3pe_id_table);
+
+enum ePXQ3PE {
+       PKT_NUM         = 312,
+       PKT_BUFSZ       = PTX_TS_SIZE * PKT_NUM,
+
+       PXQ3PE_MOD_GPIO         = 0,
+       PXQ3PE_MOD_TUNER        = 1,
+       PXQ3PE_MOD_STAT         = 2,
+
+       PXQ3PE_IRQ_STAT         = 0x808,
+       PXQ3PE_IRQ_CLEAR        = 0x80C,
+       PXQ3PE_IRQ_ACTIVE       = 0x814,
+       PXQ3PE_IRQ_DISABLE      = 0x818,
+       PXQ3PE_IRQ_ENABLE       = 0x81C,
+
+       PXQ3PE_I2C_ADR_GPIO     = 0x4A,
+       PXQ3PE_I2C_CTL_STAT     = 0x940,
+       PXQ3PE_I2C_ADR          = 0x944,
+       PXQ3PE_I2C_SW_CTL       = 0x948,
+       PXQ3PE_I2C_FIFO_STAT    = 0x950,
+       PXQ3PE_I2C_FIFO_DATA    = 0x960,
+
+       PXQ3PE_DMA_OFFSET_PORT  = 0x140,
+       PXQ3PE_DMA_TSMODE       = 0xA00,
+       PXQ3PE_DMA_MGMT         = 0xAE0,
+       PXQ3PE_DMA_OFFSET_CH    = 0x10,
+       PXQ3PE_DMA_ADR_LO       = 0xAC0,
+       PXQ3PE_DMA_ADR_HI       = 0xAC4,
+       PXQ3PE_DMA_XFR_STAT     = 0xAC8,
+       PXQ3PE_DMA_CTL          = 0xACC,
+
+       PXQ3PE_MAX_LOOP         = 1000,
+};
+
+struct pxq3pe_card {
+       void __iomem            *bar;
+       struct {
+               dma_addr_t      adr;
+               u8              *dat;
+               u32             sz;
+               bool            ON[2];
+       } dma;
+};
+
+struct pxq3pe_adap {
+       u8      tBuf[PKT_BUFSZ],
+               *sBuf;
+       u32     tBufIdx,
+               sBufSize,
+               sBufStart,
+               sBufStop,
+               sBufByteCnt;
+};
+
+bool pxq3pe_i2c_clean(struct ptx_card *card)
+{
+       struct pxq3pe_card      *c      = card->priv;
+       void __iomem            *bar    = c->bar;
+
+       if ((readl(bar + PXQ3PE_I2C_FIFO_STAT) & 0x1F) != 0x10 || readl(bar + 
PXQ3PE_I2C_FIFO_STAT) & 0x1F00) {
+               u32 stat = readl(bar + PXQ3PE_I2C_SW_CTL) | 0x20;
+
+               writel(stat, bar + PXQ3PE_I2C_SW_CTL);
+               writel(stat & 0xFFFFFFDF, bar + PXQ3PE_I2C_SW_CTL);
+               if ((readl(bar + PXQ3PE_I2C_FIFO_STAT) & 0x1F) != 0x10) {
+                       dev_err(&card->pdev->dev, "%s FIFO error", __func__);
+                       return false;
+               }
+       }
+       writel(0, bar + PXQ3PE_I2C_CTL_STAT);
+       return true;
+}
+
+bool pxq3pe_w(struct ptx_card *card, u8 slvadr, u8 regadr, u8 *wdat, u8 
bytelen, u8 mode)
+{
+       struct pxq3pe_card      *c      = card->priv;
+       void __iomem            *bar    = c->bar;
+       int     i,
+               j,
+               k;
+       u8      i2cCtlByte,
+               i2cFifoWSz;
+
+       if (!pxq3pe_i2c_clean(card))
+               return false;
+       switch (mode) {
+       case PXQ3PE_MOD_GPIO:
+               i2cCtlByte = 0xC0;
+               break;
+       case PXQ3PE_MOD_TUNER:
+               regadr = 0;
+               i2cCtlByte = 0x80;
+               break;
+       case PXQ3PE_MOD_STAT:
+               regadr = 0;
+               i2cCtlByte = 0x84;
+               break;
+       default:
+               return false;
+       }
+       writel((slvadr << 8) + regadr, bar + PXQ3PE_I2C_ADR);
+       for (i = 0; i < 16 && i < bytelen; i += 4) {
+               udelay(1000);
+               writel(*((u32 *)(wdat + i)), bar + PXQ3PE_I2C_FIFO_DATA);
+       }
+       writew((bytelen << 8) + i2cCtlByte, bar + PXQ3PE_I2C_CTL_STAT);
+       for (j = 0; j < PXQ3PE_MAX_LOOP; j++) {
+               if (i < bytelen) {
+                       i2cFifoWSz = readb(bar + PXQ3PE_I2C_FIFO_STAT) & 0x1F;
+                       for (k = 0; bytelen > 16 && k < PXQ3PE_MAX_LOOP && 
i2cFifoWSz < bytelen - 16; k++) {
+                               i2cFifoWSz = readb(bar + PXQ3PE_I2C_FIFO_STAT) 
& 0x1F;
+                               udelay(1000);
+                       }
+                       if (i2cFifoWSz & 3)
+                               continue;
+                       if (i2cFifoWSz) {
+                               for (k = i; k < bytelen && k - i < i2cFifoWSz; 
k += 4)
+                                       writel(*((u32 *)(wdat + k)), bar + 
PXQ3PE_I2C_FIFO_DATA);
+                               i = k;
+                       }
+               }
+               udelay(10);
+               if (readl(bar + PXQ3PE_I2C_CTL_STAT) & 0x400000)
+                       break;
+       }
+       return j < PXQ3PE_MAX_LOOP ? !(readl(bar + PXQ3PE_I2C_CTL_STAT) & 
0x280000) : false;
+}
+
+bool pxq3pe_r(struct ptx_card *card, u8 slvadr, u8 regadr, u8 *rdat, u8 
bytelen, u8 mode)
+{
+       struct pxq3pe_card      *c      = card->priv;
+       void __iomem            *bar    = c->bar;
+       u8      i2cCtlByte,
+               i2cStat,
+               i2cFifoRSz,
+               i2cByteCnt;
+       int     i               = 0,
+               j,
+               idx;
+       bool    ret             = false;
+
+       if (!pxq3pe_i2c_clean(card))
+               return false;
+       switch (mode) {
+       case PXQ3PE_MOD_GPIO:
+               i2cCtlByte = 0xE0;
+               break;
+       case PXQ3PE_MOD_TUNER:
+               regadr = 0;
+               i2cCtlByte = 0xA0;
+               break;
+       default:
+               return false;
+       }
+       writel((slvadr << 8) + regadr, bar + PXQ3PE_I2C_ADR);
+       writew(i2cCtlByte + (bytelen << 8), bar + PXQ3PE_I2C_CTL_STAT);
+       i2cByteCnt = bytelen;
+       j = 0;
+       while (j < PXQ3PE_MAX_LOOP) {
+               udelay(10);
+               i2cStat = (readl(bar + PXQ3PE_I2C_CTL_STAT) & 0xFF0000) >> 16;
+               if (i2cStat & 0x80) {
+                       if (i2cStat & 0x28)
+                               break;
+                       ret = true;
+               }
+               i2cFifoRSz = (readl(bar + PXQ3PE_I2C_FIFO_STAT) & 0x1F00) >> 8;
+               if (i2cFifoRSz & 3) {
+                       ++j;
+                       continue;
+               }
+               for (idx = i; i2cFifoRSz && idx < i2cByteCnt && idx - i < 
i2cFifoRSz; idx += 4)
+                       *(u32 *)(rdat + idx) = readl(bar + 
PXQ3PE_I2C_FIFO_DATA);
+               i = idx;
+               if (i < bytelen) {
+                       if (i2cFifoRSz)
+                               i2cByteCnt -= i2cFifoRSz;
+                       else
+                               ++j;
+                       continue;
+               }
+               i2cStat = (readl(bar + PXQ3PE_I2C_CTL_STAT) & 0xFF0000) >> 16;
+               if (i2cStat & 0x80) {
+                       if (i2cStat & 0x28)
+                               break;
+                       ret = true;
+                       break;
+               }
+               ++j;
+       }
+       return !(readl(bar + PXQ3PE_I2C_FIFO_STAT) & 0x1F00) && ret;
+}
+
+int pxq3pe_xfr(struct i2c_adapter *i2c, struct i2c_msg *msg, int sz)
+{
+       struct ptx_card *card   = i2c_get_adapdata(i2c);
+       u8              i;
+       bool            ret     = true;
+
+       if (!i2c || !card || !msg)
+               return -EINVAL;
+       for (i = 0; i < sz && ret; i++, msg++) {
+               u8      slvadr  = msg->addr,
+                       regadr  = msg->len ? *msg->buf : 0,
+                       mode    = slvadr == PXQ3PE_I2C_ADR_GPIO ? 
PXQ3PE_MOD_GPIO
+                               : sz > 1 && i == sz - 2         ? 
PXQ3PE_MOD_STAT
+                               : PXQ3PE_MOD_TUNER;
+
+               mutex_lock(&card->lock);
+               if (msg->flags & I2C_M_RD) {
+                       u8 buf[sz];
+
+                       ret = pxq3pe_r(card, slvadr, regadr, buf, msg->len, 
mode);
+                       memcpy(msg->buf, buf, msg->len);
+               } else
+                       ret = pxq3pe_w(card, slvadr, regadr, msg->buf, 
msg->len, mode);
+               mutex_unlock(&card->lock);
+       }
+       return i;
+}
+
+bool pxq3pe_w_gpio2(struct ptx_card *card, u8 dat, u8 mask)
+{
+       u8 val;
+
+       return  pxq3pe_r(card, PXQ3PE_I2C_ADR_GPIO, 0xB, &val, 1, 
PXQ3PE_MOD_GPIO)      &&
+               (val = (mask & dat) | (val & ~mask), pxq3pe_w(card, 
PXQ3PE_I2C_ADR_GPIO, 0xB, &val, 1, PXQ3PE_MOD_GPIO));
+}
+
+void pxq3pe_w_gpio1(struct ptx_card *card, u8 dat, u8 mask)
+{
+       struct pxq3pe_card *c = card->priv;
+
+       mask <<= 3;
+       writeb((readb(c->bar + 0x890) & ~mask) | ((dat << 3) & mask), c->bar + 
0x890);
+}
+
+void pxq3pe_w_gpio0(struct ptx_card *card, u8 dat, u8 mask)
+{
+       struct pxq3pe_card *c = card->priv;
+
+       writeb((-(mask & 1) & 4 & -(dat & 1)) | (readb(c->bar + 0x890) & 
~(-(mask & 1) & 4)), c->bar + 0x890);
+       writeb((mask & dat) | (readb(c->bar + 0x894) & ~mask), c->bar + 0x894);
+}
+
+void pxq3pe_power(struct ptx_card *card, bool ON)
+{
+       if (ON) {
+               pxq3pe_w_gpio0(card, 1, 1);
+               pxq3pe_w_gpio0(card, 0, 1);
+               pxq3pe_w_gpio0(card, 1, 1);
+               pxq3pe_w_gpio1(card, 1, 1);
+               pxq3pe_w_gpio1(card, 0, 1);
+               pxq3pe_w_gpio2(card, 2, 2);
+               pxq3pe_w_gpio2(card, 0, 2);
+               pxq3pe_w_gpio2(card, 2, 2);
+               pxq3pe_w_gpio2(card, 4, 4);
+               pxq3pe_w_gpio2(card, 0, 4);
+               pxq3pe_w_gpio2(card, 4, 4);
+       } else {
+               pxq3pe_w_gpio0(card, 0, 1);
+               pxq3pe_w_gpio0(card, 1, 1);
+               pxq3pe_w_gpio1(card, 1, 1);
+       }
+}
+
+irqreturn_t pxq3pe_irq(int irq, void *ctx)
+{
+       struct ptx_card         *card   = ctx;
+       struct pxq3pe_card      *c      = card->priv;
+       void __iomem            *bar    = c->bar;
+       u32     dmamgmt,
+               i,
+               irqstat = readl(bar + PXQ3PE_IRQ_STAT);
+       bool    ch      = irqstat & 0b0101 ? 0 : 1,
+               port    = irqstat & 0b0011 ? 0 : 1;
+       u8      *tbuf   = c->dma.dat + PKT_BUFSZ * (port * 2 + ch);
+
+       void pxq3pe_dma_put_stream(struct pxq3pe_adap *p)
+       {
+               u8      *src    = p->tBuf;
+               u32     len     = p->tBufIdx,
+                       savesz  = len <= p->sBufSize - p->sBufStop ? len : 
p->sBufSize - p->sBufStop,
+                       remain  = len - savesz;
+
+               memcpy(&p->sBuf[p->sBufStop], src, savesz);
+               if (remain)
+                       memcpy(p->sBuf, &src[savesz], remain);
+               p->sBufStop = (p->sBufStop + len) % p->sBufSize;
+               if (p->sBufByteCnt == p->sBufSize)
+                       p->sBufStart = p->sBufStop;
+               else {
+                       if (p->sBufSize >= p->sBufByteCnt + len)
+                               p->sBufByteCnt += len;
+                       else {
+                               p->sBufStart = p->sBufStop;
+                               p->sBufByteCnt = p->sBufSize;
+                       }
+               }
+       }
+
+       if (!(irqstat & 0b1111))
+               return IRQ_HANDLED;
+       writel(irqstat, bar + PXQ3PE_IRQ_CLEAR);
+       dmamgmt = readl(bar + PXQ3PE_DMA_OFFSET_PORT * port + PXQ3PE_DMA_MGMT);
+       if ((readl(bar + PXQ3PE_DMA_OFFSET_PORT * port + PXQ3PE_DMA_OFFSET_CH * 
ch + PXQ3PE_DMA_XFR_STAT) & 0x3FFFFF) == PKT_BUFSZ)
+               for (i = 0; i < PKT_BUFSZ; i += PTX_TS_SIZE) {
+                       u8 idx = !port * 4 + (tbuf[i] == 0xC7 ? 0 : tbuf[i] == 
0x47 ?
+                                       1 : tbuf[i] == 0x07 ? 2 : tbuf[i] == 
0x87 ? 3 : card->adapn);
+                       struct ptx_adap         *adap   = &card->adap[idx];
+                       struct pxq3pe_adap      *p      = adap->priv;
+
+                       if (idx < card->adapn && adap->ON) {
+                               tbuf[i] = PTX_TS_SYNC;
+                               memcpy(&p->tBuf[p->tBufIdx], &tbuf[i], 
PTX_TS_SIZE);
+                               p->tBufIdx += PTX_TS_SIZE;
+                               if (p->tBufIdx >= PKT_BUFSZ) {
+                                       pxq3pe_dma_put_stream(p);
+                                       p->tBufIdx = 0;
+                               }
+                       }
+               }
+       if (c->dma.ON[port])
+               writel(dmamgmt | (2 << (ch * 16)), bar + PXQ3PE_DMA_OFFSET_PORT 
* port + PXQ3PE_DMA_MGMT);
+       return IRQ_HANDLED;
+}
+
+int pxq3pe_thread(void *dat)
+{
+       struct ptx_adap         *adap   = dat;
+       struct pxq3pe_adap      *p      = adap->priv;
+
+       set_freezable();
+       while (!kthread_should_stop()) {
+               u8      *rbuf   = &p->sBuf[p->sBufStart];
+               int     i       = 0,
+                       j       = 0,
+                       k,
+                       sz      = p->sBufSize - p->sBufStart;
+
+               try_to_freeze();
+               if (!p->sBufByteCnt) {
+                       msleep_interruptible(0);
+                       continue;
+               }
+               if (sz > p->sBufByteCnt)
+                       sz = p->sBufByteCnt;
+               while (j < sz / PTX_TS_SIZE) {
+                       j++;
+                       i += 4;
+                       while (i < j * PTX_TS_SIZE)
+                               for (k = 0; k < 8; k++, i++)
+                                       rbuf[i] ^= xor[idx[k]];
+               }
+               dvb_dmx_swfilter(&adap->demux, rbuf, sz);
+               p->sBufStart    = (p->sBufStart + sz) % p->sBufSize;
+               p->sBufByteCnt -= sz;
+       }
+       return 0;
+}
+
+int pxq3pe_dma(struct ptx_adap *adap, bool ON)
+{
+       struct ptx_card         *card   = adap->card;
+       struct pxq3pe_card      *c      = card->priv;
+       struct pxq3pe_adap      *p      = adap->priv;
+       struct i2c_client       *d      = adap->fe->demodulator_priv;
+       u8                      idx     = (d->addr / 2) & (card->adapn - 1),
+                               i;
+       bool                    port    = !(idx & 4);
+       u32                     val     = 0b0011 << (port * 2);
+
+       if (!ON) {
+               for (i = 0; i < card->adapn; i++)
+                       if (!c->dma.ON[port] || (idx != i && (i & 4) == (idx & 
4) && c->dma.ON[port]))
+                               return 0;
+
+               i = readb(c->bar + PXQ3PE_DMA_OFFSET_PORT * port + 
PXQ3PE_DMA_MGMT);
+               if ((i & 0b1100) == 4)
+                       writeb(i & 0xFD, c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_MGMT);
+               writeb(0b0011 << (port * 2), c->bar + PXQ3PE_IRQ_DISABLE);
+               c->dma.ON[port] = false;
+               return 0;
+       }
+
+       p->sBufByteCnt  = 0;
+       p->sBufStop     = 0;
+       p->sBufStart    = 0;
+       if (c->dma.ON[port])
+               return 0;
+
+       /* SetTSMode */
+       i = readb(c->bar + PXQ3PE_DMA_OFFSET_PORT * port + PXQ3PE_DMA_TSMODE);
+       if ((i & 0x80) == 0)
+               writeb(i | 0x80, c->bar + PXQ3PE_DMA_OFFSET_PORT * port + 
PXQ3PE_DMA_TSMODE);
+
+       /* irq_enable */
+       writel(val, c->bar + PXQ3PE_IRQ_ENABLE);
+       if (val != (readl(c->bar + PXQ3PE_IRQ_ACTIVE) & val))
+               return -EIO;
+
+       /* cfg_dma */
+       for (i = 0; i < 2; i++) {
+               val             = readl(c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_MGMT);
+               writel(c->dma.adr + PKT_BUFSZ * (port * 2 + i),
+                                       c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_OFFSET_CH * i + PXQ3PE_DMA_ADR_LO);
+               writel(0,               c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_OFFSET_CH * i + PXQ3PE_DMA_ADR_HI);
+               writel(0x11C0E520,      c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_OFFSET_CH * i + PXQ3PE_DMA_CTL);
+               writel(val | 3 << (i * 16),
+                                       c->bar + PXQ3PE_DMA_OFFSET_PORT * port 
+ PXQ3PE_DMA_MGMT);
+       }
+       c->dma.ON[port] = true;
+       return 0;
+}
+
+void pxq3pe_lnb(struct ptx_card *card, bool lnb)
+{
+       pxq3pe_w_gpio2(card, lnb ? 0x20 : 0, 0x20);
+}
+
+void pxq3pe_remove(struct pci_dev *pdev)
+{
+       struct ptx_card         *card   = pci_get_drvdata(pdev);
+       struct ptx_adap         *adap;
+       struct pxq3pe_card      *c;
+       u8      regctl = 0,
+               i;
+
+       if (!card)
+               return;
+       c       = card->priv;
+       for (i = 0, adap = card->adap; adap->fe && i < card->adapn; i++, 
adap++) {
+               pxq3pe_dma(adap, false);
+               ptx_sleep(adap->fe);
+       }
+       pxq3pe_w(card, PXQ3PE_I2C_ADR_GPIO, 0x80, &regctl, 1, PXQ3PE_MOD_GPIO);
+       pxq3pe_power(card, false);
+
+       /* dma_hw_unmap */
+       free_irq(pdev->irq, card);
+       if (c->dma.dat)
+               pci_free_consistent(card->pdev, c->dma.sz, c->dma.dat, 
c->dma.adr);
+       for (i = 0; i < card->adapn; i++) {
+               struct ptx_adap         *adap   = &card->adap[i];
+               struct pxq3pe_adap      *p      = adap->priv;
+
+               vfree(p->sBuf);
+       }
+       if (c->bar)
+               pci_iounmap(pdev, c->bar);
+       ptx_unregister_adap(card);
+}
+
+static const struct i2c_algorithm pxq3pe_algo = {
+       .functionality  = ptx_i2c_func,
+       .master_xfer    = pxq3pe_xfr,
+};
+
+static int pxq3pe_probe(struct pci_dev *pdev, const struct pci_device_id 
*pci_id)
+{
+       struct ptx_subdev_info  pxq3pe_subdev_info[] = {
+               {SYS_ISDBT, 0x20, TC90522_MODNAME, 0x10, NM131_MODNAME},
+               {SYS_ISDBS, 0x22, TC90522_MODNAME, 0x11, TDA2014X_MODNAME},
+               {SYS_ISDBT, 0x24, TC90522_MODNAME, 0x12, NM131_MODNAME},
+               {SYS_ISDBS, 0x26, TC90522_MODNAME, 0x13, TDA2014X_MODNAME},
+               {SYS_ISDBT, 0x28, TC90522_MODNAME, 0x14, NM131_MODNAME},
+               {SYS_ISDBS, 0x2A, TC90522_MODNAME, 0x15, TDA2014X_MODNAME},
+               {SYS_ISDBT, 0x2C, TC90522_MODNAME, 0x16, NM131_MODNAME},
+               {SYS_ISDBS, 0x2E, TC90522_MODNAME, 0x17, TDA2014X_MODNAME},
+       };
+       struct ptx_card         *card   = ptx_alloc(pdev, KBUILD_MODNAME, 
ARRAY_SIZE(pxq3pe_subdev_info),
+                                               sizeof(struct pxq3pe_card), 
sizeof(struct pxq3pe_adap), pxq3pe_lnb);
+       struct pxq3pe_card      *c;
+       u8      regctl  = 0xA0,
+               i;
+       u16     cfg;
+       int     err     = !card || pci_read_config_word(pdev, PCI_COMMAND, 
&cfg);
+
+       if (err)
+               return ptx_abort(pdev, pxq3pe_remove, err, "Memory/PCI error, 
card=%p", card);
+       c       = card->priv;
+       if (!(cfg & PCI_COMMAND_MASTER)) {
+               pci_set_master(pdev);
+               pci_read_config_word(pdev, PCI_COMMAND, &cfg);
+               if (!(cfg & PCI_COMMAND_MASTER))
+                       return ptx_abort(pdev, pxq3pe_remove, -EIO, "Bus 
Mastering is disabled");
+       }
+       c->bar  = pci_iomap(pdev, 0, 0);
+       if (!c->bar)
+               return ptx_abort(pdev, pxq3pe_remove, -EIO, "I/O map failed");
+       if (ptx_i2c_add_adapter(card, &pxq3pe_algo))
+               return ptx_abort(pdev, pxq3pe_remove, -EIO, "Cannot add I2C");
+
+       for (i = 0; i < card->adapn; i++) {
+               struct ptx_adap         *adap   = &card->adap[i];
+               struct pxq3pe_adap      *p      = adap->priv;
+
+               p->sBufSize     = PTX_TS_SIZE * 100 << 9;
+               p->sBuf         = vzalloc(p->sBufSize);
+               if (!p->sBuf)
+                       return ptx_abort(pdev, pxq3pe_remove, -ENOMEM, "No 
memory for stream buffer");
+       }
+
+       /* dma_map */
+       if (request_irq(pdev->irq, pxq3pe_irq, IRQF_SHARED, KBUILD_MODNAME, 
card))
+               return ptx_abort(pdev, pxq3pe_remove, -EIO, "IRQ failed");
+       c->dma.sz       = PKT_BUFSZ * 4;
+       c->dma.dat      = pci_alloc_consistent(card->pdev, c->dma.sz, 
&c->dma.adr);
+       if (!c->dma.dat)
+               return ptx_abort(pdev, pxq3pe_remove, -EIO, "DMA mapping 
failed");
+
+       /* hw_init */
+       writeb(readb(c->bar + 0x880) & 0xC0, c->bar + 0x880);
+       writel(0x3200C8, c->bar + 0x904);
+       writel(0x90,     c->bar + 0x900);
+       writel(0x10000,  c->bar + 0x880);
+       writel(0x0080,   c->bar + PXQ3PE_DMA_TSMODE);                           
/* port 0 */
+       writel(0x0080,   c->bar + PXQ3PE_DMA_TSMODE + PXQ3PE_DMA_OFFSET_PORT);  
/* port 1 */
+       writel(0x0000,   c->bar + 0x888);
+       writel(0x00CF,   c->bar + 0x894);
+       writel(0x8000,   c->bar + 0x88C);
+       writel(0x1004,   c->bar + 0x890);
+       writel(0x0090,   c->bar + 0x900);
+       writel(0x3200C8, c->bar + 0x904);
+       pxq3pe_w_gpio0(card, 8, 0xFF);
+       pxq3pe_w_gpio1(card, 0, 2);
+       pxq3pe_w_gpio1(card, 1, 1);
+       pxq3pe_w_gpio0(card, 1, 1);
+       pxq3pe_w_gpio0(card, 0, 1);
+       pxq3pe_w_gpio0(card, 1, 1);
+       for (i = 0; i < 16; i++)
+               if (!pxq3pe_w(card, PXQ3PE_I2C_ADR_GPIO, 0x10 + i, auth + i, 1, 
PXQ3PE_MOD_GPIO))
+                       break;
+       if (i < 16 || !pxq3pe_w(card, PXQ3PE_I2C_ADR_GPIO, 5, &regctl, 1, 
PXQ3PE_MOD_GPIO))
+               return ptx_abort(pdev, pxq3pe_remove, -EIO, "hw_init failed 
i=%d", i);
+       pxq3pe_power(card, true);
+       err = ptx_register_adap(card, pxq3pe_subdev_info, pxq3pe_thread, 
pxq3pe_dma);
+       return err ? ptx_abort(pdev, pxq3pe_remove, err, "Unable to register 
DVB adapter & frontend") : 0;
+}
+
+static struct pci_driver pxq3pe_driver = {
+       .name           = KBUILD_MODNAME,
+       .id_table       = pxq3pe_id_table,
+       .probe          = pxq3pe_probe,
+       .remove         = pxq3pe_remove,
+};
+module_pci_driver(pxq3pe_driver);
+
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to