|
@@ -22,330 +22,93 @@
|
|
|
* Authors: Ben Skeggs
|
|
|
*/
|
|
|
#include "priv.h"
|
|
|
+#include "aux.h"
|
|
|
+#include "bus.h"
|
|
|
#include "pad.h"
|
|
|
|
|
|
#include <core/notify.h>
|
|
|
#include <core/option.h>
|
|
|
#include <subdev/bios.h>
|
|
|
#include <subdev/bios/dcb.h>
|
|
|
+#include <subdev/bios/i2c.h>
|
|
|
|
|
|
-/******************************************************************************
|
|
|
- * interface to linux i2c bit-banging algorithm
|
|
|
- *****************************************************************************/
|
|
|
-
|
|
|
-#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT
|
|
|
-#define CSTMSEL true
|
|
|
-#else
|
|
|
-#define CSTMSEL false
|
|
|
-#endif
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_pre_xfer(struct i2c_adapter *adap)
|
|
|
-{
|
|
|
- struct i2c_algo_bit_data *bit = adap->algo_data;
|
|
|
- struct nvkm_i2c_port *port = bit->data;
|
|
|
- return nvkm_i2c(port)->acquire(port, bit->timeout);
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_post_xfer(struct i2c_adapter *adap)
|
|
|
-{
|
|
|
- struct i2c_algo_bit_data *bit = adap->algo_data;
|
|
|
- struct nvkm_i2c_port *port = bit->data;
|
|
|
- return nvkm_i2c(port)->release(port);
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_setscl(void *data, int state)
|
|
|
-{
|
|
|
- struct nvkm_i2c_port *port = data;
|
|
|
- port->func->drive_scl(port, state);
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_setsda(void *data, int state)
|
|
|
-{
|
|
|
- struct nvkm_i2c_port *port = data;
|
|
|
- port->func->drive_sda(port, state);
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_getscl(void *data)
|
|
|
+static struct nvkm_i2c_pad *
|
|
|
+nvkm_i2c_pad_find(struct nvkm_i2c *i2c, int id)
|
|
|
{
|
|
|
- struct nvkm_i2c_port *port = data;
|
|
|
- return port->func->sense_scl(port);
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_getsda(void *data)
|
|
|
-{
|
|
|
- struct nvkm_i2c_port *port = data;
|
|
|
- return port->func->sense_sda(port);
|
|
|
-}
|
|
|
-
|
|
|
-/******************************************************************************
|
|
|
- * base i2c "port" class implementation
|
|
|
- *****************************************************************************/
|
|
|
-
|
|
|
-int
|
|
|
-_nvkm_i2c_port_fini(struct nvkm_object *object, bool suspend)
|
|
|
-{
|
|
|
- struct nvkm_i2c_port *port = (void *)object;
|
|
|
- struct nvkm_i2c_pad *pad = nvkm_i2c_pad(port);
|
|
|
- nv_ofuncs(pad)->fini(nv_object(pad), suspend);
|
|
|
- return nvkm_object_fini(&port->base, suspend);
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-_nvkm_i2c_port_dtor(struct nvkm_object *object)
|
|
|
-{
|
|
|
- struct nvkm_i2c_port *port = (void *)object;
|
|
|
- i2c_del_adapter(&port->adapter);
|
|
|
- nvkm_object_destroy(&port->base);
|
|
|
-}
|
|
|
-
|
|
|
-int
|
|
|
-nvkm_i2c_port_create_(struct nvkm_object *parent, struct nvkm_object *engine,
|
|
|
- struct nvkm_oclass *oclass, u8 index,
|
|
|
- const struct i2c_algorithm *algo,
|
|
|
- const struct nvkm_i2c_func *func,
|
|
|
- int size, void **pobject)
|
|
|
-{
|
|
|
- struct nvkm_device *device = nv_device(parent);
|
|
|
- struct nvkm_i2c *i2c = nvkm_i2c(parent);
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
- int ret;
|
|
|
+ struct nvkm_i2c_pad *pad;
|
|
|
|
|
|
- ret = nvkm_object_create_(parent, engine, oclass, 0, size, pobject);
|
|
|
- port = *pobject;
|
|
|
- if (ret)
|
|
|
- return ret;
|
|
|
-
|
|
|
- snprintf(port->adapter.name, sizeof(port->adapter.name),
|
|
|
- "nvkm-%s-%d", device->name, index);
|
|
|
- port->adapter.owner = THIS_MODULE;
|
|
|
- port->adapter.dev.parent = nv_device_base(device);
|
|
|
- port->index = index;
|
|
|
- port->aux = -1;
|
|
|
- port->func = func;
|
|
|
- mutex_init(&port->mutex);
|
|
|
-
|
|
|
- if ( algo == &nvkm_i2c_bit_algo &&
|
|
|
- !nvkm_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 = nvkm_i2c_pre_xfer;
|
|
|
- bit->post_xfer = nvkm_i2c_post_xfer;
|
|
|
- bit->setsda = nvkm_i2c_setsda;
|
|
|
- bit->setscl = nvkm_i2c_setscl;
|
|
|
- bit->getsda = nvkm_i2c_getsda;
|
|
|
- bit->getscl = nvkm_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);
|
|
|
+ list_for_each_entry(pad, &i2c->pad, head) {
|
|
|
+ if (pad->id == id)
|
|
|
+ return pad;
|
|
|
}
|
|
|
|
|
|
- if (ret == 0)
|
|
|
- list_add_tail(&port->head, &i2c->ports);
|
|
|
- return ret;
|
|
|
+ return NULL;
|
|
|
}
|
|
|
|
|
|
-/******************************************************************************
|
|
|
- * base i2c subdev class implementation
|
|
|
- *****************************************************************************/
|
|
|
-
|
|
|
-static struct nvkm_i2c_port *
|
|
|
-nvkm_i2c_find(struct nvkm_i2c *i2c, u8 index)
|
|
|
+struct nvkm_i2c_bus *
|
|
|
+nvkm_i2c_bus_find(struct nvkm_i2c *i2c, int id)
|
|
|
{
|
|
|
- struct nvkm_bios *bios = nvkm_bios(i2c);
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
+ struct nvkm_bios *bios = i2c->subdev.device->bios;
|
|
|
+ struct nvkm_i2c_bus *bus;
|
|
|
|
|
|
- if (index == NV_I2C_DEFAULT(0) ||
|
|
|
- index == NV_I2C_DEFAULT(1)) {
|
|
|
+ if (id == NVKM_I2C_BUS_PRI || id == NVKM_I2C_BUS_SEC) {
|
|
|
u8 ver, hdr, cnt, len;
|
|
|
u16 i2c = dcb_i2c_table(bios, &ver, &hdr, &cnt, &len);
|
|
|
if (i2c && ver >= 0x30) {
|
|
|
u8 auxidx = nvbios_rd08(bios, i2c + 4);
|
|
|
- if (index == NV_I2C_DEFAULT(0))
|
|
|
- index = (auxidx & 0x0f) >> 0;
|
|
|
+ if (id == NVKM_I2C_BUS_PRI)
|
|
|
+ id = NVKM_I2C_BUS_CCB((auxidx & 0x0f) >> 0);
|
|
|
else
|
|
|
- index = (auxidx & 0xf0) >> 4;
|
|
|
+ id = NVKM_I2C_BUS_CCB((auxidx & 0xf0) >> 4);
|
|
|
} else {
|
|
|
- index = 2;
|
|
|
+ id = NVKM_I2C_BUS_CCB(2);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- list_for_each_entry(port, &i2c->ports, head) {
|
|
|
- if (port->index == index)
|
|
|
- return port;
|
|
|
+ list_for_each_entry(bus, &i2c->bus, head) {
|
|
|
+ if (bus->id == id)
|
|
|
+ return bus;
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
-static struct nvkm_i2c_port *
|
|
|
-nvkm_i2c_find_type(struct nvkm_i2c *i2c, u16 type)
|
|
|
+struct nvkm_i2c_aux *
|
|
|
+nvkm_i2c_aux_find(struct nvkm_i2c *i2c, int id)
|
|
|
{
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
+ struct nvkm_i2c_aux *aux;
|
|
|
|
|
|
- list_for_each_entry(port, &i2c->ports, head) {
|
|
|
- if (nv_hclass(port) == type)
|
|
|
- return port;
|
|
|
+ list_for_each_entry(aux, &i2c->aux, head) {
|
|
|
+ if (aux->id == id)
|
|
|
+ return aux;
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
-nvkm_i2c_release_pad(struct nvkm_i2c_port *port)
|
|
|
-{
|
|
|
- struct nvkm_i2c_pad *pad = nvkm_i2c_pad(port);
|
|
|
- struct nvkm_i2c *i2c = nvkm_i2c(port);
|
|
|
-
|
|
|
- if (atomic_dec_and_test(&nv_object(pad)->usecount)) {
|
|
|
- nv_ofuncs(pad)->fini(nv_object(pad), false);
|
|
|
- wake_up_all(&i2c->wait);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_try_acquire_pad(struct nvkm_i2c_port *port)
|
|
|
-{
|
|
|
- struct nvkm_i2c_pad *pad = nvkm_i2c_pad(port);
|
|
|
-
|
|
|
- if (atomic_add_return(1, &nv_object(pad)->usecount) != 1) {
|
|
|
- struct nvkm_object *owner = (void *)pad->port;
|
|
|
- do {
|
|
|
- if (owner == (void *)port)
|
|
|
- return 0;
|
|
|
- owner = owner->parent;
|
|
|
- } while(owner);
|
|
|
- nvkm_i2c_release_pad(port);
|
|
|
- return -EBUSY;
|
|
|
- }
|
|
|
-
|
|
|
- pad->next = port;
|
|
|
- nv_ofuncs(pad)->init(nv_object(pad));
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_acquire_pad(struct nvkm_i2c_port *port, unsigned long timeout)
|
|
|
-{
|
|
|
- struct nvkm_i2c *i2c = nvkm_i2c(port);
|
|
|
-
|
|
|
- if (timeout) {
|
|
|
- if (wait_event_timeout(i2c->wait,
|
|
|
- nvkm_i2c_try_acquire_pad(port) == 0,
|
|
|
- timeout) == 0)
|
|
|
- return -EBUSY;
|
|
|
- } else {
|
|
|
- wait_event(i2c->wait, nvkm_i2c_try_acquire_pad(port) == 0);
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_release(struct nvkm_i2c_port *port)
|
|
|
-__releases(pad->mutex)
|
|
|
-{
|
|
|
- nvkm_i2c(port)->release_pad(port);
|
|
|
- mutex_unlock(&port->mutex);
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_acquire(struct nvkm_i2c_port *port, unsigned long timeout)
|
|
|
-__acquires(pad->mutex)
|
|
|
-{
|
|
|
- int ret;
|
|
|
- mutex_lock(&port->mutex);
|
|
|
- if ((ret = nvkm_i2c(port)->acquire_pad(port, timeout)))
|
|
|
- mutex_unlock(&port->mutex);
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-nvkm_i2c_identify(struct nvkm_i2c *i2c, int index, const char *what,
|
|
|
- struct nvkm_i2c_board_info *info,
|
|
|
- bool (*match)(struct nvkm_i2c_port *,
|
|
|
- struct i2c_board_info *, void *), void *data)
|
|
|
-{
|
|
|
- struct nvkm_subdev *subdev = &i2c->subdev;
|
|
|
- struct nvkm_i2c_port *port = nvkm_i2c_find(i2c, index);
|
|
|
- int i;
|
|
|
-
|
|
|
- if (!port) {
|
|
|
- nvkm_debug(subdev, "no bus when probing %s on %d\n",
|
|
|
- what, index);
|
|
|
- return -ENODEV;
|
|
|
- }
|
|
|
-
|
|
|
- nvkm_debug(subdev, "probing %ss on bus: %d\n", what, port->index);
|
|
|
- for (i = 0; info[i].dev.addr; i++) {
|
|
|
- u8 orig_udelay = 0;
|
|
|
-
|
|
|
- if ((port->adapter.algo == &i2c_bit_algo) &&
|
|
|
- (info[i].udelay != 0)) {
|
|
|
- struct i2c_algo_bit_data *algo = port->adapter.algo_data;
|
|
|
- nvkm_debug(subdev,
|
|
|
- "using custom udelay %d instead of %d\n",
|
|
|
- info[i].udelay, algo->udelay);
|
|
|
- orig_udelay = algo->udelay;
|
|
|
- algo->udelay = info[i].udelay;
|
|
|
- }
|
|
|
-
|
|
|
- if (nv_probe_i2c(port, info[i].dev.addr) &&
|
|
|
- (!match || match(port, &info[i].dev, data))) {
|
|
|
- nvkm_info(subdev, "detected %s: %s\n", what,
|
|
|
- info[i].dev.type);
|
|
|
- return i;
|
|
|
- }
|
|
|
-
|
|
|
- if (orig_udelay) {
|
|
|
- struct i2c_algo_bit_data *algo = port->adapter.algo_data;
|
|
|
- algo->udelay = orig_udelay;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- nvkm_debug(subdev, "no devices found.\n");
|
|
|
- return -ENODEV;
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_intr_fini(struct nvkm_event *event, int type, int index)
|
|
|
+nvkm_i2c_intr_fini(struct nvkm_event *event, int type, int id)
|
|
|
{
|
|
|
struct nvkm_i2c *i2c = container_of(event, typeof(*i2c), event);
|
|
|
- struct nvkm_i2c_port *port = i2c->find(i2c, index);
|
|
|
+ struct nvkm_i2c_aux *aux = nvkm_i2c_aux_find(i2c, id);
|
|
|
const struct nvkm_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
|
|
|
- if (port && port->aux >= 0)
|
|
|
- impl->aux_mask(i2c, type, 1 << port->aux, 0);
|
|
|
+ if (aux)
|
|
|
+ impl->aux_mask(i2c, type, aux->intr, 0);
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
-nvkm_i2c_intr_init(struct nvkm_event *event, int type, int index)
|
|
|
+nvkm_i2c_intr_init(struct nvkm_event *event, int type, int id)
|
|
|
{
|
|
|
struct nvkm_i2c *i2c = container_of(event, typeof(*i2c), event);
|
|
|
- struct nvkm_i2c_port *port = i2c->find(i2c, index);
|
|
|
+ struct nvkm_i2c_aux *aux = nvkm_i2c_aux_find(i2c, id);
|
|
|
const struct nvkm_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
|
|
|
- if (port && port->aux >= 0)
|
|
|
- impl->aux_mask(i2c, type, 1 << port->aux, 1 << port->aux);
|
|
|
+ if (aux)
|
|
|
+ impl->aux_mask(i2c, type, aux->intr, aux->intr);
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
nvkm_i2c_intr_ctor(struct nvkm_object *object, void *data, u32 size,
|
|
|
- struct nvkm_notify *notify)
|
|
|
+ struct nvkm_notify *notify)
|
|
|
{
|
|
|
struct nvkm_i2c_ntfy_req *req = data;
|
|
|
if (!WARN_ON(size != sizeof(*req))) {
|
|
@@ -358,33 +121,32 @@ nvkm_i2c_intr_ctor(struct nvkm_object *object, void *data, u32 size,
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
-nvkm_i2c_intr(struct nvkm_subdev *subdev)
|
|
|
+nvkm_i2c_intr(struct nvkm_subdev *obj)
|
|
|
{
|
|
|
- struct nvkm_i2c_impl *impl = (void *)nv_oclass(subdev);
|
|
|
- struct nvkm_i2c *i2c = nvkm_i2c(subdev);
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
- u32 hi, lo, rq, tx, e;
|
|
|
-
|
|
|
- if (impl->aux_stat) {
|
|
|
- impl->aux_stat(i2c, &hi, &lo, &rq, &tx);
|
|
|
- if (hi || lo || rq || tx) {
|
|
|
- list_for_each_entry(port, &i2c->ports, head) {
|
|
|
- if (e = 0, port->aux < 0)
|
|
|
- continue;
|
|
|
-
|
|
|
- if (hi & (1 << port->aux)) e |= NVKM_I2C_PLUG;
|
|
|
- if (lo & (1 << port->aux)) e |= NVKM_I2C_UNPLUG;
|
|
|
- if (rq & (1 << port->aux)) e |= NVKM_I2C_IRQ;
|
|
|
- if (tx & (1 << port->aux)) e |= NVKM_I2C_DONE;
|
|
|
- if (e) {
|
|
|
- struct nvkm_i2c_ntfy_rep rep = {
|
|
|
- .mask = e,
|
|
|
- };
|
|
|
- nvkm_event_send(&i2c->event, rep.mask,
|
|
|
- port->index, &rep,
|
|
|
- sizeof(rep));
|
|
|
- }
|
|
|
- }
|
|
|
+ struct nvkm_i2c *i2c = container_of(obj, typeof(*i2c), subdev);
|
|
|
+ struct nvkm_i2c_impl *impl = (void *)i2c->subdev.object.oclass;
|
|
|
+ struct nvkm_i2c_aux *aux;
|
|
|
+ u32 hi, lo, rq, tx;
|
|
|
+
|
|
|
+ if (!impl->aux_stat)
|
|
|
+ return;
|
|
|
+
|
|
|
+ impl->aux_stat(i2c, &hi, &lo, &rq, &tx);
|
|
|
+ if (!hi && !lo && !rq && !tx)
|
|
|
+ return;
|
|
|
+
|
|
|
+ list_for_each_entry(aux, &i2c->aux, head) {
|
|
|
+ u32 mask = 0;
|
|
|
+ if (hi & aux->intr) mask |= NVKM_I2C_PLUG;
|
|
|
+ if (lo & aux->intr) mask |= NVKM_I2C_UNPLUG;
|
|
|
+ if (rq & aux->intr) mask |= NVKM_I2C_IRQ;
|
|
|
+ if (tx & aux->intr) mask |= NVKM_I2C_DONE;
|
|
|
+ if (mask) {
|
|
|
+ struct nvkm_i2c_ntfy_rep rep = {
|
|
|
+ .mask = mask,
|
|
|
+ };
|
|
|
+ nvkm_event_send(&i2c->event, rep.mask, aux->id,
|
|
|
+ &rep, sizeof(rep));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -401,206 +163,241 @@ _nvkm_i2c_fini(struct nvkm_object *object, bool suspend)
|
|
|
{
|
|
|
struct nvkm_i2c_impl *impl = (void *)nv_oclass(object);
|
|
|
struct nvkm_i2c *i2c = (void *)object;
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
+ struct nvkm_i2c_pad *pad;
|
|
|
u32 mask;
|
|
|
- int ret;
|
|
|
-
|
|
|
- list_for_each_entry(port, &i2c->ports, head) {
|
|
|
- ret = nv_ofuncs(port)->fini(nv_object(port), suspend);
|
|
|
- if (ret && suspend)
|
|
|
- goto fail;
|
|
|
- }
|
|
|
|
|
|
if ((mask = (1 << impl->aux) - 1), impl->aux_stat) {
|
|
|
impl->aux_mask(i2c, NVKM_I2C_ANY, mask, 0);
|
|
|
impl->aux_stat(i2c, &mask, &mask, &mask, &mask);
|
|
|
}
|
|
|
|
|
|
- return nvkm_subdev_fini(&i2c->subdev, suspend);
|
|
|
-fail:
|
|
|
- list_for_each_entry_continue_reverse(port, &i2c->ports, head) {
|
|
|
- nv_ofuncs(port)->init(nv_object(port));
|
|
|
+ list_for_each_entry(pad, &i2c->pad, head) {
|
|
|
+ nvkm_i2c_pad_fini(pad);
|
|
|
}
|
|
|
|
|
|
- return ret;
|
|
|
+ return nvkm_subdev_fini(&i2c->subdev, suspend);
|
|
|
}
|
|
|
|
|
|
int
|
|
|
_nvkm_i2c_init(struct nvkm_object *object)
|
|
|
{
|
|
|
struct nvkm_i2c *i2c = (void *)object;
|
|
|
- struct nvkm_i2c_port *port;
|
|
|
+ struct nvkm_i2c_bus *bus;
|
|
|
+ struct nvkm_i2c_pad *pad;
|
|
|
int ret;
|
|
|
|
|
|
ret = nvkm_subdev_init(&i2c->subdev);
|
|
|
- if (ret == 0) {
|
|
|
- list_for_each_entry(port, &i2c->ports, head) {
|
|
|
- ret = nv_ofuncs(port)->init(nv_object(port));
|
|
|
- if (ret)
|
|
|
- goto fail;
|
|
|
- }
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ list_for_each_entry(pad, &i2c->pad, head) {
|
|
|
+ nvkm_i2c_pad_init(pad);
|
|
|
}
|
|
|
|
|
|
- return ret;
|
|
|
-fail:
|
|
|
- list_for_each_entry_continue_reverse(port, &i2c->ports, head) {
|
|
|
- nv_ofuncs(port)->fini(nv_object(port), false);
|
|
|
+ list_for_each_entry(bus, &i2c->bus, head) {
|
|
|
+ nvkm_i2c_bus_init(bus);
|
|
|
}
|
|
|
|
|
|
- return ret;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
void
|
|
|
_nvkm_i2c_dtor(struct nvkm_object *object)
|
|
|
{
|
|
|
struct nvkm_i2c *i2c = (void *)object;
|
|
|
- struct nvkm_i2c_port *port, *temp;
|
|
|
|
|
|
nvkm_event_fini(&i2c->event);
|
|
|
|
|
|
- list_for_each_entry_safe(port, temp, &i2c->ports, head) {
|
|
|
- nvkm_object_ref(NULL, (struct nvkm_object **)&port);
|
|
|
+ while (!list_empty(&i2c->aux)) {
|
|
|
+ struct nvkm_i2c_aux *aux =
|
|
|
+ list_first_entry(&i2c->aux, typeof(*aux), head);
|
|
|
+ nvkm_i2c_aux_del(&aux);
|
|
|
}
|
|
|
|
|
|
- nvkm_subdev_destroy(&i2c->subdev);
|
|
|
-}
|
|
|
-
|
|
|
-static struct nvkm_oclass *
|
|
|
-nvkm_i2c_extdev_sclass[] = {
|
|
|
- nvkm_anx9805_sclass,
|
|
|
-};
|
|
|
-
|
|
|
-static void
|
|
|
-nvkm_i2c_create_port(struct nvkm_i2c *i2c, int index, u8 type,
|
|
|
- struct dcb_i2c_entry *info)
|
|
|
-{
|
|
|
- const struct nvkm_i2c_impl *impl = (void *)nv_oclass(i2c);
|
|
|
- struct nvkm_oclass *oclass;
|
|
|
- struct nvkm_object *parent;
|
|
|
- struct nvkm_object *object;
|
|
|
- int ret, pad;
|
|
|
-
|
|
|
- if (info->share != DCB_I2C_UNUSED) {
|
|
|
- pad = info->share;
|
|
|
- oclass = impl->pad_s;
|
|
|
- } else {
|
|
|
- if (type != DCB_I2C_NVIO_AUX)
|
|
|
- pad = 0x100 + info->drive;
|
|
|
- else
|
|
|
- pad = 0x100 + info->auxch;
|
|
|
- oclass = impl->pad_x;
|
|
|
+ while (!list_empty(&i2c->bus)) {
|
|
|
+ struct nvkm_i2c_bus *bus =
|
|
|
+ list_first_entry(&i2c->bus, typeof(*bus), head);
|
|
|
+ nvkm_i2c_bus_del(&bus);
|
|
|
}
|
|
|
|
|
|
- ret = nvkm_object_ctor(nv_object(i2c), NULL, oclass,
|
|
|
- NULL, pad, &parent);
|
|
|
- if (ret < 0)
|
|
|
- return;
|
|
|
+ while (!list_empty(&i2c->pad)) {
|
|
|
+ struct nvkm_i2c_pad *pad =
|
|
|
+ list_first_entry(&i2c->pad, typeof(*pad), head);
|
|
|
+ nvkm_i2c_pad_del(&pad);
|
|
|
+ }
|
|
|
|
|
|
- oclass = impl->sclass;
|
|
|
- do {
|
|
|
- ret = -EINVAL;
|
|
|
- if (oclass->handle == type) {
|
|
|
- ret = nvkm_object_ctor(parent, NULL, oclass,
|
|
|
- info, index, &object);
|
|
|
- }
|
|
|
- } while (ret && (++oclass)->handle);
|
|
|
+ nvkm_subdev_destroy(&i2c->subdev);
|
|
|
+}
|
|
|
|
|
|
- nvkm_object_ref(NULL, &parent);
|
|
|
+static const struct nvkm_i2c_drv {
|
|
|
+ u8 bios;
|
|
|
+ u8 addr;
|
|
|
+ int (*pad_new)(struct nvkm_i2c_bus *, int id, u8 addr,
|
|
|
+ struct nvkm_i2c_pad **);
|
|
|
}
|
|
|
+nvkm_i2c_drv[] = {
|
|
|
+ { 0x0d, 0x39, anx9805_pad_new },
|
|
|
+ { 0x0e, 0x3b, anx9805_pad_new },
|
|
|
+ {}
|
|
|
+};
|
|
|
|
|
|
int
|
|
|
nvkm_i2c_create_(struct nvkm_object *parent, struct nvkm_object *engine,
|
|
|
struct nvkm_oclass *oclass, int length, void **pobject)
|
|
|
{
|
|
|
- struct nvkm_bios *bios = nvkm_bios(parent);
|
|
|
+ struct nvkm_i2c_impl *impl = (void *)oclass;
|
|
|
+ struct nvkm_device *device = (void *)parent;
|
|
|
+ struct nvkm_bios *bios = device->bios;
|
|
|
struct nvkm_i2c *i2c;
|
|
|
- struct nvkm_object *object;
|
|
|
- struct dcb_i2c_entry info;
|
|
|
- int ret, i, j, index = -1;
|
|
|
- struct dcb_output outp;
|
|
|
- u8 ver, hdr;
|
|
|
- u32 data;
|
|
|
+ struct dcb_i2c_entry ccbE;
|
|
|
+ struct dcb_output dcbE;
|
|
|
+ u8 ver, hdr;
|
|
|
+ int ret, i;
|
|
|
|
|
|
ret = nvkm_subdev_create(parent, engine, oclass, 0, "I2C", "i2c", &i2c);
|
|
|
*pobject = nv_object(i2c);
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
|
|
|
+ INIT_LIST_HEAD(&i2c->pad);
|
|
|
+ INIT_LIST_HEAD(&i2c->bus);
|
|
|
+ INIT_LIST_HEAD(&i2c->aux);
|
|
|
+
|
|
|
nv_subdev(i2c)->intr = nvkm_i2c_intr;
|
|
|
- i2c->find = nvkm_i2c_find;
|
|
|
- i2c->find_type = nvkm_i2c_find_type;
|
|
|
- i2c->acquire_pad = nvkm_i2c_acquire_pad;
|
|
|
- i2c->release_pad = nvkm_i2c_release_pad;
|
|
|
- i2c->acquire = nvkm_i2c_acquire;
|
|
|
- i2c->release = nvkm_i2c_release;
|
|
|
- i2c->identify = nvkm_i2c_identify;
|
|
|
- init_waitqueue_head(&i2c->wait);
|
|
|
- INIT_LIST_HEAD(&i2c->ports);
|
|
|
-
|
|
|
- while (!dcb_i2c_parse(bios, ++index, &info)) {
|
|
|
- switch (info.type) {
|
|
|
- case DCB_I2C_NV04_BIT:
|
|
|
- case DCB_I2C_NV4E_BIT:
|
|
|
- case DCB_I2C_NVIO_BIT:
|
|
|
- nvkm_i2c_create_port(i2c, NV_I2C_PORT(index),
|
|
|
- info.type, &info);
|
|
|
- break;
|
|
|
- case DCB_I2C_NVIO_AUX:
|
|
|
- nvkm_i2c_create_port(i2c, NV_I2C_AUX(index),
|
|
|
- info.type, &info);
|
|
|
- break;
|
|
|
- case DCB_I2C_PMGR:
|
|
|
- if (info.drive != DCB_I2C_UNUSED) {
|
|
|
- nvkm_i2c_create_port(i2c, NV_I2C_PORT(index),
|
|
|
- DCB_I2C_NVIO_BIT, &info);
|
|
|
- }
|
|
|
- if (info.auxch != DCB_I2C_UNUSED) {
|
|
|
- nvkm_i2c_create_port(i2c, NV_I2C_AUX(index),
|
|
|
- DCB_I2C_NVIO_AUX, &info);
|
|
|
- }
|
|
|
- break;
|
|
|
- case DCB_I2C_UNUSED:
|
|
|
- default:
|
|
|
+
|
|
|
+ i = -1;
|
|
|
+ while (!dcb_i2c_parse(bios, ++i, &ccbE)) {
|
|
|
+ struct nvkm_i2c_pad *pad = NULL;
|
|
|
+ struct nvkm_i2c_bus *bus = NULL;
|
|
|
+ struct nvkm_i2c_aux *aux = NULL;
|
|
|
+
|
|
|
+ nvkm_debug(&i2c->subdev, "ccb %02x: type %02x drive %02x "
|
|
|
+ "sense %02x share %02x auxch %02x\n", i, ccbE.type,
|
|
|
+ ccbE.drive, ccbE.sense, ccbE.share, ccbE.auxch);
|
|
|
+
|
|
|
+ if (ccbE.share != DCB_I2C_UNUSED) {
|
|
|
+ const int id = NVKM_I2C_PAD_HYBRID(ccbE.share);
|
|
|
+ if (!(pad = nvkm_i2c_pad_find(i2c, id)))
|
|
|
+ ret = impl->pad_s_new(i2c, id, &pad);
|
|
|
+ else
|
|
|
+ ret = 0;
|
|
|
+ } else {
|
|
|
+ ret = impl->pad_x_new(i2c, NVKM_I2C_PAD_CCB(i), &pad);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "ccb %02x pad, %d\n", i, ret);
|
|
|
+ nvkm_i2c_pad_del(&pad);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pad->func->bus_new_0 && ccbE.type == DCB_I2C_NV04_BIT) {
|
|
|
+ ret = pad->func->bus_new_0(pad, NVKM_I2C_BUS_CCB(i),
|
|
|
+ ccbE.drive,
|
|
|
+ ccbE.sense, &bus);
|
|
|
+ } else
|
|
|
+ if (pad->func->bus_new_4 &&
|
|
|
+ ( ccbE.type == DCB_I2C_NV4E_BIT ||
|
|
|
+ ccbE.type == DCB_I2C_NVIO_BIT ||
|
|
|
+ (ccbE.type == DCB_I2C_PMGR &&
|
|
|
+ ccbE.drive != DCB_I2C_UNUSED))) {
|
|
|
+ ret = pad->func->bus_new_4(pad, NVKM_I2C_BUS_CCB(i),
|
|
|
+ ccbE.drive, &bus);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "ccb %02x bus, %d\n", i, ret);
|
|
|
+ nvkm_i2c_bus_del(&bus);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pad->func->aux_new_6 &&
|
|
|
+ ( ccbE.type == DCB_I2C_NVIO_AUX ||
|
|
|
+ (ccbE.type == DCB_I2C_PMGR &&
|
|
|
+ ccbE.auxch != DCB_I2C_UNUSED))) {
|
|
|
+ ret = pad->func->aux_new_6(pad, NVKM_I2C_BUS_CCB(i),
|
|
|
+ ccbE.auxch, &aux);
|
|
|
+ } else {
|
|
|
+ ret = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "ccb %02x aux, %d\n", i, ret);
|
|
|
+ nvkm_i2c_aux_del(&aux);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ccbE.type != DCB_I2C_UNUSED && !bus && !aux) {
|
|
|
+ nvkm_warn(&i2c->subdev, "ccb %02x was ignored\n", i);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* in addition to the busses specified in the i2c table, there
|
|
|
- * may be ddc/aux channels hiding behind external tmds/dp/etc
|
|
|
- * transmitters.
|
|
|
- */
|
|
|
- index = NV_I2C_EXT(0);
|
|
|
i = -1;
|
|
|
- while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &outp))) {
|
|
|
- if (!outp.location || !outp.extdev)
|
|
|
+ while (dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE)) {
|
|
|
+ const struct nvkm_i2c_drv *drv = nvkm_i2c_drv;
|
|
|
+ struct nvkm_i2c_bus *bus;
|
|
|
+ struct nvkm_i2c_pad *pad;
|
|
|
+
|
|
|
+ /* internal outputs handled by native i2c busses (above) */
|
|
|
+ if (!dcbE.location)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* we need an i2c bus to talk to the external encoder */
|
|
|
+ bus = nvkm_i2c_bus_find(i2c, dcbE.i2c_index);
|
|
|
+ if (!bus) {
|
|
|
+ nvkm_debug(&i2c->subdev, "dcb %02x no bus\n", i);
|
|
|
continue;
|
|
|
+ }
|
|
|
|
|
|
- switch (outp.type) {
|
|
|
- case DCB_OUTPUT_TMDS:
|
|
|
- info.type = NV_I2C_TYPE_EXTDDC(outp.extdev);
|
|
|
- break;
|
|
|
- case DCB_OUTPUT_DP:
|
|
|
- info.type = NV_I2C_TYPE_EXTAUX(outp.extdev);
|
|
|
- break;
|
|
|
- default:
|
|
|
+ /* ... and a driver for it */
|
|
|
+ while (drv->pad_new) {
|
|
|
+ if (drv->bios == dcbE.extdev)
|
|
|
+ break;
|
|
|
+ drv++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!drv->pad_new) {
|
|
|
+ nvkm_debug(&i2c->subdev, "dcb %02x drv %02x unknown\n",
|
|
|
+ i, dcbE.extdev);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- ret = -ENODEV;
|
|
|
- j = -1;
|
|
|
- while (ret && ++j < ARRAY_SIZE(nvkm_i2c_extdev_sclass)) {
|
|
|
- parent = nv_object(i2c->find(i2c, outp.i2c_index));
|
|
|
- oclass = nvkm_i2c_extdev_sclass[j];
|
|
|
- do {
|
|
|
- if (oclass->handle != info.type)
|
|
|
- continue;
|
|
|
- ret = nvkm_object_ctor(parent, NULL, oclass,
|
|
|
- NULL, index++, &object);
|
|
|
- } while (ret && (++oclass)->handle);
|
|
|
+ /* find/create an instance of the driver */
|
|
|
+ pad = nvkm_i2c_pad_find(i2c, NVKM_I2C_PAD_EXT(dcbE.extdev));
|
|
|
+ if (!pad) {
|
|
|
+ const int id = NVKM_I2C_PAD_EXT(dcbE.extdev);
|
|
|
+ ret = drv->pad_new(bus, id, drv->addr, &pad);
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "dcb %02x pad, %d\n",
|
|
|
+ i, ret);
|
|
|
+ nvkm_i2c_pad_del(&pad);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* create any i2c bus / aux channel required by the output */
|
|
|
+ if (pad->func->aux_new_6 && dcbE.type == DCB_OUTPUT_DP) {
|
|
|
+ const int id = NVKM_I2C_AUX_EXT(dcbE.extdev);
|
|
|
+ struct nvkm_i2c_aux *aux = NULL;
|
|
|
+ ret = pad->func->aux_new_6(pad, id, 0, &aux);
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "dcb %02x aux, %d\n",
|
|
|
+ i, ret);
|
|
|
+ nvkm_i2c_aux_del(&aux);
|
|
|
+ }
|
|
|
+ } else
|
|
|
+ if (pad->func->bus_new_4) {
|
|
|
+ const int id = NVKM_I2C_BUS_EXT(dcbE.extdev);
|
|
|
+ struct nvkm_i2c_bus *bus = NULL;
|
|
|
+ ret = pad->func->bus_new_4(pad, id, 0, &bus);
|
|
|
+ if (ret) {
|
|
|
+ nvkm_error(&i2c->subdev, "dcb %02x bus, %d\n",
|
|
|
+ i, ret);
|
|
|
+ nvkm_i2c_bus_del(&bus);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- ret = nvkm_event_init(&nvkm_i2c_intr_func, 4, index, &i2c->event);
|
|
|
+ ret = nvkm_event_init(&nvkm_i2c_intr_func, 4, i, &i2c->event);
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
|