aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/core/subdev/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau/core/subdev/i2c')
-rw-r--r--drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c212
-rw-r--r--drivers/gpu/drm/nouveau/core/subdev/i2c/base.c407
-rw-r--r--drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c230
3 files changed, 849 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c
new file mode 100644
index 000000000000..fe1ebf199ba9
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2009 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Ben Skeggs
+ */
+
+#include <subdev/i2c.h>
+
+/******************************************************************************
+ * aux channel util functions
+ *****************************************************************************/
+#define AUX_DBG(fmt, args...) nv_debug(aux, "AUXCH(%d): " fmt, ch, ##args)
+#define AUX_ERR(fmt, args...) nv_error(aux, "AUXCH(%d): " fmt, ch, ##args)
+
+static void
+auxch_fini(struct nouveau_i2c *aux, int ch)
+{
+ nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000);
+}
+
+static int
+auxch_init(struct nouveau_i2c *aux, int ch)
+{
+ const u32 unksel = 1; /* nfi which to use, or if it matters.. */
+ const u32 ureq = unksel ? 0x00100000 : 0x00200000;
+ const u32 urep = unksel ? 0x01000000 : 0x02000000;
+ u32 ctrl, timeout;
+
+ /* wait up to 1ms for any previous transaction to be done... */
+ timeout = 1000;
+ do {
+ ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50));
+ udelay(1);
+ if (!timeout--) {
+ AUX_ERR("begin idle timeout 0x%08x", ctrl);
+ return -EBUSY;
+ }
+ } while (ctrl & 0x03010000);
+
+ /* set some magic, and wait up to 1ms for it to appear */
+ nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq);
+ timeout = 1000;
+ do {
+ ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50));
+ udelay(1);
+ if (!timeout--) {
+ AUX_ERR("magic wait 0x%08x\n", ctrl);
+ auxch_fini(aux, ch);
+ return -EBUSY;
+ }
+ } while ((ctrl & 0x03000000) != urep);
+
+ return 0;
+}
+
+static int
+auxch_tx(struct nouveau_i2c *aux, int ch, u8 type, u32 addr, u8 *data, u8 size)
+{
+ u32 ctrl, stat, timeout, retries;
+ u32 xbuf[4] = {};
+ int ret, i;
+
+ AUX_DBG("%d: 0x%08x %d\n", type, addr, size);
+
+ ret = auxch_init(aux, ch);
+ if (ret)
+ goto out;
+
+ stat = nv_rd32(aux, 0x00e4e8 + (ch * 0x50));
+ if (!(stat & 0x10000000)) {
+ AUX_DBG("sink not detected\n");
+ ret = -ENXIO;
+ goto out;
+ }
+
+ if (!(type & 1)) {
+ memcpy(xbuf, data, size);
+ for (i = 0; i < 16; i += 4) {
+ AUX_DBG("wr 0x%08x\n", xbuf[i / 4]);
+ nv_wr32(aux, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]);
+ }
+ }
+
+ ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50));
+ ctrl &= ~0x0001f0ff;
+ ctrl |= type << 12;
+ ctrl |= size - 1;
+ nv_wr32(aux, 0x00e4e0 + (ch * 0x50), addr);
+
+ /* retry transaction a number of times on failure... */
+ ret = -EREMOTEIO;
+ for (retries = 0; retries < 32; retries++) {
+ /* reset, and delay a while if this is a retry */
+ nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl);
+ nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl);
+ if (retries)
+ udelay(400);
+
+ /* transaction request, wait up to 1ms for it to complete */
+ nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl);
+
+ timeout = 1000;
+ do {
+ ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50));
+ udelay(1);
+ if (!timeout--) {
+ AUX_ERR("tx req timeout 0x%08x\n", ctrl);
+ goto out;
+ }
+ } while (ctrl & 0x00010000);
+
+ /* read status, and check if transaction completed ok */
+ stat = nv_mask(aux, 0x00e4e8 + (ch * 0x50), 0, 0);
+ if (!(stat & 0x000f0f00)) {
+ ret = 0;
+ break;
+ }
+
+ AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat);
+ }
+
+ if (type & 1) {
+ for (i = 0; i < 16; i += 4) {
+ xbuf[i / 4] = nv_rd32(aux, 0x00e4d0 + (ch * 0x50) + i);
+ AUX_DBG("rd 0x%08x\n", xbuf[i / 4]);
+ }
+ memcpy(data, xbuf, size);
+ }
+
+out:
+ auxch_fini(aux, ch);
+ return ret;
+}
+
+int
+nv_rdaux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size)
+{
+ return auxch_tx(auxch->i2c, auxch->drive, 9, addr, data, size);
+}
+
+int
+nv_wraux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size)
+{
+ return auxch_tx(auxch->i2c, auxch->drive, 8, addr, data, size);
+}
+
+static int
+aux_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct nouveau_i2c_port *auxch = (struct nouveau_i2c_port *)adap;
+ struct i2c_msg *msg = msgs;
+ int ret, mcnt = num;
+
+ while (mcnt--) {
+ u8 remaining = msg->len;
+ u8 *ptr = msg->buf;
+
+ while (remaining) {
+ u8 cnt = (remaining > 16) ? 16 : remaining;
+ u8 cmd;
+
+ if (msg->flags & I2C_M_RD)
+ cmd = 1;
+ else
+ cmd = 0;
+
+ if (mcnt || remaining > 16)
+ cmd |= 4; /* MOT */
+
+ ret = auxch_tx(auxch->i2c, auxch->drive, cmd,
+ msg->addr, ptr, cnt);
+ if (ret < 0)
+ return ret;
+
+ ptr += cnt;
+ remaining -= cnt;
+ }
+
+ msg++;
+ }
+
+ return num;
+}
+
+static u32
+aux_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+const struct i2c_algorithm nouveau_i2c_aux_algo = {
+ .master_xfer = aux_xfer,
+ .functionality = aux_func
+};
diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
new file mode 100644
index 000000000000..3d2c88310f98
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Ben Skeggs
+ */
+
+#include "core/option.h"
+
+#include "subdev/i2c.h"
+#include "subdev/vga.h"
+
+int
+nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg)
+{
+ u8 val;
+ struct i2c_msg msgs[] = {
+ { .addr = addr, .flags = 0, .len = 1, .buf = &reg },
+ { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val },
+ };
+
+ int ret = i2c_transfer(&port->adapter, msgs, 2);
+ if (ret != 2)
+ return -EIO;
+
+ return val;
+}
+
+int
+nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val)
+{
+ struct i2c_msg msgs[] = {
+ { .addr = addr, .flags = 0, .len = 1, .buf = &reg },
+ { .addr = addr, .flags = 0, .len = 1, .buf = &val },
+ };
+
+ int ret = i2c_transfer(&port->adapter, msgs, 2);
+ if (ret != 2)
+ return -EIO;
+
+ return 0;
+}
+
+bool
+nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr)
+{
+ u8 buf[] = { 0 };
+ struct i2c_msg msgs[] = {
+ {
+ .addr = addr,
+ .flags = 0,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .len = 1,
+ .buf = buf,
+ }
+ };
+
+ return i2c_transfer(&port->adapter, msgs, 2) == 2;
+}
+
+static struct nouveau_i2c_port *
+nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index)
+{
+ struct nouveau_bios *bios = nouveau_bios(i2c);
+ struct nouveau_i2c_port *port;
+
+ if (index == NV_I2C_DEFAULT(0) ||
+ index == NV_I2C_DEFAULT(1)) {
+ u8 ver, hdr, cnt, len;
+ u16 i2c = dcb_i2c_table(bios, &ver, &hdr, &cnt, &len);
+ if (i2c && ver >= 0x30) {
+ u8 auxidx = nv_ro08(bios, i2c + 4);
+ if (index == NV_I2C_DEFAULT(0))
+ index = (auxidx & 0x0f) >> 0;
+ else
+ index = (auxidx & 0xf0) >> 4;
+ } else {
+ index = 2;
+ }
+ }
+
+ list_for_each_entry(port, &i2c->ports, head) {
+ if (port->index == index)
+ break;
+ }
+
+ if (&port->head == &i2c->ports)
+ return NULL;
+
+ if (nv_device(i2c)->card_type >= NV_50 && (port->dcb & 0x00000100)) {
+ u32 reg = 0x00e500, val;
+ if (port->type == 6) {
+ reg += port->drive * 0x50;
+ val = 0x2002;
+ } else {
+ reg += ((port->dcb & 0x1e00) >> 9) * 0x50;
+ val = 0xe001;
+ }
+
+ /* nfi, but neither auxch or i2c work if it's 1 */
+ nv_mask(i2c, reg + 0x0c, 0x00000001, 0x00000000);
+ /* nfi, but switches auxch vs normal i2c */
+ nv_mask(i2c, reg + 0x00, 0x0000f003, val);
+ }
+
+ return port;
+}
+
+static int
+nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what,
+ struct i2c_board_info *info,
+ bool (*match)(struct nouveau_i2c_port *,
+ struct i2c_board_info *))
+{
+ struct nouveau_i2c_port *port = nouveau_i2c_find(i2c, index);
+ int i;
+
+ if (!port) {
+ nv_debug(i2c, "no bus when probing %s on %d\n", what, index);
+ return -ENODEV;
+ }
+
+ nv_debug(i2c, "probing %ss on bus: %d\n", what, port->index);
+ for (i = 0; info[i].addr; i++) {
+ if (nv_probe_i2c(port, info[i].addr) &&
+ (!match || match(port, &info[i]))) {
+ nv_info(i2c, "detected %s: %s\n", what, info[i].type);
+ return i;
+ }
+ }
+
+ nv_debug(i2c, "no devices found.\n");
+ return -ENODEV;
+}
+
+void
+nouveau_i2c_drive_scl(void *data, int state)
+{
+ struct nouveau_i2c_port *port = data;
+
+ if (port->type == DCB_I2C_NV04_BIT) {
+ u8 val = nv_rdvgac(port->i2c, 0, port->drive);
+ if (state) val |= 0x20;
+ else val &= 0xdf;
+ nv_wrvgac(port->i2c, 0, port->drive, val | 0x01);
+ } else
+ if (port->type == DCB_I2C_NV4E_BIT) {
+ nv_mask(port->i2c, port->drive, 0x2f, state ? 0x21 : 0x01);
+ } else
+ if (port->type == DCB_I2C_NVIO_BIT) {
+ if (state) port->state |= 0x01;
+ else port->state &= 0xfe;
+ nv_wr32(port->i2c, port->drive, 4 | port->state);
+ }
+}
+
+void
+nouveau_i2c_drive_sda(void *data, int state)
+{
+ struct nouveau_i2c_port *port = data;
+
+ if (port->type == DCB_I2C_NV04_BIT) {
+ u8 val = nv_rdvgac(port->i2c, 0, port->drive);
+ if (state) val |= 0x10;
+ else val &= 0xef;
+ nv_wrvgac(port->i2c, 0, port->drive, val | 0x01);
+ } else
+ if (port->type == DCB_I2C_NV4E_BIT) {
+ nv_mask(port->i2c, port->drive, 0x1f, state ? 0x11 : 0x01);
+ } else
+ if (port->type == DCB_I2C_NVIO_BIT) {
+ if (state) port->state |= 0x02;
+ else port->state &= 0xfd;
+ nv_wr32(port->i2c, port->drive, 4 | port->state);
+ }
+}
+
+int
+nouveau_i2c_sense_scl(void *data)
+{
+ struct nouveau_i2c_port *port = data;
+ struct nouveau_device *device = nv_device(port->i2c);
+
+ if (port->type == DCB_I2C_NV04_BIT) {
+ return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x04);
+ } else
+ if (port->type == DCB_I2C_NV4E_BIT) {
+ return !!(nv_rd32(port->i2c, port->sense) & 0x00040000);
+ } else
+ if (port->type == DCB_I2C_NVIO_BIT) {
+ if (device->card_type < NV_D0)
+ return !!(nv_rd32(port->i2c, port->sense) & 0x01);
+ else
+ return !!(nv_rd32(port->i2c, port->sense) & 0x10);
+ }
+
+ return 0;
+}
+
+int
+nouveau_i2c_sense_sda(void *data)
+{
+ struct nouveau_i2c_port *port = data;
+ struct nouveau_device *device = nv_device(port->i2c);
+
+ if (port->type == DCB_I2C_NV04_BIT) {
+ return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x08);
+ } else
+ if (port->type == DCB_I2C_NV4E_BIT) {
+ return !!(nv_rd32(port->i2c, port->sense) & 0x00080000);
+ } else
+ if (port->type == DCB_I2C_NVIO_BIT) {
+ if (device->card_type < NV_D0)
+ return !!(nv_rd32(port->i2c, port->sense) & 0x02);
+ else
+ return !!(nv_rd32(port->i2c, port->sense) & 0x20);
+ }
+
+ return 0;
+}
+
+static const u32 nv50_i2c_port[] = {
+ 0x00e138, 0x00e150, 0x00e168, 0x00e180,
+ 0x00e254, 0x00e274, 0x00e764, 0x00e780,
+ 0x00e79c, 0x00e7b8
+};
+
+static int
+nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
+ struct nouveau_oclass *oclass, void *data, u32 size,
+ struct nouveau_object **pobject)
+{
+ struct nouveau_device *device = nv_device(parent);
+ struct nouveau_bios *bios = nouveau_bios(parent);
+ struct nouveau_i2c_port *port;
+ struct nouveau_i2c *i2c;
+ struct dcb_i2c_entry info;
+ int ret, i = -1;
+
+ ret = nouveau_subdev_create(parent, engine, oclass, 0,
+ "I2C", "i2c", &i2c);
+ *pobject = nv_object(i2c);
+ if (ret)
+ return ret;
+
+ i2c->find = nouveau_i2c_find;
+ i2c->identify = nouveau_i2c_identify;
+ INIT_LIST_HEAD(&i2c->ports);
+
+ while (!dcb_i2c_parse(bios, ++i, &info)) {
+ if (info.type == DCB_I2C_UNUSED)
+ continue;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ nv_error(i2c, "failed port memory alloc at %d\n", i);
+ break;
+ }
+
+ port->type = info.type;
+ switch (port->type) {
+ case DCB_I2C_NV04_BIT:
+ port->drive = info.drive;
+ port->sense = info.sense;
+ break;
+ case DCB_I2C_NV4E_BIT:
+ port->drive = 0x600800 + info.drive;
+ port->sense = port->drive;
+ break;
+ case DCB_I2C_NVIO_BIT:
+ port->drive = info.drive & 0x0f;
+ if (device->card_type < NV_D0) {
+ if (info.drive >= ARRAY_SIZE(nv50_i2c_port))
+ break;
+ port->drive = nv50_i2c_port[port->drive];
+ port->sense = port->drive;
+ } else {
+ port->drive = 0x00d014 + (port->drive * 0x20);
+ port->sense = port->drive;
+ }
+ break;
+ case DCB_I2C_NVIO_AUX:
+ port->drive = info.drive & 0x0f;
+ port->sense = port->drive;
+ port->adapter.algo = &nouveau_i2c_aux_algo;
+ break;
+ default:
+ break;
+ }
+
+ if (!port->adapter.algo && !port->drive) {
+ nv_error(i2c, "I2C%d: type %d index %x/%x unknown\n",
+ i, port->type, port->drive, port->sense);
+ kfree(port);
+ continue;
+ }
+
+ snprintf(port->adapter.name, sizeof(port->adapter.name),
+ "nouveau-%s-%d", device->name, i);
+ port->adapter.owner = THIS_MODULE;
+ port->adapter.dev.parent = &device->pdev->dev;
+ port->i2c = i2c;
+ port->index = i;
+ port->dcb = info.data;
+ i2c_set_adapdata(&port->adapter, i2c);
+
+ if (port->adapter.algo != &nouveau_i2c_aux_algo) {
+ nouveau_i2c_drive_scl(port, 0);
+ nouveau_i2c_drive_sda(port, 1);
+ nouveau_i2c_drive_scl(port, 1);
+
+#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT
+ if (nouveau_boolopt(device->cfgopt, "NvI2C", true)) {
+#else
+ if (nouveau_boolopt(device->cfgopt, "NvI2C", false)) {
+#endif
+ port->adapter.algo = &nouveau_i2c_bit_algo;
+ ret = i2c_add_adapter(&port->adapter);
+ } else {
+ port->adapter.algo_data = &port->bit;
+ port->bit.udelay = 10;
+ port->bit.timeout = usecs_to_jiffies(2200);
+ port->bit.data = port;
+ port->bit.setsda = nouveau_i2c_drive_sda;
+ port->bit.setscl = nouveau_i2c_drive_scl;
+ port->bit.getsda = nouveau_i2c_sense_sda;
+ port->bit.getscl = nouveau_i2c_sense_scl;
+ ret = i2c_bit_add_bus(&port->adapter);
+ }
+ } else {
+ port->adapter.algo = &nouveau_i2c_aux_algo;
+ ret = i2c_add_adapter(&port->adapter);
+ }
+
+ if (ret) {
+ nv_error(i2c, "I2C%d: failed register: %d\n", i, ret);
+ kfree(port);
+ continue;
+ }
+
+ list_add_tail(&port->head, &i2c->ports);
+ }
+
+ return 0;
+}
+
+static void
+nouveau_i2c_dtor(struct nouveau_object *object)
+{
+ struct nouveau_i2c *i2c = (void *)object;
+ struct nouveau_i2c_port *port, *temp;
+
+ list_for_each_entry_safe(port, temp, &i2c->ports, head) {
+ i2c_del_adapter(&port->adapter);
+ list_del(&port->head);
+ kfree(port);
+ }
+
+ nouveau_subdev_destroy(&i2c->base);
+}
+
+static int
+nouveau_i2c_init(struct nouveau_object *object)
+{
+ struct nouveau_i2c *i2c = (void *)object;
+ return nouveau_subdev_init(&i2c->base);
+}
+
+static int
+nouveau_i2c_fini(struct nouveau_object *object, bool suspend)
+{
+ struct nouveau_i2c *i2c = (void *)object;
+ return nouveau_subdev_fini(&i2c->base, suspend);
+}
+
+struct nouveau_oclass
+nouveau_i2c_oclass = {
+ .handle = NV_SUBDEV(I2C, 0x00),
+ .ofuncs = &(struct nouveau_ofuncs) {
+ .ctor = nouveau_i2c_ctor,
+ .dtor = nouveau_i2c_dtor,
+ .init = nouveau_i2c_init,
+ .fini = nouveau_i2c_fini,
+ },
+};
diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c
new file mode 100644
index 000000000000..1c4c9a5c8e2e
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Ben Skeggs
+ */
+
+#include "subdev/i2c.h"
+
+#ifdef CONFIG_NOUVEAU_I2C_INTERNAL
+#define T_TIMEOUT 2200000
+#define T_RISEFALL 1000
+#define T_HOLD 5000
+
+static inline void
+i2c_drive_scl(struct nouveau_i2c_port *port, int state)
+{
+ nouveau_i2c_drive_scl(port, state);
+}
+
+static inline void
+i2c_drive_sda(struct nouveau_i2c_port *port, int state)
+{
+ nouveau_i2c_drive_sda(port, state);
+}
+
+static inline int
+i2c_sense_scl(struct nouveau_i2c_port *port)
+{
+ return nouveau_i2c_sense_scl(port);
+}
+
+static inline int
+i2c_sense_sda(struct nouveau_i2c_port *port)
+{
+ return nouveau_i2c_sense_sda(port);
+}
+
+static void
+i2c_delay(struct nouveau_i2c_port *port, u32 nsec)
+{
+ udelay((nsec + 500) / 1000);
+}
+
+static bool
+i2c_raise_scl(struct nouveau_i2c_port *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_port *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_port *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_port *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_port *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_port *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_port *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_port *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_port *port = (struct nouveau_i2c_port *)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;
+}
+#else
+static int
+i2c_bit_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ return -ENODEV;
+}
+#endif
+
+static u32
+i2c_bit_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+const struct i2c_algorithm nouveau_i2c_bit_algo = {
+ .master_xfer = i2c_bit_xfer,
+ .functionality = i2c_bit_func
+};