icE1usb fw: Import I2C peripheral driver

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Change-Id: Ib729bb5f4e94eec25c86517042cdfdcb6847ba25
diff --git a/firmware/ice40-riscv/icE1usb/Makefile b/firmware/ice40-riscv/icE1usb/Makefile
index 3fde2ca..f385408 100644
--- a/firmware/ice40-riscv/icE1usb/Makefile
+++ b/firmware/ice40-riscv/icE1usb/Makefile
@@ -55,6 +55,7 @@
 	gps.h \
 	gpsdo.h \
 	ice1usb_proto.h \
+	i2c.h \
 	misc.h \
 	usb_desc_ids.h \
 	usb_dev.h \
@@ -69,6 +70,7 @@
 	fw_app.c \
 	gps.c \
 	gpsdo.c \
+	i2c.c \
 	misc.c \
 	usb_desc_app.c \
 	usb_dev.c \
diff --git a/firmware/ice40-riscv/icE1usb/i2c.c b/firmware/ice40-riscv/icE1usb/i2c.c
new file mode 100644
index 0000000..d3a9378
--- /dev/null
+++ b/firmware/ice40-riscv/icE1usb/i2c.c
@@ -0,0 +1,125 @@
+/*
+ * i2c.c
+ *
+ * Copyright (C) 2021-2022  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "console.h"
+
+#include "config.h"
+
+
+struct i2c {
+	uint32_t csr;
+} __attribute__((packed,aligned(4)));
+
+#define I2C_CMD_START	(0 << 12)
+#define I2C_CMD_STOP	(1 << 12)
+#define I2C_CMD_WRITE	(2 << 12)
+#define I2C_CMD_READ	(3 << 12)
+
+#define I2C_GET_RESP	(1 << 15)
+#define I2C_ACK		(0 <<  8)	/* ack bit value = 0 means ACK */
+#define I2C_NAK		(1 <<  8)	/* ack bit value = 1 means NAK */
+
+#define I2C_VALID	(1 << 31)
+
+
+static volatile struct i2c * const i2c_regs = (void *)(I2C_BASE);
+
+
+static inline uint32_t
+_i2c_wait(void)
+{
+	uint32_t v;
+
+	do {
+		v = i2c_regs->csr;
+	} while (!(v & I2C_VALID));
+
+	return v & 0x1ff;
+}
+
+bool
+i2c_ready(void)
+{
+	return i2c_regs->csr & (1 << 31);
+}
+
+void
+i2c_start(void)
+{
+	i2c_regs->csr = I2C_CMD_START;
+}
+
+void
+i2c_stop(void)
+{
+	i2c_regs->csr = I2C_CMD_STOP;
+}
+
+bool
+i2c_write(uint8_t data)
+{
+	i2c_regs->csr = I2C_CMD_WRITE | data;
+	return (_i2c_wait() & (I2C_ACK | I2C_NAK)) == I2C_ACK;
+}
+
+uint8_t
+i2c_read(bool ack)
+{
+	i2c_regs->csr = I2C_CMD_READ | I2C_GET_RESP | (ack ? I2C_ACK : I2C_NAK);
+	return _i2c_wait() & 0xff;
+}
+
+
+bool
+i2c_write_reg(uint8_t dev, uint8_t reg, uint8_t val)
+{
+	bool rv = true;
+	i2c_start();
+	rv = rv && i2c_write(dev);
+	rv = rv && i2c_write(reg);
+	rv = rv && i2c_write(val);
+	i2c_stop();
+	return rv;
+}
+
+bool
+i2c_read_reg(uint8_t dev, uint8_t reg, uint8_t *val)
+{
+	bool rv = true;
+	i2c_start();
+	rv = rv && i2c_write(dev);
+	rv = rv && i2c_write(reg);
+	if (rv)
+		i2c_start();
+	rv = rv && i2c_write(dev|1);
+	*val = rv ? i2c_read(false) : 0x00; // NAK
+	i2c_stop();
+	return rv;
+}
+
+
+bool
+i2c_probe(uint8_t dev)
+{
+	bool rv;
+	i2c_start();
+	rv = i2c_write(dev);
+	i2c_stop();
+	return rv;
+}
+
+void
+i2c_scan(void)
+{
+	for (uint8_t addr=0; addr<128; addr++) {
+		if (i2c_probe(addr << 1))
+			printf("I2C @ %08x\n", addr << 1);
+	}
+}
diff --git a/firmware/ice40-riscv/icE1usb/i2c.h b/firmware/ice40-riscv/icE1usb/i2c.h
new file mode 100644
index 0000000..da11eb5
--- /dev/null
+++ b/firmware/ice40-riscv/icE1usb/i2c.h
@@ -0,0 +1,22 @@
+/*
+ * i2c.h
+ *
+ * Copyright (C) 2021-2022  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+void    i2c_start(void);
+void    i2c_stop(void);
+bool    i2c_write(uint8_t data);
+uint8_t i2c_read(bool ack);
+
+bool    i2c_write_reg(uint8_t dev, uint8_t reg, uint8_t  val);
+bool    i2c_read_reg (uint8_t dev, uint8_t reg, uint8_t *val);
+
+bool    i2c_probe(uint8_t dev);
+void    i2c_scan(void);