diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/core/subdev/i2c/base.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/i2c/base.c | 481 |
1 files changed, 221 insertions, 260 deletions
diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c index dbfc2abf0cfe..a114a0ed7e98 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c @@ -1,5 +1,5 @@ /* - * Copyright 2012 Red Hat Inc. + * Copyright 2013 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"), @@ -22,64 +22,136 @@ * Authors: Ben Skeggs */ -#include "core/option.h" +#include <core/option.h> -#include "subdev/i2c.h" -#include "subdev/vga.h" +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/bios/i2c.h> +#include <subdev/i2c.h> +#include <subdev/vga.h> -int -nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg) +/****************************************************************************** + * interface to linux i2c bit-banging algorithm + *****************************************************************************/ + +#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT +#define CSTMSEL true +#else +#define CSTMSEL false +#endif + +static int +nouveau_i2c_pre_xfer(struct i2c_adapter *adap) { - u8 val; - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, - }; + struct i2c_algo_bit_data *bit = adap->algo_data; + struct nouveau_i2c_port *port = bit->data; + if (port->func->acquire) + port->func->acquire(port); + return 0; +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static void +nouveau_i2c_setscl(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_scl(port, state); +} - return val; +static void +nouveau_i2c_setsda(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_sda(port, state); } -int -nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val) +static int +nouveau_i2c_getscl(void *data) { - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = 0, .len = 1, .buf = &val }, - }; + struct nouveau_i2c_port *port = data; + return port->func->sense_scl(port); +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static int +nouveau_i2c_getsda(void *data) +{ + struct nouveau_i2c_port *port = data; + return port->func->sense_sda(port); +} - return 0; +/****************************************************************************** + * base i2c "port" class implementation + *****************************************************************************/ + +void +_nouveau_i2c_port_dtor(struct nouveau_object *object) +{ + struct nouveau_i2c_port *port = (void *)object; + i2c_del_adapter(&port->adapter); + nouveau_object_destroy(&port->base); } -bool -nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr) +int +nouveau_i2c_port_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, u8 index, + const struct i2c_algorithm *algo, + int size, void **pobject) { - 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, - } - }; + struct nouveau_device *device = nv_device(parent); + struct nouveau_i2c *i2c = (void *)engine; + struct nouveau_i2c_port *port; + int ret; - return i2c_transfer(&port->adapter, msgs, 2) == 2; + ret = nouveau_object_create_(parent, engine, oclass, 0, size, pobject); + port = *pobject; + if (ret) + return ret; + + snprintf(port->adapter.name, sizeof(port->adapter.name), + "nouveau-%s-%d", device->name, index); + port->adapter.owner = THIS_MODULE; + port->adapter.dev.parent = &device->pdev->dev; + port->index = index; + i2c_set_adapdata(&port->adapter, i2c); + + if ( algo == &nouveau_i2c_bit_algo && + !nouveau_boolopt(device->cfgopt, "NvI2C", CSTMSEL)) { + struct i2c_algo_bit_data *bit; + + bit = kzalloc(sizeof(*bit), GFP_KERNEL); + if (!bit) + return -ENOMEM; + + bit->udelay = 10; + bit->timeout = usecs_to_jiffies(2200); + bit->data = port; + bit->pre_xfer = nouveau_i2c_pre_xfer; + bit->setsda = nouveau_i2c_setsda; + bit->setscl = nouveau_i2c_setscl; + bit->getsda = nouveau_i2c_getsda; + bit->getscl = nouveau_i2c_getscl; + + port->adapter.algo_data = bit; + ret = i2c_bit_add_bus(&port->adapter); + } else { + port->adapter.algo_data = port; + port->adapter.algo = algo; + ret = i2c_add_adapter(&port->adapter); + } + + /* drop port's i2c subdev refcount, i2c handles this itself */ + if (ret == 0) { + list_add_tail(&port->head, &i2c->ports); + atomic_dec(&engine->refcount); + } + + return ret; } +/****************************************************************************** + * base i2c subdev class implementation + *****************************************************************************/ + static struct nouveau_i2c_port * nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) { @@ -103,29 +175,23 @@ nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) list_for_each_entry(port, &i2c->ports, head) { if (port->index == index) - break; + return port; } - if (&port->head == &i2c->ports) - return NULL; + 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; - } +static struct nouveau_i2c_port * +nouveau_i2c_find_type(struct nouveau_i2c *i2c, u16 type) +{ + struct nouveau_i2c_port *port; - /* 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); + list_for_each_entry(port, &i2c->ports, head) { + if (nv_hclass(port) == type) + return port; } - return port; + return NULL; } static int @@ -155,109 +221,86 @@ nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what, return -ENODEV; } -void -nouveau_i2c_drive_scl(void *data, int state) +int +_nouveau_i2c_fini(struct nouveau_object *object, bool suspend) { - struct nouveau_i2c_port *port = data; + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; - 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); + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->fini(nv_object(port), suspend); + if (ret && suspend) + goto fail; } -} - -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); + return nouveau_subdev_fini(&i2c->base, suspend); +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->init(nv_object(port)); } + + return ret; } int -nouveau_i2c_sense_scl(void *data) +_nouveau_i2c_init(struct nouveau_object *object) { - 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); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; + + ret = nouveau_subdev_init(&i2c->base); + if (ret == 0) { + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->init(nv_object(port)); + if (ret) + goto fail; + } } - return 0; + return ret; +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->fini(nv_object(port), false); + } + + return ret; } -int -nouveau_i2c_sense_sda(void *data) +void +_nouveau_i2c_dtor(struct nouveau_object *object) { - 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); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port, *temp; + + list_for_each_entry_safe(port, temp, &i2c->ports, head) { + nouveau_object_ref(NULL, (struct nouveau_object **)&port); } - return 0; + nouveau_subdev_destroy(&i2c->base); } -static const u32 nv50_i2c_port[] = { - 0x00e138, 0x00e150, 0x00e168, 0x00e180, - 0x00e254, 0x00e274, 0x00e764, 0x00e780, - 0x00e79c, 0x00e7b8 +static struct nouveau_oclass * +nouveau_i2c_extdev_sclass[] = { + nouveau_anx9805_sclass, }; -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) +int +nouveau_i2c_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, + struct nouveau_oclass *sclass, + int length, void **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 nouveau_object *object; struct dcb_i2c_entry info; - int ret, i = -1; + int ret, i, j, index = -1; + struct dcb_output outp; + u8 ver, hdr; + u32 data; ret = nouveau_subdev_create(parent, engine, oclass, 0, "I2C", "i2c", &i2c); @@ -266,142 +309,60 @@ nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, return ret; i2c->find = nouveau_i2c_find; + i2c->find_type = nouveau_i2c_find_type; i2c->identify = nouveau_i2c_identify; INIT_LIST_HEAD(&i2c->ports); - while (!dcb_i2c_parse(bios, ++i, &info)) { + while (!dcb_i2c_parse(bios, ++index, &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 (port->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; + oclass = sclass; + do { + ret = -EINVAL; + if (oclass->handle == info.type) { + ret = nouveau_object_ctor(*pobject, *pobject, + oclass, &info, + index, &object); } + } while (ret && (++oclass)->handle); + } + + /* in addition to the busses specified in the i2c table, there + * may be ddc/aux channels hiding behind external tmds/dp/etc + * transmitters. + */ + index = ((index + 0x0f) / 0x10) * 0x10; + i = -1; + while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &outp))) { + if (!outp.location || !outp.extdev) + continue; + + switch (outp.type) { + case DCB_OUTPUT_TMDS: + info.type = NV_I2C_TYPE_EXTDDC(outp.extdev); break; - case DCB_I2C_NVIO_AUX: - port->drive = info.drive & 0x0f; - port->sense = port->drive; - port->adapter.algo = &nouveau_i2c_aux_algo; + case DCB_OUTPUT_DP: + info.type = NV_I2C_TYPE_EXTAUX(outp.extdev); 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; + ret = -ENODEV; + j = -1; + while (ret && ++j < ARRAY_SIZE(nouveau_i2c_extdev_sclass)) { + parent = nv_object(i2c->find(i2c, outp.i2c_index)); + oclass = nouveau_i2c_extdev_sclass[j]; + do { + if (oclass->handle != info.type) + continue; + ret = nouveau_object_ctor(parent, *pobject, + oclass, NULL, + index++, &object); + } while (ret && (++oclass)->handle); } - - 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, - }, -}; |