Switch the inteldrm(4) i2c code over to the Linux code base. This gives us
authorkettenis <kettenis@openbsd.org>
Mon, 15 Jan 2018 22:24:17 +0000 (22:24 +0000)
committerkettenis <kettenis@openbsd.org>
Mon, 15 Jan 2018 22:24:17 +0000 (22:24 +0000)
several quirks that wre absent in the old OpenBSD-specific reimplementation.
Fixes several issues with external connectors on several generations of
hardware.

ok deraadt@, benno@

sys/dev/pci/drm/drm_linux.c
sys/dev/pci/drm/drm_linux.h
sys/dev/pci/drm/i915/i915_drv.h
sys/dev/pci/drm/i915/intel_i2c.c

index 40141b8..7cabeac 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: drm_linux.c,v 1.17 2018/01/13 13:03:42 robert Exp $   */
+/*     $OpenBSD: drm_linux.c,v 1.18 2018/01/15 22:24:17 kettenis Exp $ */
 /*
  * Copyright (c) 2013 Jonathan Gray <jsg@openbsd.org>
  * Copyright (c) 2015, 2016 Mark Kettenis <kettenis@openbsd.org>
@@ -552,16 +552,13 @@ sg_copy_from_buffer(struct scatterlist *sgl, unsigned int nents,
 }
 
 int
-i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
 {
        void *cmd = NULL;
        int cmdlen = 0;
        int err, ret = 0;
        int op;
 
-       if (adap->algo)
-               return adap->algo->master_xfer(adap, msgs, num);
-
        iic_acquire_bus(&adap->ic, 0);
 
        while (num > 2) {
@@ -585,8 +582,10 @@ i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
                ret++;
        }
 
-       op = (msgs->flags & I2C_M_RD) ? I2C_OP_READ_WITH_STOP : I2C_OP_WRITE_WITH_STOP;
-       err = iic_exec(&adap->ic, op, msgs->addr, cmd, cmdlen, msgs->buf, msgs->len, 0);
+       op = (msgs->flags & I2C_M_RD) ?
+           I2C_OP_READ_WITH_STOP : I2C_OP_WRITE_WITH_STOP;
+       err = iic_exec(&adap->ic, op, msgs->addr, cmd, cmdlen,
+           msgs->buf, msgs->len, 0);
        if (err) {
                ret = -err;
                goto fail;
@@ -600,6 +599,38 @@ fail:
        return ret;
 }
 
+int
+i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+       if (adap->algo)
+               return adap->algo->master_xfer(adap, msgs, num);
+
+       return i2c_master_xfer(adap, msgs, num);
+}
+
+int
+i2c_bb_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+       struct i2c_algo_bit_data *algo = adap->algo_data;
+       struct i2c_adapter bb;
+
+       memset(&bb, 0, sizeof(bb));
+       bb.ic = algo->ic;
+       bb.retries = adap->retries;
+       return i2c_master_xfer(&bb, msgs, num);
+}
+
+uint32_t
+i2c_bb_functionality(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+struct i2c_algorithm i2c_bit_algo = {
+       .master_xfer = i2c_bb_master_xfer,
+       .functionality = i2c_bb_functionality
+};
+
 #if defined(__amd64__) || defined(__i386__)
 
 /*
index c7a8305..64ea410 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: drm_linux.h,v 1.66 2018/01/13 14:15:07 jsg Exp $      */
+/*     $OpenBSD: drm_linux.h,v 1.67 2018/01/15 22:24:17 kettenis Exp $ */
 /*
  * Copyright (c) 2013, 2014, 2015 Mark Kettenis
  * Copyright (c) 2017 Martin Pieuchot
@@ -1199,6 +1199,11 @@ finish_wait(wait_queue_head_t *wq, wait_queue_head_t **wait)
 static inline long
 schedule_timeout(long timeout, wait_queue_head_t **wait)
 {
+       if (cold) {
+               delay((timeout * 1000000) / hz);
+               return -ETIMEDOUT;
+       }
+
        return -msleep(*wait, &(*wait)->lock, PZERO, "schto", timeout);
 }
 
@@ -1609,8 +1614,14 @@ struct i2c_msg {
 #define I2C_M_NOSTART  0x0002
 
 struct i2c_algorithm {
-       u32 (*functionality)(struct i2c_adapter *);
        int (*master_xfer)(struct i2c_adapter *, struct i2c_msg *, int);
+       u32 (*functionality)(struct i2c_adapter *);
+};
+
+extern struct i2c_algorithm i2c_bit_algo;
+
+struct i2c_algo_bit_data {
+       struct i2c_controller ic;
 };
 
 int i2c_transfer(struct i2c_adapter *, struct i2c_msg *, int);
index c1e4b6f..bb8b2dd 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: i915_drv.h,v 1.78 2017/09/30 07:36:56 robert Exp $ */
+/* $OpenBSD: i915_drv.h,v 1.79 2018/01/15 22:24:17 kettenis Exp $ */
 /* i915_drv.h -- Private header for the I915 driver -*- linux-c -*-
  */
 /*
@@ -1102,9 +1102,7 @@ struct intel_gmbus {
        u32 force_bit;
        u32 reg0;
        u32 gpio_reg;
-#ifdef __linux__
        struct i2c_algo_bit_data bit_algo;
-#endif
        struct drm_i915_private *dev_priv;
 };
 
index bfe2deb..36428ad 100644 (file)
@@ -1,19 +1,3 @@
-/*     $OpenBSD: intel_i2c.c,v 1.12 2017/09/30 07:36:56 robert Exp $   */
-/*
- * Copyright (c) 2012, 2013 Mark Kettenis <kettenis@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
 /*
  * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
  * Copyright © 2006-2008,2010 Intel Corporation
  *     Eric Anholt <eric@anholt.net>
  *     Chris Wilson <chris@chris-wilson.co.uk>
  */
-
+#ifdef __linux__
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/export.h>
+#endif
 #include <dev/pci/drm/drmP.h>
-#include <dev/pci/drm/drm.h>
 #include "intel_drv.h"
+#include <dev/pci/drm/i915_drm.h>
 #include "i915_drv.h"
-#include "i915_reg.h"
 
 #include <dev/i2c/i2cvar.h>
 #include <dev/i2c/i2c_bitbang.h>
 
-int    intel_gmbus_acquire_bus(void *, int);
-void   intel_gmbus_release_bus(void *, int);
-int    intel_gmbus_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
-           void *buf, size_t, int);
-
-void   intel_gpio_bb_set_bits(void *, uint32_t);
-void   intel_gpio_bb_set_dir(void *, uint32_t);
-uint32_t intel_gpio_bb_read_bits(void *);
-
-int    intel_gpio_send_start(void *, int);
-int    intel_gpio_send_stop(void *, int);
-int    intel_gpio_initiate_xfer(void *, i2c_addr_t, int);
-int    intel_gpio_read_byte(void *, u_int8_t *, int);
-int    intel_gpio_write_byte(void *, u_int8_t, int);
-
-enum disp_clk {
-       CDCLK,
-       CZCLK
-};
-
 struct gmbus_pin {
        const char *name;
        int reg;
@@ -156,242 +123,620 @@ intel_i2c_reset(struct drm_device *dev)
        I915_WRITE(GMBUS4, 0);
 }
 
-int
-intel_gmbus_acquire_bus(void *cookie, int flags)
+static void intel_i2c_quirk_set(struct drm_i915_private *dev_priv, bool enable)
 {
-       struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
-
-       intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS);
+       u32 val;
 
-       I915_WRITE(GMBUS0, bus->reg0);
+       /* When using bit bashing for I2C, this bit needs to be set to 1 */
+       if (!IS_PINEVIEW(dev_priv->dev))
+               return;
 
-       return (0);
+       val = I915_READ(DSPCLK_GATE_D);
+       if (enable)
+               val |= DPCUNIT_CLOCK_GATE_DISABLE;
+       else
+               val &= ~DPCUNIT_CLOCK_GATE_DISABLE;
+       I915_WRITE(DSPCLK_GATE_D, val);
 }
 
-void
-intel_gmbus_release_bus(void *cookie, int flags)
+static u32 get_reserved(struct intel_gmbus *bus)
 {
-       struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       struct drm_device *dev = dev_priv->dev;
+       u32 reserved = 0;
 
-       I915_WRITE(GMBUS0, 0);
+       /* On most chips, these bits must be preserved in software. */
+       if (!IS_I830(dev) && !IS_845G(dev))
+               reserved = I915_READ_NOTRACE(bus->gpio_reg) &
+                                            (GPIO_DATA_PULLUP_DISABLE |
+                                             GPIO_CLOCK_PULLUP_DISABLE);
 
-       intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS);
+       return reserved;
 }
 
-int
-intel_gmbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
-    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
+static int get_clock(void *data)
 {
-       struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
-       uint32_t reg, st, val;
-       uint8_t *b;
-       int i, retries;
-       uint16_t rem = len;
-       int bus_err = 0;
-
-       if (cmdlen > 1)
-               return (EOPNOTSUPP);
-
-       if (I2C_OP_WRITE_P(op)) {
-               val = 0;        
-
-               b = buf;
-               for (i = 0; i < 4 && rem > 0; i++, rem--) {
-                       val |= *b++ << (8 * i);
-               }
+       struct intel_gmbus *bus = data;
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       u32 reserved = get_reserved(bus);
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved | GPIO_CLOCK_DIR_MASK);
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved);
+       return (I915_READ_NOTRACE(bus->gpio_reg) & GPIO_CLOCK_VAL_IN) != 0;
+}
 
-               I915_WRITE(GMBUS3, val);
-       }
+static int get_data(void *data)
+{
+       struct intel_gmbus *bus = data;
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       u32 reserved = get_reserved(bus);
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved | GPIO_DATA_DIR_MASK);
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved);
+       return (I915_READ_NOTRACE(bus->gpio_reg) & GPIO_DATA_VAL_IN) != 0;
+}
 
-       reg = 0;
-       if (len > 0)
-               reg |= GMBUS_CYCLE_WAIT;
-       if (I2C_OP_STOP_P(op))
-               reg |= GMBUS_CYCLE_STOP;
-       if (I2C_OP_READ_P(op))
-               reg |= GMBUS_SLAVE_READ;
+static void set_clock(void *data, int state_high)
+{
+       struct intel_gmbus *bus = data;
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       u32 reserved = get_reserved(bus);
+       u32 clock_bits;
+
+       if (state_high)
+               clock_bits = GPIO_CLOCK_DIR_IN | GPIO_CLOCK_DIR_MASK;
        else
-               reg |= GMBUS_SLAVE_WRITE;
-       if (cmdlen > 0) {
-               reg |= GMBUS_CYCLE_INDEX;
-               b = (void *)cmdbuf;
-               reg |= (b[0] << GMBUS_SLAVE_INDEX_SHIFT);
-       }
-       reg |= (addr << GMBUS_SLAVE_ADDR_SHIFT);
-       reg |= (len << GMBUS_BYTE_COUNT_SHIFT);
-       I915_WRITE(GMBUS1, reg | GMBUS_SW_RDY);
-
-       if (I2C_OP_READ_P(op)) {
-               b = buf;
-               while (len > 0) {
-                       for (retries = 50; retries > 0; retries--) {
-                               st = I915_READ(GMBUS2);
-                               if (st & (GMBUS_SATOER | GMBUS_HW_RDY))
-                                       break;
-                               DELAY(1000);
-                       }
-                       if (st & GMBUS_SATOER) {
-                               bus_err = 1;
-                               goto out;
-                       }
-                       if ((st & GMBUS_HW_RDY) == 0)
-                               return (ETIMEDOUT);
-
-                       val = I915_READ(GMBUS3);
-                       for (i = 0; i < 4 && len > 0; i++, len--) {
-                               *b++ = val & 0xff;
-                               val >>= 8;
-                       }
-               }
-       }
+               clock_bits = GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK |
+                       GPIO_CLOCK_VAL_MASK;
 
-       if (I2C_OP_WRITE_P(op)) {
-               while (rem > 0) {
-                       for (retries = 50; retries > 0; retries--) {
-                               st = I915_READ(GMBUS2);
-                               if (st & (GMBUS_SATOER | GMBUS_HW_RDY))
-                                       break;
-                               DELAY(1000);
-                       }
-                       if (st & GMBUS_SATOER) {
-                               bus_err = 1;
-                               goto out;
-                       }
-                       if ((st & GMBUS_HW_RDY) == 0)
-                               return (ETIMEDOUT);
-
-                       val = 0;
-                       for (i = 0; i < 4 && rem > 0; i++, rem--) {
-                               val |= *b++ << (8 * i);
-                       }
-                       I915_WRITE(GMBUS3, val);
-               }
-       }
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved | clock_bits);
+       POSTING_READ(bus->gpio_reg);
+}
 
-out:
-       if (I2C_OP_STOP_P(op)) {
-               for (retries = 10; retries > 0; retries--) {
-                       st = I915_READ(GMBUS2);
-                       if (st & GMBUS_SATOER)
-                               bus_err = 1;
-                       if ((st & GMBUS_ACTIVE) == 0)
-                               break;
-                       DELAY(1000);
-               }
-               if (st & GMBUS_ACTIVE)
-                       return (ETIMEDOUT);
-       } else {
-               for (retries = 10; retries > 0; retries--) {
-                       st = I915_READ(GMBUS2);
-                       if (st & GMBUS_SATOER)
-                               bus_err = 1;
-                       if (st & GMBUS_HW_WAIT_PHASE)
-                               break;
-                       DELAY(1000);
-               }
-               if ((st & GMBUS_HW_WAIT_PHASE) == 0)
-                       return (ETIMEDOUT);
-       }
+static void set_data(void *data, int state_high)
+{
+       struct intel_gmbus *bus = data;
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       u32 reserved = get_reserved(bus);
+       u32 data_bits;
 
-       /* after the bus is idle clear the bus error */
-       if (bus_err) {
-               I915_WRITE(GMBUS1, GMBUS_SW_CLR_INT);
-               I915_WRITE(GMBUS1, 0);
-               I915_WRITE(GMBUS0, 0);
-               return (ENXIO);
-       }
+       if (state_high)
+               data_bits = GPIO_DATA_DIR_IN | GPIO_DATA_DIR_MASK;
+       else
+               data_bits = GPIO_DATA_DIR_OUT | GPIO_DATA_DIR_MASK |
+                       GPIO_DATA_VAL_MASK;
 
-       return (0);
+       I915_WRITE_NOTRACE(bus->gpio_reg, reserved | data_bits);
+       POSTING_READ(bus->gpio_reg);
+}
+
+static int
+intel_gpio_pre_xfer(struct i2c_adapter *adapter)
+{
+       struct intel_gmbus *bus = container_of(adapter,
+                                              struct intel_gmbus,
+                                              adapter);
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+
+       intel_i2c_reset(dev_priv->dev);
+       intel_i2c_quirk_set(dev_priv, true);
+       set_data(bus, 1);
+       set_clock(bus, 1);
+       udelay(I2C_RISEFALL_TIME);
+       return 0;
+}
+
+static void
+intel_gpio_post_xfer(struct i2c_adapter *adapter)
+{
+       struct intel_gmbus *bus = container_of(adapter,
+                                              struct intel_gmbus,
+                                              adapter);
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+
+       set_data(bus, 1);
+       set_clock(bus, 1);
+       intel_i2c_quirk_set(dev_priv, false);
 }
 
-struct i2c_bitbang_ops intel_gpio_bbops = {
-       intel_gpio_bb_set_bits,
-       intel_gpio_bb_set_dir,
-       intel_gpio_bb_read_bits,
-       { GPIO_DATA_VAL_IN, GPIO_CLOCK_VAL_IN,
-         GPIO_DATA_DIR_OUT, GPIO_DATA_DIR_IN }
+void   intel_bb_set_bits(void *, uint32_t);
+void   intel_bb_set_dir(void *, uint32_t);
+uint32_t intel_bb_read_bits(void *);
+
+int    intel_acquire_bus(void *, int);
+void   intel_release_bus(void *, int);
+int    intel_send_start(void *, int);
+int    intel_send_stop(void *, int);
+int    intel_initiate_xfer(void *, i2c_addr_t, int);
+int    intel_read_byte(void *, u_int8_t *, int);
+int    intel_write_byte(void *, u_int8_t, int);
+
+#define INTEL_BB_SDA           (1 << I2C_BIT_SDA)
+#define INTEL_BB_SCL           (1 << I2C_BIT_SCL)
+
+struct i2c_bitbang_ops intel_bbops = {
+       intel_bb_set_bits,
+       intel_bb_set_dir,
+       intel_bb_read_bits,
+       { INTEL_BB_SDA, INTEL_BB_SCL, 0, 0 }
 };
 
 void
-intel_gpio_bb_set_bits(void *cookie, uint32_t bits)
+intel_bb_set_bits(void *cookie, uint32_t bits)
 {
-       struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
-       uint32_t reg;
-
-       reg = I915_READ_NOTRACE(bus->gpio_reg);
-       reg &= (GPIO_DATA_PULLUP_DISABLE | GPIO_CLOCK_PULLUP_DISABLE);
-       if (bits & GPIO_DATA_VAL_IN)
-               reg |= GPIO_DATA_VAL_OUT;
-       if (bits & GPIO_CLOCK_VAL_IN)
-               reg |= GPIO_CLOCK_VAL_OUT;
-       reg |= (GPIO_DATA_VAL_MASK | GPIO_CLOCK_VAL_MASK);
-       I915_WRITE_NOTRACE(bus->gpio_reg, reg);
+       set_clock(cookie, bits & INTEL_BB_SCL);
+       set_data(cookie, bits & INTEL_BB_SDA);
 }
 
 void
-intel_gpio_bb_set_dir(void *cookie, uint32_t bits)
+intel_bb_set_dir(void *cookie, uint32_t bits)
+{
+}
+
+uint32_t
+intel_bb_read_bits(void *cookie)
+{
+       uint32_t bits = 0;
+
+       if (get_clock(cookie))
+               bits |= INTEL_BB_SCL;
+       if (get_data(cookie))
+               bits |= INTEL_BB_SDA;
+
+       return bits;
+}
+
+int
+intel_acquire_bus(void *cookie, int flags)
 {
        struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
-       uint32_t reg;
 
-       reg = I915_READ_NOTRACE(bus->gpio_reg);
-       reg &= (GPIO_DATA_PULLUP_DISABLE | GPIO_CLOCK_PULLUP_DISABLE);
-       if (bits & GPIO_DATA_DIR_OUT)
-               reg |= GPIO_DATA_DIR_OUT;
-       reg |= (GPIO_DATA_DIR_MASK | GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK);
-       I915_WRITE_NOTRACE(bus->gpio_reg, reg);
+       intel_gpio_pre_xfer(&bus->adapter);
+       return (0);
 }
 
-uint32_t
-intel_gpio_bb_read_bits(void *cookie)
+void
+intel_release_bus(void *cookie, int flags)
 {
        struct intel_gmbus *bus = cookie;
-       struct inteldrm_softc *dev_priv = bus->dev_priv;
 
-       return I915_READ_NOTRACE(bus->gpio_reg);
+       intel_gpio_post_xfer(&bus->adapter);
 }
 
 int
-intel_gpio_send_start(void *cookie, int flags)
+intel_send_start(void *cookie, int flags)
 {
-       return (i2c_bitbang_send_start(cookie, flags, &intel_gpio_bbops));
+       return (i2c_bitbang_send_start(cookie, flags, &intel_bbops));
 }
 
 int
-intel_gpio_send_stop(void *cookie, int flags)
+intel_send_stop(void *cookie, int flags)
 {
-       return (i2c_bitbang_send_stop(cookie, flags, &intel_gpio_bbops));
+       return (i2c_bitbang_send_stop(cookie, flags, &intel_bbops));
 }
 
 int
-intel_gpio_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
+intel_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
 {
-       return (i2c_bitbang_initiate_xfer(cookie, addr, flags, &intel_gpio_bbops));
+       return (i2c_bitbang_initiate_xfer(cookie, addr, flags, &intel_bbops));
 }
 
 int
-intel_gpio_read_byte(void *cookie, u_int8_t *bytep, int flags)
+intel_read_byte(void *cookie, u_int8_t *bytep, int flags)
 {
-       return (i2c_bitbang_read_byte(cookie, bytep, flags, &intel_gpio_bbops));
+       return (i2c_bitbang_read_byte(cookie, bytep, flags, &intel_bbops));
 }
 
 int
-intel_gpio_write_byte(void *cookie, u_int8_t byte, int flags)
+intel_write_byte(void *cookie, u_int8_t byte, int flags)
 {
-       return (i2c_bitbang_write_byte(cookie, byte, flags, &intel_gpio_bbops));
+       return (i2c_bitbang_write_byte(cookie, byte, flags, &intel_bbops));
 }
 
-int
-intel_setup_gmbus(struct drm_device *dev)
+static void
+intel_gpio_setup(struct intel_gmbus *bus, unsigned int pin)
+{
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       struct i2c_algo_bit_data *algo;
+
+       algo = &bus->bit_algo;
+
+       bus->gpio_reg = dev_priv->gpio_mmio_base +
+               get_gmbus_pin(dev_priv, pin)->reg;
+
+       bus->adapter.algo_data = algo;
+#ifdef __linux__
+       algo->setsda = set_data;
+       algo->setscl = set_clock;
+       algo->getsda = get_data;
+       algo->getscl = get_clock;
+       algo->pre_xfer = intel_gpio_pre_xfer;
+       algo->post_xfer = intel_gpio_post_xfer;
+       algo->udelay = I2C_RISEFALL_TIME;
+       algo->timeout = usecs_to_jiffies(2200);
+       algo->data = bus;
+#else
+       algo->ic.ic_cookie = bus;
+       algo->ic.ic_acquire_bus = intel_acquire_bus;
+       algo->ic.ic_release_bus = intel_release_bus;
+       algo->ic.ic_send_start = intel_send_start;
+       algo->ic.ic_send_stop = intel_send_stop;
+       algo->ic.ic_initiate_xfer = intel_initiate_xfer;
+       algo->ic.ic_read_byte = intel_read_byte;
+       algo->ic.ic_write_byte = intel_write_byte;
+#endif
+}
+
+static int
+gmbus_wait_hw_status(struct drm_i915_private *dev_priv,
+                    u32 gmbus2_status,
+                    u32 gmbus4_irq_en)
+{
+       int i;
+       u32 gmbus2 = 0;
+       DEFINE_WAIT(wait);
+
+       if (!HAS_GMBUS_IRQ(dev_priv->dev) || cold)
+               gmbus4_irq_en = 0;
+
+       /* Important: The hw handles only the first bit, so set only one! Since
+        * we also need to check for NAKs besides the hw ready/idle signal, we
+        * need to wake up periodically and check that ourselves. */
+       I915_WRITE(GMBUS4, gmbus4_irq_en);
+
+       for (i = 0; i < msecs_to_jiffies_timeout(50); i++) {
+               prepare_to_wait(&dev_priv->gmbus_wait_queue, &wait,
+                               TASK_UNINTERRUPTIBLE);
+
+               gmbus2 = I915_READ_NOTRACE(GMBUS2);
+               if (gmbus2 & (GMBUS_SATOER | gmbus2_status))
+                       break;
+
+               schedule_timeout(1, &wait);
+       }
+       finish_wait(&dev_priv->gmbus_wait_queue, &wait);
+
+       I915_WRITE(GMBUS4, 0);
+
+       if (gmbus2 & GMBUS_SATOER)
+               return -ENXIO;
+       if (gmbus2 & gmbus2_status)
+               return 0;
+       return -ETIMEDOUT;
+}
+
+static int
+gmbus_wait_idle(struct drm_i915_private *dev_priv)
+{
+       int ret;
+
+#define C ((I915_READ_NOTRACE(GMBUS2) & GMBUS_ACTIVE) == 0)
+
+       if (!HAS_GMBUS_IRQ(dev_priv->dev) || cold)
+               return wait_for(C, 10);
+
+       /* Important: The hw handles only the first bit, so set only one! */
+       I915_WRITE(GMBUS4, GMBUS_IDLE_EN);
+
+       ret = wait_event_timeout(dev_priv->gmbus_wait_queue, C,
+                                msecs_to_jiffies_timeout(10));
+
+       I915_WRITE(GMBUS4, 0);
+
+       if (ret)
+               return 0;
+       else
+               return -ETIMEDOUT;
+#undef C
+}
+
+static int
+gmbus_xfer_read_chunk(struct drm_i915_private *dev_priv,
+                     unsigned short addr, u8 *buf, unsigned int len,
+                     u32 gmbus1_index)
+{
+       I915_WRITE(GMBUS1,
+                  gmbus1_index |
+                  GMBUS_CYCLE_WAIT |
+                  (len << GMBUS_BYTE_COUNT_SHIFT) |
+                  (addr << GMBUS_SLAVE_ADDR_SHIFT) |
+                  GMBUS_SLAVE_READ | GMBUS_SW_RDY);
+       while (len) {
+               int ret;
+               u32 val, loop = 0;
+
+               ret = gmbus_wait_hw_status(dev_priv, GMBUS_HW_RDY,
+                                          GMBUS_HW_RDY_EN);
+               if (ret)
+                       return ret;
+
+               val = I915_READ(GMBUS3);
+               do {
+                       *buf++ = val & 0xff;
+                       val >>= 8;
+               } while (--len && ++loop < 4);
+       }
+
+       return 0;
+}
+
+static int
+gmbus_xfer_read(struct drm_i915_private *dev_priv, struct i2c_msg *msg,
+               u32 gmbus1_index)
+{
+       u8 *buf = msg->buf;
+       unsigned int rx_size = msg->len;
+       unsigned int len;
+       int ret;
+
+       do {
+               len = min(rx_size, GMBUS_BYTE_COUNT_MAX);
+
+               ret = gmbus_xfer_read_chunk(dev_priv, msg->addr,
+                                           buf, len, gmbus1_index);
+               if (ret)
+                       return ret;
+
+               rx_size -= len;
+               buf += len;
+       } while (rx_size != 0);
+
+       return 0;
+}
+
+static int
+gmbus_xfer_write_chunk(struct drm_i915_private *dev_priv,
+                      unsigned short addr, u8 *buf, unsigned int len)
+{
+       unsigned int chunk_size = len;
+       u32 val, loop;
+
+       val = loop = 0;
+       while (len && loop < 4) {
+               val |= *buf++ << (8 * loop++);
+               len -= 1;
+       }
+
+       I915_WRITE(GMBUS3, val);
+       I915_WRITE(GMBUS1,
+                  GMBUS_CYCLE_WAIT |
+                  (chunk_size << GMBUS_BYTE_COUNT_SHIFT) |
+                  (addr << GMBUS_SLAVE_ADDR_SHIFT) |
+                  GMBUS_SLAVE_WRITE | GMBUS_SW_RDY);
+       while (len) {
+               int ret;
+
+               val = loop = 0;
+               do {
+                       val |= *buf++ << (8 * loop);
+               } while (--len && ++loop < 4);
+
+               I915_WRITE(GMBUS3, val);
+
+               ret = gmbus_wait_hw_status(dev_priv, GMBUS_HW_RDY,
+                                          GMBUS_HW_RDY_EN);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int
+gmbus_xfer_write(struct drm_i915_private *dev_priv, struct i2c_msg *msg)
+{
+       u8 *buf = msg->buf;
+       unsigned int tx_size = msg->len;
+       unsigned int len;
+       int ret;
+
+       do {
+               len = min(tx_size, GMBUS_BYTE_COUNT_MAX);
+
+               ret = gmbus_xfer_write_chunk(dev_priv, msg->addr, buf, len);
+               if (ret)
+                       return ret;
+
+               buf += len;
+               tx_size -= len;
+       } while (tx_size != 0);
+
+       return 0;
+}
+
+/*
+ * The gmbus controller can combine a 1 or 2 byte write with a read that
+ * immediately follows it by using an "INDEX" cycle.
+ */
+static bool
+gmbus_is_index_read(struct i2c_msg *msgs, int i, int num)
+{
+       return (i + 1 < num &&
+               msgs[i].addr == msgs[i + 1].addr &&
+               !(msgs[i].flags & I2C_M_RD) &&
+               (msgs[i].len == 1 || msgs[i].len == 2) &&
+               (msgs[i + 1].flags & I2C_M_RD));
+}
+
+static int
+gmbus_xfer_index_read(struct drm_i915_private *dev_priv, struct i2c_msg *msgs)
+{
+       u32 gmbus1_index = 0;
+       u32 gmbus5 = 0;
+       int ret;
+
+       if (msgs[0].len == 2)
+               gmbus5 = GMBUS_2BYTE_INDEX_EN |
+                        msgs[0].buf[1] | (msgs[0].buf[0] << 8);
+       if (msgs[0].len == 1)
+               gmbus1_index = GMBUS_CYCLE_INDEX |
+                              (msgs[0].buf[0] << GMBUS_SLAVE_INDEX_SHIFT);
+
+       /* GMBUS5 holds 16-bit index */
+       if (gmbus5)
+               I915_WRITE(GMBUS5, gmbus5);
+
+       ret = gmbus_xfer_read(dev_priv, &msgs[1], gmbus1_index);
+
+       /* Clear GMBUS5 after each index transfer */
+       if (gmbus5)
+               I915_WRITE(GMBUS5, 0);
+
+       return ret;
+}
+
+static int
+gmbus_xfer(struct i2c_adapter *adapter,
+          struct i2c_msg *msgs,
+          int num)
+{
+       struct intel_gmbus *bus = container_of(adapter,
+                                              struct intel_gmbus,
+                                              adapter);
+       struct drm_i915_private *dev_priv = bus->dev_priv;
+       int i = 0, inc, try = 0;
+       int ret = 0;
+
+       intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS);
+       mutex_lock(&dev_priv->gmbus_mutex);
+
+       if (bus->force_bit) {
+               ret = i2c_bit_algo.master_xfer(adapter, msgs, num);
+               goto out;
+       }
+
+retry:
+       I915_WRITE(GMBUS0, bus->reg0);
+
+       for (; i < num; i += inc) {
+               inc = 1;
+               if (gmbus_is_index_read(msgs, i, num)) {
+                       ret = gmbus_xfer_index_read(dev_priv, &msgs[i]);
+                       inc = 2; /* an index read is two msgs */
+               } else if (msgs[i].flags & I2C_M_RD) {
+                       ret = gmbus_xfer_read(dev_priv, &msgs[i], 0);
+               } else {
+                       ret = gmbus_xfer_write(dev_priv, &msgs[i]);
+               }
+
+               if (ret == -ETIMEDOUT)
+                       goto timeout;
+               if (ret == -ENXIO)
+                       goto clear_err;
+
+               ret = gmbus_wait_hw_status(dev_priv, GMBUS_HW_WAIT_PHASE,
+                                          GMBUS_HW_WAIT_EN);
+               if (ret == -ENXIO)
+                       goto clear_err;
+               if (ret)
+                       goto timeout;
+       }
+
+       /* Generate a STOP condition on the bus. Note that gmbus can't generata
+        * a STOP on the very first cycle. To simplify the code we
+        * unconditionally generate the STOP condition with an additional gmbus
+        * cycle. */
+       I915_WRITE(GMBUS1, GMBUS_CYCLE_STOP | GMBUS_SW_RDY);
+
+       /* Mark the GMBUS interface as disabled after waiting for idle.
+        * We will re-enable it at the start of the next xfer,
+        * till then let it sleep.
+        */
+       if (gmbus_wait_idle(dev_priv)) {
+               DRM_DEBUG_KMS("GMBUS [%s] timed out waiting for idle\n",
+                        adapter->name);
+               ret = -ETIMEDOUT;
+       }
+       I915_WRITE(GMBUS0, 0);
+       ret = ret ?: i;
+       goto out;
+
+clear_err:
+       /*
+        * Wait for bus to IDLE before clearing NAK.
+        * If we clear the NAK while bus is still active, then it will stay
+        * active and the next transaction may fail.
+        *
+        * If no ACK is received during the address phase of a transaction, the
+        * adapter must report -ENXIO. It is not clear what to return if no ACK
+        * is received at other times. But we have to be careful to not return
+        * spurious -ENXIO because that will prevent i2c and drm edid functions
+        * from retrying. So return -ENXIO only when gmbus properly quiescents -
+        * timing out seems to happen when there _is_ a ddc chip present, but
+        * it's slow responding and only answers on the 2nd retry.
+        */
+       ret = -ENXIO;
+       if (gmbus_wait_idle(dev_priv)) {
+               DRM_DEBUG_KMS("GMBUS [%s] timed out after NAK\n",
+                             adapter->name);
+               ret = -ETIMEDOUT;
+       }
+
+       /* Toggle the Software Clear Interrupt bit. This has the effect
+        * of resetting the GMBUS controller and so clearing the
+        * BUS_ERROR raised by the slave's NAK.
+        */
+       I915_WRITE(GMBUS1, GMBUS_SW_CLR_INT);
+       I915_WRITE(GMBUS1, 0);
+       I915_WRITE(GMBUS0, 0);
+
+       DRM_DEBUG_KMS("GMBUS [%s] NAK for addr: %04x %c(%d)\n",
+                        adapter->name, msgs[i].addr,
+                        (msgs[i].flags & I2C_M_RD) ? 'r' : 'w', msgs[i].len);
+
+       /*
+        * Passive adapters sometimes NAK the first probe. Retry the first
+        * message once on -ENXIO for GMBUS transfers; the bit banging algorithm
+        * has retries internally. See also the retry loop in
+        * drm_do_probe_ddc_edid, which bails out on the first -ENXIO.
+        */
+       if (ret == -ENXIO && i == 0 && try++ == 0) {
+               DRM_DEBUG_KMS("GMBUS [%s] NAK on first message, retry\n",
+                             adapter->name);
+               goto retry;
+       }
+
+       goto out;
+
+timeout:
+       DRM_INFO("GMBUS [%s] timed out, falling back to bit banging on pin %d\n",
+                bus->adapter.name, bus->reg0 & 0xff);
+       I915_WRITE(GMBUS0, 0);
+
+       /* Hardware may not support GMBUS over these pins? Try GPIO bitbanging instead. */
+       bus->force_bit = 1;
+       ret = i2c_bit_algo.master_xfer(adapter, msgs, num);
+
+out:
+       mutex_unlock(&dev_priv->gmbus_mutex);
+
+       intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS);
+
+       return ret;
+}
+
+static u32 gmbus_func(struct i2c_adapter *adapter)
+{
+       return i2c_bit_algo.functionality(adapter) &
+               (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
+               /* I2C_FUNC_10BIT_ADDR | */
+               I2C_FUNC_SMBUS_READ_BLOCK_DATA |
+               I2C_FUNC_SMBUS_BLOCK_PROC_CALL);
+}
+
+static const struct i2c_algorithm gmbus_algorithm = {
+       .master_xfer    = gmbus_xfer,
+       .functionality  = gmbus_func
+};
+
+/**
+ * intel_gmbus_setup - instantiate all Intel i2c GMBuses
+ * @dev: DRM device
+ */
+int intel_setup_gmbus(struct drm_device *dev)
 {
        struct drm_i915_private *dev_priv = dev->dev_private;
        struct intel_gmbus *bus;
        unsigned int pin;
+       int ret;
 
        if (HAS_PCH_NOP(dev))
                return 0;
@@ -402,35 +747,58 @@ intel_setup_gmbus(struct drm_device *dev)
        else
                dev_priv->gpio_mmio_base = 0;
 
+       rw_init(&dev_priv->gmbus_mutex, "gmbus");
+       init_waitqueue_head(&dev_priv->gmbus_wait_queue);
+
        for (pin = 0; pin < ARRAY_SIZE(dev_priv->gmbus); pin++) {
                if (!intel_gmbus_is_valid_pin(dev_priv, pin))
                        continue;
 
                bus = &dev_priv->gmbus[pin];
 
+#ifdef notyet
+               bus->adapter.owner = THIS_MODULE;
+               bus->adapter.class = I2C_CLASS_DDC;
+#endif
+               snprintf(bus->adapter.name,
+                        sizeof(bus->adapter.name),
+                        "i915 gmbus %s",
+                        get_gmbus_pin(dev_priv, pin)->name);
+
+#ifdef notyet
+               bus->adapter.dev.parent = &dev->pdev->dev;
+#endif
                bus->dev_priv = dev_priv;
 
+               bus->adapter.algo = &gmbus_algorithm;
+
                /* By default use a conservative clock rate */
                bus->reg0 = pin | GMBUS_RATE_100KHZ;
 
-               bus->adapter.ic.ic_cookie = bus;
-               bus->adapter.ic.ic_acquire_bus = intel_gmbus_acquire_bus;
-               bus->adapter.ic.ic_release_bus = intel_gmbus_release_bus;
-               bus->adapter.ic.ic_exec = intel_gmbus_exec;
+               /* gmbus seems to be broken on i830 */
+               if (IS_I830(dev))
+                       bus->force_bit = 1;
 
-               bus->gpio_reg = dev_priv->gpio_mmio_base +
-                       get_gmbus_pin(dev_priv, pin)->reg;
+               intel_gpio_setup(bus, pin);
 
-               bus->adapter.ic.ic_send_start = intel_gpio_send_start;
-               bus->adapter.ic.ic_send_stop = intel_gpio_send_stop;
-               bus->adapter.ic.ic_initiate_xfer = intel_gpio_initiate_xfer;
-               bus->adapter.ic.ic_read_byte = intel_gpio_read_byte;
-               bus->adapter.ic.ic_write_byte = intel_gpio_write_byte;
+               ret = i2c_add_adapter(&bus->adapter);
+               if (ret)
+                       goto err;
        }
 
        intel_i2c_reset(dev_priv->dev);
 
-       return (0);
+       return 0;
+
+err:
+       while (pin--) {
+               if (!intel_gmbus_is_valid_pin(dev_priv, pin))
+                       continue;
+
+               bus = &dev_priv->gmbus[pin];
+               i2c_del_adapter(&bus->adapter);
+       }
+       return ret;
 }
 
 struct i2c_adapter *intel_gmbus_get_adapter(struct drm_i915_private *dev_priv,
@@ -442,25 +810,34 @@ struct i2c_adapter *intel_gmbus_get_adapter(struct drm_i915_private *dev_priv,
        return &dev_priv->gmbus[pin].adapter;
 }
 
-void
-intel_teardown_gmbus(struct drm_device *dev)
+void intel_gmbus_set_speed(struct i2c_adapter *adapter, int speed)
 {
+       struct intel_gmbus *bus = to_intel_gmbus(adapter);
+
+       bus->reg0 = (bus->reg0 & ~(0x3 << 8)) | speed;
 }
 
-void
-intel_gmbus_force_bit(struct i2c_adapter *adapter, bool force_bit)
+void intel_gmbus_force_bit(struct i2c_adapter *adapter, bool force_bit)
 {
        struct intel_gmbus *bus = to_intel_gmbus(adapter);
 
-       if (force_bit)
-               bus->force_bit++;
-       else
-               bus->force_bit--;
+       bus->force_bit += force_bit ? 1 : -1;
+       DRM_DEBUG_KMS("%sabling bit-banging on %s. force bit now %d\n",
+                     force_bit ? "en" : "dis", adapter->name,
+                     bus->force_bit);
+}
 
-       KASSERT(bus->force_bit >= 0);
+void intel_teardown_gmbus(struct drm_device *dev)
+{
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_gmbus *bus;
+       unsigned int pin;
 
-       if (bus->force_bit)
-               bus->adapter.ic.ic_exec = NULL;
-       else
-               bus->adapter.ic.ic_exec = intel_gmbus_exec;
+       for (pin = 0; pin < ARRAY_SIZE(dev_priv->gmbus); pin++) {
+               if (!intel_gmbus_is_valid_pin(dev_priv, pin))
+                       continue;
+
+               bus = &dev_priv->gmbus[pin];
+               i2c_del_adapter(&bus->adapter);
+       }
 }