diff options
Diffstat (limited to 'tools/src/i2c.c')
| -rw-r--r-- | tools/src/i2c.c | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/tools/src/i2c.c b/tools/src/i2c.c new file mode 100644 index 0000000..942ef1e --- /dev/null +++ b/tools/src/i2c.c @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: MIT */ + +#include "adt.h" +#include "i2c.h" +#include "malloc.h" +#include "pmgr.h" +#include "types.h" +#include "utils.h" + +#define PASEMI_FIFO_TX 0x00 +#define PASEMI_TX_FLAG_READ BIT(10) +#define PASEMI_TX_FLAG_STOP BIT(9) +#define PASEMI_TX_FLAG_START BIT(8) + +#define PASEMI_FIFO_RX 0x04 +#define PASEMI_RX_FLAG_EMPTY BIT(8) + +#define PASEMI_STATUS 0x14 +#define PASEMI_STATUS_XFER_BUSY BIT(28) +#define PASEMI_STATUS_XFER_ENDED BIT(27) + +#define PASEMI_CONTROL 0x1c +#define PASEMI_CONTROL_CLEAR_RX BIT(10) +#define PASEMI_CONTROL_CLEAR_TX BIT(9) + +struct i2c_dev { + uintptr_t base; +}; + +i2c_dev_t *i2c_init(const char *adt_node) +{ + int adt_path[8]; + int adt_offset; + adt_offset = adt_path_offset_trace(adt, adt_node, adt_path); + if (adt_offset < 0) { + printf("i2c: Error getting %s node\n", adt_node); + return NULL; + } + + u64 base; + if (adt_get_reg(adt, adt_path, "reg", 0, &base, NULL) < 0) { + printf("i2c: Error getting %s regs\n", adt_node); + return NULL; + } + + if (pmgr_adt_power_enable(adt_node)) { + printf("i2c: Error enabling power for %s\n", adt_node); + return NULL; + } + + i2c_dev_t *dev = malloc(sizeof(*dev)); + if (!dev) + return NULL; + + dev->base = base; + return dev; +} + +void i2c_shutdown(i2c_dev_t *dev) +{ + free(dev); +} + +static void i2c_clear_fifos(i2c_dev_t *dev) +{ + set32(dev->base + PASEMI_CONTROL, PASEMI_CONTROL_CLEAR_TX | PASEMI_CONTROL_CLEAR_RX); +} + +static void i2c_clear_status(i2c_dev_t *dev) +{ + write32(dev->base + PASEMI_STATUS, 0xffffffff); +} + +static void i2c_xfer_start_read(i2c_dev_t *dev, u8 addr, size_t len) +{ + write32(dev->base + PASEMI_FIFO_TX, PASEMI_TX_FLAG_START | (addr << 1) | 1); + write32(dev->base + PASEMI_FIFO_TX, PASEMI_TX_FLAG_READ | PASEMI_TX_FLAG_STOP | len); +} + +static size_t i2c_xfer_read(i2c_dev_t *dev, u8 *bfr, size_t len) +{ + for (size_t i = 0; i < len; ++i) { + u32 timeout = 5000; + u32 val; + + do { + val = read32(dev->base + PASEMI_FIFO_RX); + if (!(val & PASEMI_RX_FLAG_EMPTY)) + break; + udelay(10); + } while (--timeout); + + if (val & PASEMI_RX_FLAG_EMPTY) { + printf("i2c: timeout while reading (got %lu, expected %lu bytes)\n", i, len); + return i; + } + + bfr[i] = val; + } + + return len; +} + +static int i2c_xfer_write(i2c_dev_t *dev, u8 addr, u32 start, u32 stop, const u8 *bfr, size_t len) +{ + if (start) + write32(dev->base + PASEMI_FIFO_TX, PASEMI_TX_FLAG_START | (addr << 1)); + + for (size_t i = 0; i < len; ++i) { + u32 data = bfr[i]; + if (i == (len - 1) && stop) + data |= PASEMI_TX_FLAG_STOP; + + write32(dev->base + PASEMI_FIFO_TX, data); + } + + if (!stop) + return 0; + + if (poll32(dev->base + PASEMI_STATUS, PASEMI_STATUS_XFER_BUSY, 0, 50000)) { + printf( + "i2c: timeout while waiting for PASEMI_STATUS_XFER_BUSY to clear after write xfer\n"); + return -1; + } + + return 0; +} + +int i2c_smbus_read(i2c_dev_t *dev, u8 addr, u8 reg, u8 *bfr, size_t len) +{ + int ret = -1; + + i2c_clear_fifos(dev); + i2c_clear_status(dev); + + if (i2c_xfer_write(dev, addr, 1, 0, ®, 1)) + goto err; + + i2c_xfer_start_read(dev, addr, len + 1); + u8 len_reply; + if (i2c_xfer_read(dev, &len_reply, 1) != 1) + goto err; + + if (len_reply < len) + printf("i2c: want to read %ld bytes from addr %d but can only read %d\n", len, addr, + len_reply); + if (len_reply > len) + printf("i2c: want to read %ld bytes from addr %d but device wants to send %d\n", len, addr, + len_reply); + + ret = i2c_xfer_read(dev, bfr, min(len, len_reply)); + +err: + if (poll32(dev->base + PASEMI_STATUS, PASEMI_STATUS_XFER_BUSY, 0, 50000)) { + printf("i2c: timeout while waiting for PASEMI_STATUS_XFER_BUSY to clear after read xfer\n"); + return -1; + } + + return ret; +} + +int i2c_smbus_write(i2c_dev_t *dev, u8 addr, u8 reg, const u8 *bfr, size_t len) +{ + i2c_clear_fifos(dev); + i2c_clear_status(dev); + + if (i2c_xfer_write(dev, addr, 1, 0, ®, 1)) + return -1; + + u8 len_send = len; + if (i2c_xfer_write(dev, addr, 0, 0, &len_send, 1)) + return -1; + if (i2c_xfer_write(dev, addr, 0, 1, bfr, len)) + return -1; + + return len_send; +} + +int i2c_smbus_read32(i2c_dev_t *dev, u8 addr, u8 reg, u32 *val) +{ + u8 bfr[4]; + if (i2c_smbus_read(dev, addr, reg, bfr, 4) != 4) + return -1; + + *val = (bfr[0]) | (bfr[1] << 8) | (bfr[2] << 16) | (bfr[3] << 24); + return 0; +} + +int i2c_smbus_read16(i2c_dev_t *dev, u8 addr, u8 reg, u16 *val) +{ + u8 bfr[2]; + if (i2c_smbus_read(dev, addr, reg, bfr, 2) != 2) + return -1; + + *val = (bfr[0]) | (bfr[1] << 8); + return 0; +} + +int i2c_smbus_write32(i2c_dev_t *dev, u8 addr, u8 reg, u32 val) +{ + u8 bfr[4]; + + bfr[0] = val; + bfr[1] = val >> 8; + bfr[2] = val >> 16; + bfr[3] = val >> 24; + + return i2c_smbus_write(dev, addr, reg, bfr, 4); +} + +int i2c_smbus_read8(i2c_dev_t *dev, u8 addr, u8 reg, u8 *val) +{ + if (i2c_smbus_read(dev, addr, reg, val, 1) != 1) + return -1; + return 0; +} |
