Support for sysmoOCTSIM NCN8025/SX1503 control

This adds an I2C bit-banging layer, defines the four busses on the
sysmoOCTSIM and adds some high-level functions to control the NCN8025
for each SIM slot.

Change-Id: Ic5287cf80d2be2070c504e9d40f7c6fc0d37d8b9
diff --git a/sysmoOCTSIM/ncn8025.c b/sysmoOCTSIM/ncn8025.c
new file mode 100644
index 0000000..99b93b8
--- /dev/null
+++ b/sysmoOCTSIM/ncn8025.c
@@ -0,0 +1,136 @@
+/* Access functions for the per-SIM-slot NCN8025 chip card interface,
+ * which is controlled via (half) a SX1503 I2C GPIO expander.
+ *
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <utils_assert.h>
+#include <utils.h>
+#include "octsim_i2c.h"
+#include "ncn8025.h"
+
+#define SX1503_ADDR	0x20
+
+/*! translate from ncn8025_settings into SX1503 register value */
+static uint8_t ncn8025_encode(const struct ncn8025_settings *set)
+{
+	uint8_t reg = 0;
+	if (set->rstin)
+		reg |= 0x01;
+	if (!set->cmdvcc)
+		reg |= 0x02;
+	if (set->clkdiv & 1)
+		reg |= 0x04;
+	if (set->clkdiv & 2)
+		reg |= 0x08;
+	if (set->vsel & 1)
+		reg |= 0x10;
+	if (set->vsel & 2)
+		reg |= 0x20;
+	if (set->led)
+		reg |= 0x80;
+	return reg;
+}
+
+/*! translate from register value to ncn8025_settings */
+static int ncn8025_decode(uint8_t reg, struct ncn8025_settings *set)
+{
+	memset(set, 0, sizeof(*set));
+
+	if (reg & 0x01)
+		set->rstin = true;
+	if (!(reg & 0x02))
+		set->cmdvcc = true;
+	if (reg & 0x04)
+		set->clkdiv |= 0x01;
+	if (reg & 0x08)
+		set->clkdiv |= 0x02;
+	if (reg & 0x10)
+		set->vsel |= 0x01;
+	if (reg & 0x20)
+		set->vsel |= 0x02;
+	if (!(reg & 0x40))
+		set->simpres = true;
+	if ((reg & 0x80))
+		set->led = true;
+
+	return 0;
+}
+
+static const struct i2c_adapter *slot2adapter(unsigned int slot)
+{
+	unsigned int idx = slot / 2;
+	ASSERT(idx < ARRAY_SIZE(i2c));
+	return &i2c[idx];
+}
+
+
+static const uint8_t slot2data_reg(unsigned int slot)
+{
+	if (slot & 1)
+		return 0x00;
+	else
+		return 0x01;
+}
+
+static const uint8_t slot2dir_reg(unsigned int slot)
+{
+	if (slot & 1)
+		return 0x02;
+	else
+		return 0x03;
+}
+
+
+/*! Set a given NCN8025 as described in 'set'.
+ *  \param[in] slot Slot number (0..7)
+ *  \param[in] set Settings that shall be written
+ *  \returns 0 on success; negative on error */
+int ncn8025_set(uint8_t slot, const struct ncn8025_settings *set)
+{
+	const struct i2c_adapter *adap = slot2adapter(slot);
+	uint8_t reg = slot2data_reg(slot);
+	uint8_t raw = ncn8025_encode(set);
+	return i2c_write_reg(adap, SX1503_ADDR, reg, raw);
+}
+
+/*! Get a given NCN8025 state from the chip.
+ *  \param[in] slot Slot number (0..7)
+ *  \param[out] set Settings that are retrieved
+ *  \returns 0 on success; negative on error */
+int ncn8025_get(uint8_t slot, struct ncn8025_settings *set)
+{
+	const struct i2c_adapter *adap = slot2adapter(slot);
+	uint8_t reg = slot2data_reg(slot);
+	int rc;
+	rc = i2c_read_reg(adap, SX1503_ADDR, reg);
+	if (rc < 0)
+		return rc;
+	return ncn8025_decode(rc, set);
+}
+
+/*! default settings we use at start-up: powered off, in reset, slowest clock, 3V */
+static const struct ncn8025_settings def_settings = {
+	.rstin = true,
+	.cmdvcc = false,
+	.led = false,
+	.clkdiv = SIM_CLKDIV_8,
+	.vsel = SIM_VOLT_3V0,
+};
+
+/*! Initialize a given NCN8025/slot. */
+int ncn8025_init(unsigned int slot)
+{
+	const struct i2c_adapter *adap = slot2adapter(slot);
+	uint8_t reg = slot2dir_reg(slot);
+	int rc;
+	/* IO6 of each bank is input (!PRESENT), rest are outputs */
+	rc = i2c_write_reg(adap, SX1503_ADDR, reg, 0x40);
+	if (rc < 0)
+		return rc;
+	return ncn8025_set(slot, &def_settings);
+}