diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/subdev/i2c/bit.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/subdev/i2c/bit.c | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/bit.c b/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/bit.c new file mode 100644 index 000000000..cdce11bba --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/bit.c @@ -0,0 +1,216 @@ +/* + * 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 busions 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 "bus.h" + +#ifdef CONFIG_NOUVEAU_I2C_INTERNAL +#define T_TIMEOUT 2200000 +#define T_RISEFALL 1000 +#define T_HOLD 5000 + +static inline void +nvkm_i2c_drive_scl(struct nvkm_i2c_bus *bus, int state) +{ + bus->func->drive_scl(bus, state); +} + +static inline void +nvkm_i2c_drive_sda(struct nvkm_i2c_bus *bus, int state) +{ + bus->func->drive_sda(bus, state); +} + +static inline int +nvkm_i2c_sense_scl(struct nvkm_i2c_bus *bus) +{ + return bus->func->sense_scl(bus); +} + +static inline int +nvkm_i2c_sense_sda(struct nvkm_i2c_bus *bus) +{ + return bus->func->sense_sda(bus); +} + +static void +nvkm_i2c_delay(struct nvkm_i2c_bus *bus, u32 nsec) +{ + udelay((nsec + 500) / 1000); +} + +static bool +nvkm_i2c_raise_scl(struct nvkm_i2c_bus *bus) +{ + u32 timeout = T_TIMEOUT / T_RISEFALL; + + nvkm_i2c_drive_scl(bus, 1); + do { + nvkm_i2c_delay(bus, T_RISEFALL); + } while (!nvkm_i2c_sense_scl(bus) && --timeout); + + return timeout != 0; +} + +static int +i2c_start(struct nvkm_i2c_bus *bus) +{ + int ret = 0; + + if (!nvkm_i2c_sense_scl(bus) || + !nvkm_i2c_sense_sda(bus)) { + nvkm_i2c_drive_scl(bus, 0); + nvkm_i2c_drive_sda(bus, 1); + if (!nvkm_i2c_raise_scl(bus)) + ret = -EBUSY; + } + + nvkm_i2c_drive_sda(bus, 0); + nvkm_i2c_delay(bus, T_HOLD); + nvkm_i2c_drive_scl(bus, 0); + nvkm_i2c_delay(bus, T_HOLD); + return ret; +} + +static void +i2c_stop(struct nvkm_i2c_bus *bus) +{ + nvkm_i2c_drive_scl(bus, 0); + nvkm_i2c_drive_sda(bus, 0); + nvkm_i2c_delay(bus, T_RISEFALL); + + nvkm_i2c_drive_scl(bus, 1); + nvkm_i2c_delay(bus, T_HOLD); + nvkm_i2c_drive_sda(bus, 1); + nvkm_i2c_delay(bus, T_HOLD); +} + +static int +i2c_bitw(struct nvkm_i2c_bus *bus, int sda) +{ + nvkm_i2c_drive_sda(bus, sda); + nvkm_i2c_delay(bus, T_RISEFALL); + + if (!nvkm_i2c_raise_scl(bus)) + return -ETIMEDOUT; + nvkm_i2c_delay(bus, T_HOLD); + + nvkm_i2c_drive_scl(bus, 0); + nvkm_i2c_delay(bus, T_HOLD); + return 0; +} + +static int +i2c_bitr(struct nvkm_i2c_bus *bus) +{ + int sda; + + nvkm_i2c_drive_sda(bus, 1); + nvkm_i2c_delay(bus, T_RISEFALL); + + if (!nvkm_i2c_raise_scl(bus)) + return -ETIMEDOUT; + nvkm_i2c_delay(bus, T_HOLD); + + sda = nvkm_i2c_sense_sda(bus); + + nvkm_i2c_drive_scl(bus, 0); + nvkm_i2c_delay(bus, T_HOLD); + return sda; +} + +static int +nvkm_i2c_get_byte(struct nvkm_i2c_bus *bus, u8 *byte, bool last) +{ + int i, bit; + + *byte = 0; + for (i = 7; i >= 0; i--) { + bit = i2c_bitr(bus); + if (bit < 0) + return bit; + *byte |= bit << i; + } + + return i2c_bitw(bus, last ? 1 : 0); +} + +static int +nvkm_i2c_put_byte(struct nvkm_i2c_bus *bus, u8 byte) +{ + int i, ret; + for (i = 7; i >= 0; i--) { + ret = i2c_bitw(bus, !!(byte & (1 << i))); + if (ret < 0) + return ret; + } + + ret = i2c_bitr(bus); + if (ret == 1) /* nack */ + ret = -EIO; + return ret; +} + +static int +i2c_addr(struct nvkm_i2c_bus *bus, struct i2c_msg *msg) +{ + u32 addr = msg->addr << 1; + if (msg->flags & I2C_M_RD) + addr |= 1; + return nvkm_i2c_put_byte(bus, addr); +} + +int +nvkm_i2c_bit_xfer(struct nvkm_i2c_bus *bus, struct i2c_msg *msgs, int num) +{ + struct i2c_msg *msg = msgs; + int ret = 0, mcnt = num; + + while (!ret && mcnt--) { + u8 remaining = msg->len; + u8 *ptr = msg->buf; + + ret = i2c_start(bus); + if (ret == 0) + ret = i2c_addr(bus, msg); + + if (msg->flags & I2C_M_RD) { + while (!ret && remaining--) + ret = nvkm_i2c_get_byte(bus, ptr++, !remaining); + } else { + while (!ret && remaining--) + ret = nvkm_i2c_put_byte(bus, *ptr++); + } + + msg++; + } + + i2c_stop(bus); + return (ret < 0) ? ret : num; +} +#else +int +nvkm_i2c_bit_xfer(struct nvkm_i2c_bus *bus, struct i2c_msg *msgs, int num) +{ + return -ENODEV; +} +#endif |