drm/nouveau/i2c: handle bit-banging ourselves
authorBen Skeggs <bskeggs@redhat.com>
Wed, 21 Dec 2011 08:09:12 +0000 (18:09 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 21 Dec 2011 09:01:41 +0000 (19:01 +1000)
i2c-algo-bit doesn't actually work very well on one card I have access to
(NVS 300), random single-bit errors occur most of the time - what we're
doing now is closer to what xf86i2c.c does.

The original plan was to figure out why i2c-algo-bit fails on the NVS 300,
and fix it.  However, while investigating I discovered i2c-algo-bit calls
cond_resched(), which makes it a bad idea for us to be using as we execute
VBIOS scripts from a tasklet, and there may very well be i2c transfers as
a result.

So, since I already wrote this code in userspace to track down the NVS 300
bug, and it's not really much code - lets use it.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_i2c.c
drivers/gpu/drm/nouveau/nouveau_i2c.h

index 116ef3193fc794079c2533f951e2071800c0b2be..820ae7f520448086c388caa6cb5da00c8096f180 100644 (file)
 #include "nouveau_i2c.h"
 #include "nouveau_hw.h"
 
+#define T_TIMEOUT  2200000
+#define T_RISEFALL 1000
+#define T_HOLD     5000
+
 static void
 i2c_drive_scl(void *data, int state)
 {
@@ -109,6 +113,175 @@ i2c_sense_sda(void *data)
        return 0;
 }
 
+static void
+i2c_delay(struct nouveau_i2c_chan *port, u32 nsec)
+{
+       udelay((nsec + 500) / 1000);
+}
+
+static bool
+i2c_raise_scl(struct nouveau_i2c_chan *port)
+{
+       u32 timeout = T_TIMEOUT / T_RISEFALL;
+
+       i2c_drive_scl(port, 1);
+       do {
+               i2c_delay(port, T_RISEFALL);
+       } while (!i2c_sense_scl(port) && --timeout);
+
+       return timeout != 0;
+}
+
+static int
+i2c_start(struct nouveau_i2c_chan *port)
+{
+       int ret = 0;
+
+       port->state  = i2c_sense_scl(port);
+       port->state |= i2c_sense_sda(port) << 1;
+       if (port->state != 3) {
+               i2c_drive_scl(port, 0);
+               i2c_drive_sda(port, 1);
+               if (!i2c_raise_scl(port))
+                       ret = -EBUSY;
+       }
+
+       i2c_drive_sda(port, 0);
+       i2c_delay(port, T_HOLD);
+       i2c_drive_scl(port, 0);
+       i2c_delay(port, T_HOLD);
+       return ret;
+}
+
+static void
+i2c_stop(struct nouveau_i2c_chan *port)
+{
+       i2c_drive_scl(port, 0);
+       i2c_drive_sda(port, 0);
+       i2c_delay(port, T_RISEFALL);
+
+       i2c_drive_scl(port, 1);
+       i2c_delay(port, T_HOLD);
+       i2c_drive_sda(port, 1);
+       i2c_delay(port, T_HOLD);
+}
+
+static int
+i2c_bitw(struct nouveau_i2c_chan *port, int sda)
+{
+       i2c_drive_sda(port, sda);
+       i2c_delay(port, T_RISEFALL);
+
+       if (!i2c_raise_scl(port))
+               return -ETIMEDOUT;
+       i2c_delay(port, T_HOLD);
+
+       i2c_drive_scl(port, 0);
+       i2c_delay(port, T_HOLD);
+       return 0;
+}
+
+static int
+i2c_bitr(struct nouveau_i2c_chan *port)
+{
+       int sda;
+
+       i2c_drive_sda(port, 1);
+       i2c_delay(port, T_RISEFALL);
+
+       if (!i2c_raise_scl(port))
+               return -ETIMEDOUT;
+       i2c_delay(port, T_HOLD);
+
+       sda = i2c_sense_sda(port);
+
+       i2c_drive_scl(port, 0);
+       i2c_delay(port, T_HOLD);
+       return sda;
+}
+
+static int
+i2c_get_byte(struct nouveau_i2c_chan *port, u8 *byte, bool last)
+{
+       int i, bit;
+
+       *byte = 0;
+       for (i = 7; i >= 0; i--) {
+               bit = i2c_bitr(port);
+               if (bit < 0)
+                       return bit;
+               *byte |= bit << i;
+       }
+
+       return i2c_bitw(port, last ? 1 : 0);
+}
+
+static int
+i2c_put_byte(struct nouveau_i2c_chan *port, u8 byte)
+{
+       int i, ret;
+       for (i = 7; i >= 0; i--) {
+               ret = i2c_bitw(port, !!(byte & (1 << i)));
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = i2c_bitr(port);
+       if (ret == 1) /* nack */
+               ret = -EIO;
+       return ret;
+}
+
+static int
+i2c_addr(struct nouveau_i2c_chan *port, struct i2c_msg *msg)
+{
+       u32 addr = msg->addr << 1;
+       if (msg->flags & I2C_M_RD)
+               addr |= 1;
+       return i2c_put_byte(port, addr);
+}
+
+static int
+i2c_bit_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+       struct nouveau_i2c_chan *port = (struct nouveau_i2c_chan *)adap;
+       struct i2c_msg *msg = msgs;
+       int ret = 0, mcnt = num;
+
+       while (!ret && mcnt--) {
+               u8 remaining = msg->len;
+               u8 *ptr = msg->buf;
+
+               ret = i2c_start(port);
+               if (ret == 0)
+                       ret = i2c_addr(port, msg);
+
+               if (msg->flags & I2C_M_RD) {
+                       while (!ret && remaining--)
+                               ret = i2c_get_byte(port, ptr++, !remaining);
+               } else {
+                       while (!ret && remaining--)
+                               ret = i2c_put_byte(port, *ptr++);
+               }
+
+               msg++;
+       }
+
+       i2c_stop(port);
+       return (ret < 0) ? ret : num;
+}
+
+static u32
+i2c_bit_func(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+const struct i2c_algorithm i2c_bit_algo = {
+       .master_xfer = i2c_bit_xfer,
+       .functionality = i2c_bit_func
+};
+
 static const uint32_t nv50_i2c_port[] = {
        0x00e138, 0x00e150, 0x00e168, 0x00e180,
        0x00e254, 0x00e274, 0x00e764, 0x00e780,
@@ -211,10 +384,12 @@ nouveau_i2c_init(struct drm_device *dev)
                case 0: /* NV04:NV50 */
                        port->drive = entry[0];
                        port->sense = entry[1];
+                       port->adapter.algo = &i2c_bit_algo;
                        break;
                case 4: /* NV4E */
                        port->drive = 0x600800 + entry[1];
                        port->sense = port->drive;
+                       port->adapter.algo = &i2c_bit_algo;
                        break;
                case 5: /* NV50- */
                        port->drive = entry[0] & 0x0f;
@@ -227,6 +402,7 @@ nouveau_i2c_init(struct drm_device *dev)
                                port->drive = 0x00d014 + (port->drive * 0x20);
                                port->sense = port->drive;
                        }
+                       port->adapter.algo = &i2c_bit_algo;
                        break;
                case 6: /* NV50- DP AUX */
                        port->drive = entry[0];
@@ -237,7 +413,7 @@ nouveau_i2c_init(struct drm_device *dev)
                        break;
                }
 
-               if (!port->adapter.algo && !port->drive) {
+               if (!port->adapter.algo) {
                        NV_ERROR(dev, "I2C%d: type %d index %x/%x unknown\n",
                                 i, port->type, port->drive, port->sense);
                        kfree(port);
@@ -253,26 +429,7 @@ nouveau_i2c_init(struct drm_device *dev)
                port->dcb = ROM32(entry[0]);
                i2c_set_adapdata(&port->adapter, i2c);
 
-               if (port->adapter.algo != &nouveau_dp_i2c_algo) {
-                       port->adapter.algo_data = &port->bit;
-                       port->bit.udelay = 40;
-                       port->bit.timeout = usecs_to_jiffies(5000);
-                       port->bit.data = port;
-                       port->bit.setsda = i2c_drive_sda;
-                       port->bit.setscl = i2c_drive_scl;
-                       port->bit.getsda = i2c_sense_sda;
-                       port->bit.getscl = i2c_sense_scl;
-
-                       i2c_drive_scl(port, 0);
-                       i2c_drive_sda(port, 1);
-                       i2c_drive_scl(port, 1);
-
-                       ret = i2c_bit_add_bus(&port->adapter);
-               } else {
-                       port->adapter.algo = &nouveau_dp_i2c_algo;
-                       ret = i2c_add_adapter(&port->adapter);
-               }
-
+               ret = i2c_add_adapter(&port->adapter);
                if (ret) {
                        NV_ERROR(dev, "I2C%d: failed register: %d\n", i, ret);
                        kfree(port);
index 1d083893a4d74b29b853a6baf4ba898c4e1a0db4..4d2e4e9031be6dc8f51882c5c95a933c0e763064 100644 (file)
@@ -34,7 +34,6 @@
 struct nouveau_i2c_chan {
        struct i2c_adapter adapter;
        struct drm_device *dev;
-       struct i2c_algo_bit_data bit;
        struct list_head head;
        u8  index;
        u8  type;