#include "stv0367_priv.h"
 #include "tda18212.h"
 
+static int xo2_speed = 2;
+module_param(xo2_speed, int, 0444);
+MODULE_PARM_DESC(xo2_speed, "default transfer speed for xo2 based duoflex, 0=55,1=75,2=90,3=104 MBit/s, default=2, use attribute to change for individual cards");
+
 DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
 
 /* MSI had problems with lost interrupts, fixed but needs testing */
 
 /******************************************************************************/
 
+static int i2c_io(struct i2c_adapter *adapter, u8 adr,
+                 u8 *wbuf, u32 wlen, u8 *rbuf, u32 rlen)
+{
+       struct i2c_msg msgs[2] = {{.addr = adr,  .flags = 0,
+                                  .buf  = wbuf, .len   = wlen },
+                                 {.addr = adr,  .flags = I2C_M_RD,
+                                  .buf  = rbuf,  .len   = rlen } };
+       return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1;
+}
+
+static int i2c_write(struct i2c_adapter *adap, u8 adr, u8 *data, int len)
+{
+       struct i2c_msg msg = {.addr = adr, .flags = 0,
+                             .buf = data, .len = len};
+
+       return (i2c_transfer(adap, &msg, 1) == 1) ? 0 : -1;
+}
+
 static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val)
 {
        struct i2c_msg msgs[1] = {{.addr = adr,  .flags = I2C_M_RD,
        return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1;
 }
 
+static int i2c_write_reg(struct i2c_adapter *adap, u8 adr,
+                        u8 reg, u8 val)
+{
+       u8 msg[2] = {reg, val};
+
+       return i2c_write(adap, adr, msg, 2);
+}
+
 static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd)
 {
        struct ddb *dev = i2c->dev;
 /****************************************************************************/
 /****************************************************************************/
 
+static int init_xo2(struct ddb_port *port)
+{
+       struct i2c_adapter *i2c = &port->i2c->adap;
+       u8 val, data[2];
+       int res;
+
+       res = i2c_read_regs(i2c, 0x10, 0x04, data, 2);
+       if (res < 0)
+               return res;
+
+       if (data[0] != 0x01)  {
+               pr_info("Port %d: invalid XO2\n", port->nr);
+               return -1;
+       }
+
+       i2c_read_reg(i2c, 0x10, 0x08, &val);
+       if (val != 0) {
+               i2c_write_reg(i2c, 0x10, 0x08, 0x00);
+               msleep(100);
+       }
+       /* Enable tuner power, disable pll, reset demods */
+       i2c_write_reg(i2c, 0x10, 0x08, 0x04);
+       usleep_range(2000, 3000);
+       /* Release demod resets */
+       i2c_write_reg(i2c, 0x10, 0x08, 0x07);
+
+       /* speed: 0=55,1=75,2=90,3=104 MBit/s */
+       i2c_write_reg(i2c, 0x10, 0x09,
+               ((xo2_speed >= 0 && xo2_speed <= 3) ? xo2_speed : 2));
+
+       i2c_write_reg(i2c, 0x10, 0x0a, 0x01);
+       i2c_write_reg(i2c, 0x10, 0x0b, 0x01);
+
+       usleep_range(2000, 3000);
+       /* Start XO2 PLL */
+       i2c_write_reg(i2c, 0x10, 0x08, 0x87);
+
+       return 0;
+}
+
+static int port_has_xo2(struct ddb_port *port, u8 *type, u8 *id)
+{
+       u8 probe[1] = { 0x00 }, data[4];
+
+       *type = DDB_XO2_TYPE_NONE;
+
+       if (i2c_io(&port->i2c->adap, 0x10, probe, 1, data, 4))
+               return 0;
+       if (data[0] == 'D' && data[1] == 'F') {
+               *id = data[2];
+               *type = DDB_XO2_TYPE_DUOFLEX;
+               return 1;
+       }
+       if (data[0] == 'C' && data[1] == 'I') {
+               *id = data[2];
+               *type = DDB_XO2_TYPE_CI;
+               return 1;
+       }
+       return 0;
+}
+
+/****************************************************************************/
+/****************************************************************************/
+
 static int port_has_ci(struct ddb_port *port)
 {
        u8 val;
 {
        struct ddb *dev = port->dev;
        char *modname = "NO MODULE";
+       u8 xo2_type, xo2_id;
 
        port->class = DDB_PORT_NONE;
 
                modname = "CI";
                port->class = DDB_PORT_CI;
                ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING);
+       } else if (port_has_xo2(port, &xo2_type, &xo2_id)) {
+               printk(KERN_INFO "Port %d (TAB %d): XO2 type: %d, id: %d\n",
+                       port->nr, port->nr+1, xo2_type, xo2_id);
+
+               ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING);
+
+               switch (xo2_type) {
+               case DDB_XO2_TYPE_DUOFLEX:
+                       init_xo2(port);
+                       switch (xo2_id >> 2) {
+                       case 0:
+                               modname = "DUAL DVB-S2 (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_DVBS_STV0910;
+                               break;
+                       case 1:
+                               modname = "DUAL DVB-C/T/T2 (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_DVBCT2_SONY;
+                               break;
+                       case 2:
+                               modname = "DUAL DVB-ISDBT (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_ISDBT_SONY;
+                               break;
+                       case 3:
+                               modname = "DUAL DVB-C/C2/T/T2 (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_DVBC2T2_SONY;
+                               break;
+                       case 4:
+                               modname = "DUAL ATSC (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_ATSC_ST;
+                               break;
+                       case 5:
+                               modname = "DUAL DVB-C/C2/T/T2/ISDBT (unsupported)";
+                               port->class = DDB_PORT_NONE;
+                               port->type = DDB_TUNER_XO2_DVBC2T2I_SONY;
+                               break;
+                       default:
+                               modname = "Unknown XO2 DuoFlex module\n";
+                               break;
+                       }
+                       break;
+               case DDB_XO2_TYPE_CI:
+                       printk(KERN_INFO "DuoFlex CI modules not supported\n");
+                       break;
+               default:
+                       printk(KERN_INFO "Unknown XO2 DuoFlex module\n");
+                       break;
+               }
        } else if (port_has_stv0900(port)) {
                modname = "DUAL DVB-S2";
                port->class = DDB_PORT_TUNER;