#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/sched.h>

#include "dvb_ci_phys.h"

#define __DVB_CI_PHYS_DEBUG 0

#define dprintk if(__DVB_CI_PHYS_DEBUG) printk

#define CTRLIF_DATA      0
#define CTRLIF_COMMAND   1
#define CTRLIF_STATUS    1
#define CTRLIF_SIZE_LOW  2
#define CTRLIF_SIZE_HIGH 3

#define CMDREG_HC        1  /* Host control */
#define CMDREG_SW        2  /* Size write */
#define CMDREG_SR        4  /* Size read */
#define CMDREG_RS        8  /* Reset interface */
#define CMDREG_FRIE   0x40  /* Enable FR interrupt */
#define CMDREG_DAIE   0x80  /* Enable DA interrupt */
#define IRQEN (CMDREG_DAIE)

#define STATUSREG_RE     1  /* read error */
#define STATUSREG_WE     2  /* write error */
#define STATUSREG_FR  0x40  /* module free */
#define STATUSREG_DA  0x80  /* data available */
#define STATUSREG_TXERR (STATUSREG_RE|STATUSREG_WE) /* general transfer error */

#define __GOTO(x, y) \
        ret = y; \
        goto x;

struct dvb_ci_slot
{
    dvb_ci_slot_state state;
    u16 buf_size;
    u8 buf[256];
    unsigned int radr;
    unsigned int cor;
    unsigned int init_err;
};

struct dvb_ci_private
{
    struct dvb_ci * cidev;
    struct dvb_device * dvbdev;
    int slot_count;
    struct dvb_ci_slot * slots;
};

/*-------------------------------------------------------------------------
 * private functions
 *-----------------------------------------------------------------------*/
static u8 *
__dvb_ci_phys_read_subtuple(u8 * tuple,
                            int offset,
                            u8 * code,
                            u8 * link)
{
    *code = tuple[offset];
    *link = tuple[offset + 1];

    return tuple + offset + 2;
}

static u8
__dvb_ci_phys_read_tuple(struct dvb_ci * ci,
                         int slot,
                         int offset,
                         u8 * length,
                         u8 * buf)
{
    u8 type;
    u8 i;

    type = ci->attr_read(slot, offset);
    if(type == 0xff){
        // end of tuple chain
        return type;
    }
    offset += 2;

    *length = ci->attr_read(slot, offset);
    offset += 2;

    for(i=0; i<*length; i++){
        buf[i] = ci->attr_read(slot, offset + i * 2);
    }

    return type;
}

static int
__tpce_parse_pd(u8 * buf,
                int length)
{
    int off = 0;
    u8 sel;
    int i;

    sel = buf[off ++];
    for(i=0; i<8; i++){
        if(sel & (1 << i)){
            if(i == 0){
                // nominal voltage
                if(buf[off] == 0x55 ||
                   ((buf[off] == 0xd5) && ((buf[off+1]&0x7f) == 0))){
                    // 5V
                    dprintk("\t\tnominal voltage 5V\n");
                }
                else{
                    dprintk("\t\tnominal voltage byte: 0x%x\n",buf[off]);
                }
            }
            while(off<length && ((buf[off] & 0x80) != 0)) off++;
            off ++;
        }
    }
    return off;
}

static int
__tpce_parse_td(u8 * buf,
                int length)
{
    int off = 0;
    u8 sel;
    static const u8 mask[3] = {0x03, 0x1c, 0xe0};
    int i;

    sel = buf[off];
    for(i=0; i<sizeof(mask); i++){
        if(sel & mask[i]){
            while(off<length && ((buf[off] & 0x80) != 0)) off++;
            off ++;
        }
    }

    return off;
}

static int
__dvb_ci_phys_check_cis(struct dvb_ci_private * priv,
                        int slot)
{
    struct dvb_ci * ci = priv->cidev;
    int offset;
    u8 type;
    u8 length;
    u8 buf[257];
    u8 rasz;
    u8 rmsz;
    u8 fs;
    u8 ifns;
    int ce_skip;
    int ce_offset;
    u8 intf;
    int stop;
    int i;

    int stl_offset;
    u8 stl_code;
    u8 stl_link;
    u8 * stl;

    dprintk("CIS checking...\n");

    // CISTPL_DEVICE_OA
    offset = 0;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    if(type != 0x1d){
        dprintk("\tERR: 0x1d wanted, but got %x\n", type);
        return -EINVAL;
    }
    dprintk("\tCIS: CISTPL_DEVICE_OA length=%d\n", length);

    // CISTPL_DEVICE_OC
    offset += 4 + length * 2;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    if(type != 0x1c){
        dprintk("\tERR: 0x1c wanted, but got %x\n", type);
        return -EINVAL;
    }
    dprintk("\tCIS: CISTPL_DEVICE_OC length=%d\n", length);

    // CISTPL_VERS_1: TPLLV1_MAJOR = 05h; TPLLV1_MINOR = 00h
    offset += 4 + length * 2;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    if(type != 0x15){
        dprintk("\tERR: 0x15 wanted, but got %x\n", type);
        return -EINVAL;
    }
    if(buf[0] != 0x05 || buf[1] != 0x00){
        dprintk("\tERR: error MAJOR/MINOR number: %x/%x\n",
                buf[0], buf[1]);
        return -EINVAL;
    }
    dprintk("\tCIS: CISTPL_VERS_1: MAJOR %x MINOR %x\n",
            buf[0], buf[1]);

    // CISTPL_MANFID
    offset += 4 + length * 2;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    if(type != 0x20){
        dprintk("\tERR: 0x20 wanted, but got %x\n", type);
        return -EINVAL;
    }
    dprintk("\tCIS: CISTPL_MANFID\n");

    // CISTPL_CONFIG
    offset += 4 + length * 2;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    if(type != 0x1a){
        dprintk("\tERR: 0x1a wanted, but got %x\n", type);
        return -EINVAL;
    }
    dprintk("\tCIS: CISTPL_CONFIG\n");

    rasz = (buf[0] & 0x3) + 1;
    priv->slots[slot].radr = 0;
    for(i=0; i<rasz; i++){
        priv->slots[slot].radr |= (buf[2 + i] << (i * 8));
    }
    rmsz = ((buf[0] >> 2) & 0xf) + 1;
    dprintk("\t\trasz %x rmsz %x\n", rasz, rmsz);
    dprintk("\t\tRADR %x\n", priv->slots[slot].radr);

    stop = 0;
    stl_offset  = 2 + rasz + rmsz;
    stl = __dvb_ci_phys_read_subtuple(buf, stl_offset, &stl_code, &stl_link);
    while(stl_code != 0xff){
        switch(stl_code){
        case 0xc0: // CIF
            if(stop){
                // CIF already got, skip it
                break;
            }
            ifns = ((stl[0] >> 6) & 0x3) + 1;
            if(strncmp((stl + ifns), "DVB_CI_V1.00", 12)){
                dprintk("\tERR: unknown CIF, DVB_CI_V1.00 wanted\n");
                return -EINVAL;
            }
            dprintk("\t\tCIF: DVB_CI_V1.00\n");
            stop = 1;
            break;
        }
        stl_offset += 2 + stl_link;
        stl = __dvb_ci_phys_read_subtuple(buf, stl_offset, &stl_code, &stl_link);
    }
    if(stop != 1){
        // CIF not found
        dprintk("\tERR: CIF not found\n");
        return -EINVAL;
    }

    // rest tuples
    stop = 0;
    offset += 4 + length * 2;
    type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    intf = 0xff;
    while(type != 0xff){
        switch(type){
        case 0x1b:
            // CISTPL_CFTABLE_ENTRY
            dprintk("\tCIS: CISTPL_CFTABLE_ENTRY\n");
            dprintk("\t\tgot COR %x\n", buf[0] & 0x3f);
            // -- TPCE_INDX
            switch(buf[0] & 0xc0){
            case 0x00:
                // default 0, index 0
                buf[1] = intf;
                break;
            case 0x40:
                // default 1, index 0
                buf[1] = 0xff;
                intf = 0xff;
                break;
            case 0x80:
                // default 0, index 1
                break;
            case 0xc0:
                // default 1, index 1
                intf = buf[1];
                break;
            }
            dprintk("\t\tdefault %d interface %d\n", 
                    (buf[0] >> 6) & 0x01,
                    (buf[0] >> 7) & 0x01);

            // -- TPCE_IF
            if((buf[1] & 0x0f) != 0x04){
                // interface must be customer inferface 0
                dprintk("\tWARN: interface must be 0x04(customer 0), skip tuple\n");
                break;
            }
            fs = buf[2];
            if((fs & 0x08) == 0x00){
                // TPCE_IO must be set
                dprintk("\tWARN: TPCE_IO must be set in FS(%x), skip tuple\n", fs);
                break;
            }
            if((fs & 0x03) == 0x00){
                // TPCE_PD must be set
                dprintk("\tWARN: TPCE_PD must be set in FS(%x), skip tuple\n", fs);
                break;
            }

            // -- TPCE_PD
            ce_offset = 3;
            if((fs & 0x03)){
                // Vcc
                ce_skip = __tpce_parse_pd(buf + ce_offset, length);
                if(ce_skip < 0){
                    dprintk("\tERR: error parsing TPCE_PD(Vcc)\n");
                    return -EINVAL;
                }
                dprintk("\t\tTPCE_PD Vcc\n");
                ce_offset += ce_skip;
            }
            if(fs & 0x02){
                // Vpp
                ce_skip = __tpce_parse_pd(buf + ce_offset, length);
                if(ce_skip < 0){
                    dprintk("\tERR: error parsing TPCE_PD(Vpp)\n");
                    return -EINVAL;
                }
                dprintk("\t\tTPCE_PD Vpp\n");
                ce_offset += ce_skip;
            }
            // -- TPCE_TD
            if(fs & 0x04){
                ce_skip = __tpce_parse_td(buf + ce_offset, length);
                if(ce_skip < 0){
                    dprintk("\tERR: error parsing TPCE_TD\n");
                    return -EINVAL;
                }
                dprintk("\t\tTPCE_TD\n");
                ce_offset += ce_skip;
            }
            // -- TPCE_IO
            if(buf[ce_offset] != 0x22){
                dprintk("\tWARN: TPCE_IO must be 0x22, skip tuple\n");
                break;
            }
            dprintk("\t\tTPCE_IO\n");

            ce_offset ++;
            // -- STCE_EV
            // -- STCE_PD
            // These two sub-tuple is not so-important
            stl_offset  = ce_offset;
            stl = __dvb_ci_phys_read_subtuple(buf, stl_offset, &stl_code, &stl_link);
            while(stl_code != 0xff && stop < 2){
                switch(stl_code){
                case 0xc0: // STCE_EV
                    if(strncmp((stl), "DVB_HOST", 8)){
                        dprintk("\tWARN: DVB_HOST wanted\n");
                        //return -EINVAL;
                    }
                    dprintk("\t\tSTCE_EV: DVB_HOST\n");
                    stop ++;
                    break;
                case 0xc1: // STCE_PD
                    if(strncmp((stl), "DVB_CI_MODULE", 13)){
                        dprintk("WARN: DVB_CI_MODULE wanted\n");
                        //return -EINVAL;
                    }
                    dprintk("\t\tSTCE_PD: DVB_CI_MODULE\n");
                    stop ++;
                    break;
                }
                stl_offset += 2 + stl_link;
                stl = __dvb_ci_phys_read_subtuple(buf, stl_offset, &stl_code, &stl_link);
            }
            // save cor
            priv->slots[slot].cor = (buf[0] & 0x3f);
            if(stop == 2){
                // for the tuple we wanted, save it's cor for writing
                priv->slots[slot].cor = (buf[0] & 0x3f);
                dprintk("\t\tsave COR: %x\n", priv->slots[slot].cor);
            }

            break;
        default:
            dprintk("\tWARN: unknown tuple %x\n", type);
            break;
        }

        offset += 4 + length * 2;
        type = __dvb_ci_phys_read_tuple(ci, slot, offset, &length, buf);
    }

    if(stop != 2){
        // subtuple not found
        dprintk("\tWARN: subtuple not found\n");
        //return -EINVAL;
    }

    dprintk("CIS: ----------OK-----------\n");
    return 0;
}

static int
__dvb_ci_phys_write_cor(struct dvb_ci_private * priv,
                        int slot)
{
    struct dvb_ci * ci = priv->cidev;
    u8 check;

    dprintk("%s\n", __FUNCTION__);

    do{
        ci->attr_write(slot, priv->slots[slot].radr, priv->slots[slot].cor);
        check = ci->attr_read(slot, priv->slots[slot].radr);
        if(check == priv->slots[slot].cor){
            dprintk("CHECK COR success:%x %x\n", priv->slots[slot].cor, check);
            break;
        }
        dprintk("CHECK COR failed:%x %x\n", priv->slots[slot].cor, check);
    }while(0);

    return 0;
}

static int
__dvb_ci_phys_wait_status(struct dvb_ci * ci,
                          int slot, 
                          u8 wait)
{
    u8 status;
    unsigned long timeout;
    unsigned start = jiffies;

    timeout = start + HZ/10;

    while(1){
        status = ci->io_read(slot, CTRLIF_STATUS);
        if(status & wait){
            return 0;
        }
        if(time_after(jiffies, timeout)){
            // timeout
            break;
        }
        msleep(1);
    }

    return -1;
}

static int
__dvb_ci_phys_link_init(struct dvb_ci_private * priv,
                        int slot)
{
    u8 buf[2];
    struct dvb_ci * ci = priv->cidev;

    // reset on
    dprintk("LINK: reset...\n");

    ci->io_write(slot, CTRLIF_COMMAND, CMDREG_RS);
    msleep(1000);
    // reset off
    ci->io_write(slot, CTRLIF_COMMAND, 0);

    // wait
    dprintk("LINK: wait for FR...\n");
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("LINK: reset failed\n");
        return -1;
    }

    // size read on
    dprintk("LINK: read size\n");
    ci->io_write(slot, CTRLIF_COMMAND, CMDREG_SR);
    // wait
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_DA) != 0){
        printk("LINK: size read failed, no data available\n");
        return -1;
    }
    // read size
    buf[0] = ci->io_read(slot, CTRLIF_DATA);
    buf[1] = ci->io_read(slot, CTRLIF_DATA);
    dprintk("LINK: buffer size %02x %02x\n", buf[0], buf[1]);

    if(buf[0] != 0){
        buf[0] = 0;
        buf[1] = 0xff;
    }
    priv->slots[slot].buf_size = buf[1];

    // size read off
    ci->io_write(slot, CTRLIF_COMMAND, 0);

    // size write on
    dprintk("LINK: write size back %02x %02x\n", buf[0], buf[1]);
    ci->io_write(slot, CTRLIF_COMMAND, CMDREG_SW);
    // wait
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("LINK: size write failed after SW\n");
        // this is not so important, wait for next FR after HC
        //return -1;
    }
    ci->io_write(slot, CTRLIF_COMMAND, CMDREG_HC|CMDREG_SW);
    // wait
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("LINK: size write failed after HC\n");
        return -1;
    }
    ci->io_write(slot, CTRLIF_SIZE_LOW, 0x02);
    ci->io_write(slot, CTRLIF_SIZE_HIGH, 0x00);
    // write size
    ci->io_write(slot, CTRLIF_DATA, buf[0]);
    ci->io_write(slot, CTRLIF_DATA, buf[1]);
    // size write off
    ci->io_write(slot, CTRLIF_COMMAND, 0);
    // wait
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("LINK: size write failed\n");
    }

    dprintk("LINK: done\n");

    return 0;
}

static int 
__dvb_ci_phys_do_ioctl(struct inode *inode, 
                       struct file *file,
                       unsigned int cmd, 
                       void *parg)
{
    int i;
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = (struct dvb_ci_private *)dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;
    struct ca_caps * caps;
    struct ca_slot_info * info;

    dprintk("%s\n", __FUNCTION__);

    switch(cmd){
    case CA_RESET:
        for(i=0; i<priv->slot_count; i++){
            if(priv->slots[i].state == DVB_CI_P){
                priv->slots[i].state = DVB_CI_NP;
                ci->bypass(i, DVB_CI_BYPASS);
                ci->reset(i);
            }
        }
        break;
    case CA_GET_CAP:
        caps = (struct ca_caps *)parg;
        caps->slot_num = priv->slot_count;
        caps->slot_type = CA_CI_PHYS;
        caps->descr_num = 0;
        caps->descr_type = 0;
        break;
    case CA_GET_SLOT_INFO:
        info = (struct ca_slot_info *)parg;
        if((info->num > priv->slot_count) || (info->num < 0)){
            return -EINVAL;
        }

        info->type = CA_CI_PHYS;
        if(priv->slots[info->num].state == DVB_CI_P){
            info->flags = CA_CI_MODULE_PRESENT;
        }
        else{
            info->flags = 0;
        }
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

void
__dvb_ci_init_error(struct dvb_ci_private * priv,
                    int slot)
{
    dprintk("%s\n", __FUNCTION__);
    // an init error occurs
    priv->slots[slot].init_err ++;

    // assume, for most card, init can be done before
    // 10 times of error
    if(priv->slots[slot].init_err >= 10){
        // reset
        dprintk("ci initialize failed more than 10 times, reseting...\n");
        priv->cidev->reset(slot);
        priv->slots[slot].init_err = 0;
    }

    return;
}

/*-------------------------------------------------------------------------
 * file operations
 *-----------------------------------------------------------------------*/
static int 
__dvb_ci_phys_ioctl(struct inode *inode, 
                    struct file *file,
                    unsigned int cmd, 
                    unsigned long arg)
{
    dprintk("%s\n", __FUNCTION__);
    return dvb_usercopy(inode, 
                        file, 
                        cmd, 
                        arg, 
                        __dvb_ci_phys_do_ioctl);
}

static ssize_t 
__dvb_ci_phys_write(struct file *file,
                    const char __user * buf, 
                    size_t count, 
                    loff_t * ppos)
{
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;
    int slot = 0;
    int i;

    dprintk("%s\n", __FUNCTION__);

    // check slot state
    if(priv->slots[slot].state != DVB_CI_P){
        printk("%s: card not present\n", __FUNCTION__);
        return -EINVAL;
    }
    // check length to be write
    if(count > priv->slots[slot].buf_size){
        printk("%s: write buffer size overflow(%d %d)\n",
               __FUNCTION__, count, priv->slots[slot].buf_size);
        return -EINVAL;
    }

    // check for FR
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("%s: card not free to write\n",
               __FUNCTION__);
        // not ready for write
        return -EAGAIN;
    }

    // HC
    ci->io_write(slot, CTRLIF_COMMAND, CMDREG_HC);
    // check for FR
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("%s: card not free after HC\n",
               __FUNCTION__);
        // not ready for write
        return -EIO;
    }
    // write size
    ci->io_write(slot, CTRLIF_SIZE_LOW, count & 0xff);
    ci->io_write(slot, CTRLIF_SIZE_HIGH, (count >> 8) & 0xff);

    // copy from user space
    copy_from_user(priv->slots[slot].buf, buf, count);

    // write data
    for(i=0; i<count; i++){
        ci->io_write(slot, CTRLIF_DATA, priv->slots[slot].buf[i]);
    }
    // clear HC
    ci->io_write(slot, CTRLIF_COMMAND, 0);

    // check for FR
    // FR flags may not be set immediately after writing
    // so ignore this checking
    /*
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        dprintk("%s: card not free after HC clear\n",
                __FUNCTION__);
        // not ready for write
        return -EIO;
    }
    */

    return count;
}

static ssize_t 
__dvb_ci_phys_read(struct file *file, 
                   char __user * buf,
                   size_t count, 
                   loff_t * ppos)
{
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;
    int slot = 0;
    size_t rcount = 0;
    int i;

    dprintk("%s\n", __FUNCTION__);

    // check slot state
    if(priv->slots[slot].state != DVB_CI_P){
        printk("%s: card not present\n",
               __FUNCTION__);
        return -EINVAL;
    }
    // check length to be write
    if(count > priv->slots[slot].buf_size){
        printk("%s: write buffer size overflow(%d %d)\n",
               __FUNCTION__, count, priv->slots[slot].buf_size);
        return -EINVAL;
    }

    // check for DA
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_DA) != 0){
        printk("%s: no data available on card\n",
               __FUNCTION__);
        // not ready for read
        return -EAGAIN;
    }

    rcount = ci->io_read(slot, CTRLIF_SIZE_LOW);
    rcount |= ci->io_read(slot, CTRLIF_SIZE_HIGH);

    // check buffer size
    if(rcount > priv->slots[slot].buf_size){
        printk("%s: read buffer size overflow(%d %d)\n",
               __FUNCTION__, rcount, priv->slots[slot].buf_size);
        return -EINVAL;
    }

    // read data
    for(i=0; i<rcount; i++){
        priv->slots[slot].buf[i] = ci->io_read(slot, CTRLIF_DATA);
    }

    // check for FR
    if(__dvb_ci_phys_wait_status(ci, slot, STATUSREG_FR) != 0){
        printk("%s: card not free after read\n",
               __FUNCTION__);
        // not free after read, that's not important
        //return -EIO;
    }

    // copy to user space
    copy_to_user(buf, priv->slots[slot].buf, rcount);

    return rcount;
}

static int 
__dvb_ci_phys_open(struct inode *inode, 
                   struct file *file)
{
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;

    dprintk("%s\n", __FUNCTION__);

    if(!try_module_get(ci->owner)){
        dprintk("CI open error: module get failed\n"); 
        return -EIO;
    }

    if(dvb_generic_open(inode, file) < 0){
        dprintk("CI open error: generic failed\n"); 
        return -EIO;
    }


    return 0;
}

static int 
__dvb_ci_phys_release(struct inode *inode, 
                      struct file *file)
{
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;
    int i;

    dprintk("%s\n", __FUNCTION__);

    dvb_generic_release(inode, file);

    module_put(ci->owner);

    dprintk("reset slots[%d]\n", priv->slot_count);
    for(i=0; i<priv->slot_count; i++){
        priv->slots[i].buf_size = sizeof(priv->slots[i].buf);
        priv->slots[i].state = DVB_CI_NP;
        ci->bypass(i, DVB_CI_BYPASS);
    }

    return 0;
}

static unsigned int 
__dvb_ci_phys_poll(struct file *file, 
                   poll_table * wait)
{
    struct dvb_device *dvbdev = file->private_data;
    struct dvb_ci_private * priv = dvbdev->priv;
    struct dvb_ci * ci = priv->cidev;
    dvb_ci_slot_state slot_state;
    unsigned int mask = 0;
    int slot = 0;

    //dprintk("%s\n", __FUNCTION__);

    slot_state = ci->detect(slot);

    if(priv->slots[slot].state != slot_state){
        if(slot_state == DVB_CI_P){
            // NP2P
            // reset here is not good because:
            // ci->reset use a PIO controls to reset the PCMCIA interface
            // but it takes a long time, so the following cis checking,
            // write_cor or link_init will failed. we don't know how long
            // a reset takes, it depends on card vendor.
            //
            // it's also not good to call ci->reset() while a error returns
            // from cis checking, write_cor or link_init. reason is the same
            // reset will take a long time and before the card is really
            // done resetting, application may call this function again.
            //
            // so a init_err counter is added
            //

            // memory switch to attr
            ci->memory_switch(slot, DVB_CI_ATTR);
            // check cis
            if(__dvb_ci_phys_check_cis(priv, slot) != 0){
                __dvb_ci_init_error(priv, slot);
                return POLLHUP;
            }
            // write cor
            if(__dvb_ci_phys_write_cor(priv, slot) != 0){
                __dvb_ci_init_error(priv, slot);
                return POLLHUP;
            }
            // memory switch to io
            ci->memory_switch(slot, DVB_CI_IO);
            // link init
            if(__dvb_ci_phys_link_init(priv, slot) != 0){
                __dvb_ci_init_error(priv, slot);
                return POLLHUP;
            }

            // init done, clear error flag
            priv->slots[slot].init_err = 0;

            // bypass
            ci->bypass(slot, DVB_CI_THROUGH);
            // change state
            priv->slots[slot].state = slot_state;
            return POLLPRI;
        }
        else{
            // P2NP
            priv->slots[slot].init_err = 0;
            priv->slots[slot].state = slot_state;
            // bypass
            ci->bypass(slot, DVB_CI_BYPASS);
            return POLLHUP;
        }
    }

    if(slot_state == DVB_CI_P){
        u8 status = ci->io_read(slot, CTRLIF_STATUS);

        if(status & STATUSREG_TXERR){
            mask |= POLLERR;
        }
        if(status & STATUSREG_FR){
            mask |= POLLOUT;
        }
        if(status & STATUSREG_DA){
            mask |= POLLIN;
        }
    }
    else{
        mask = POLLHUP;
    }

    return mask;
}

static struct file_operations __dvb_ci_phys_fops = {
    .owner = THIS_MODULE,
    .read = __dvb_ci_phys_read,
    .write = __dvb_ci_phys_write,
    .ioctl = __dvb_ci_phys_ioctl,
    .open = __dvb_ci_phys_open,
    .release = __dvb_ci_phys_release,
    .poll = __dvb_ci_phys_poll,
};

static struct dvb_device __dvb_ci = {
    .priv = NULL,
    .users = 1,
    .readers = 1,
    .writers = 1,
    .fops = &__dvb_ci_phys_fops,
};

/*-------------------------------------------------------------------------
 * export symbols(init/release)
 *-----------------------------------------------------------------------*/

struct dvb_ci_private * __priv;

int
dvb_ci_phys_init(struct dvb_adapter * adapter,
                 struct dvb_ci * ci,
                 int slot_count)
{
    int ret;
    struct dvb_ci_private * priv;
    int i;

    dprintk("%s\n", __FUNCTION__);
    
    // alloc
    priv = kzalloc(sizeof(struct dvb_ci_private),
                   GFP_KERNEL);

    if(priv == NULL){
        __GOTO(error, -ENOMEM);
    }

    priv->slots = kcalloc(slot_count,
                          sizeof(struct dvb_ci_slot),
                          GFP_KERNEL);
    if(priv->slots == NULL){
        __GOTO(error, -ENOMEM);
    }
    priv->slot_count = slot_count;

    // register dvb device(link priv to dvbdev)
    ret = dvb_register_device(adapter,              // adapter
                              &priv->dvbdev,        // DVB device
                              &__dvb_ci,            // DVB device template
                              priv,                 // private data
                              DVB_DEVICE_CA);
    if(ret){
        __GOTO(error, ret);
    }

    /* now initialise each slot */
    for(i = 0; i < slot_count; i++) {
        priv->slots[i].state = DVB_CI_NP;
        //ci->bypass(i, DVB_CI_BYPASS);
    }

    priv->cidev = ci;

    __priv = priv;

    return 0;
error:

    printk("dvb ci init error\n");
    if(priv){
        if(priv->dvbdev){
            dvb_unregister_device(priv->dvbdev);
        }
        kfree(priv->slots);
        kfree(priv);
    }

    return ret;
}

EXPORT_SYMBOL(dvb_ci_phys_init);

void
dvb_ci_phys_release(void)
{
    int i;

    dprintk("%s\n", __FUNCTION__);

    if(__priv){
        for(i=0; i<__priv->slot_count; i++){
            __priv->cidev->bypass(i, DVB_CI_BYPASS);
            //vfree(__priv->slots[i].buf);
        }

        kfree(__priv->slots);
        dvb_unregister_device(__priv->dvbdev);
        kfree(__priv);
    }

    __priv = NULL;
}

EXPORT_SYMBOL(dvb_ci_phys_release);

