summaryrefslogtreecommitdiff
path: root/tools/src/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/src/i2c.c')
-rw-r--r--tools/src/i2c.c216
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, &reg, 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, &reg, 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;
+}