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);
+}