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/gcc/Makefile b/sysmoOCTSIM/gcc/Makefile
index 572fb21..5e3c38c 100644
--- a/sysmoOCTSIM/gcc/Makefile
+++ b/sysmoOCTSIM/gcc/Makefile
@@ -70,6 +70,9 @@
gcc/gcc/startup_same54.o \
hal/src/hal_usb_device.o \
main.o \
+i2c_bitbang.o \
+octsim_i2c.o \
+ncn8025.o \
hpl/osc32kctrl/hpl_osc32kctrl.o \
examples/driver_examples.o \
driver_init.o \
@@ -108,6 +111,9 @@
"gcc/gcc/startup_same54.o" \
"hal/src/hal_usb_device.o" \
"main.o" \
+"i2c_bitbang.o" \
+"octsim_i2c.o" \
+"ncn8025.o" \
"hpl/osc32kctrl/hpl_osc32kctrl.o" \
"examples/driver_examples.o" \
"driver_init.o" \
@@ -152,6 +158,9 @@
"hal/src/hal_usart_async.d" \
"hpl/osc32kctrl/hpl_osc32kctrl.d" \
"main.d" \
+"i2c_bitbang.d" \
+"octsim_i2c.d" \
+"ncn8025.d" \
"examples/driver_examples.d" \
"hal/src/hal_cache.d" \
"hal/src/hal_sleep.d" \
diff --git a/sysmoOCTSIM/i2c_bitbang.c b/sysmoOCTSIM/i2c_bitbang.c
new file mode 100644
index 0000000..b1a9a62
--- /dev/null
+++ b/sysmoOCTSIM/i2c_bitbang.c
@@ -0,0 +1,189 @@
+/* Bit-banging I2C layer, inspired to a large extent from Linux kernel
+ * i2c-algo-bit.c code (C) 1995-2000 Simon G. Vogl. This particular
+ * implementation is (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <hal_gpio.h>
+#include <hal_delay.h>
+#include <err_codes.h>
+#include "i2c_bitbang.h"
+
+#define setsda(adap, val) gpio_set_pin_level((adap)->pin_sda, val)
+#define setscl(adap, val) gpio_set_pin_level((adap)->pin_scl, val)
+
+static int getsda(const struct i2c_adapter *adap)
+{
+ int rc;
+ gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_IN);
+ rc = gpio_get_pin_level(adap->pin_sda);
+ gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_OUT);
+ return rc;
+}
+
+static int getscl(const struct i2c_adapter *adap)
+{
+ int rc;
+ gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_IN);
+ rc = gpio_get_pin_level(adap->pin_scl);
+ gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_OUT);
+ return rc;
+}
+
+static inline void sdalo(const struct i2c_adapter *adap)
+{
+ setsda(adap, 0);
+ delay_us((adap->udelay+1) / 2);
+}
+
+static inline void sdahi(const struct i2c_adapter *adap)
+{
+ setsda(adap, 1);
+ delay_us((adap->udelay+1) / 2);
+}
+
+static inline void scllo(const struct i2c_adapter *adap)
+{
+ setscl(adap, 0);
+ delay_us(adap->udelay / 2);
+}
+
+
+static int sclhi(const struct i2c_adapter *adap)
+{
+ setscl(adap, 1);
+
+ /* wait for slow slaves' clock stretching to complete */
+ while (!getscl(adap)) {
+ /* FIXME: abort at some point */
+ }
+ return 0;
+}
+
+static void i2c_start(const struct i2c_adapter *adap)
+{
+ /* Assert: SCL + SDA are high */
+ setsda(adap, 0); /* set SDA to low */
+ delay_us(adap->udelay); /* delay */
+ scllo(adap); /* Set SCL to low */
+}
+
+static void i2c_repstart(const struct i2c_adapter *adap)
+{
+ /* Assert: SCL is low */
+ sdahi(adap);
+ sclhi(adap);
+ setsda(adap, 0);
+ delay_us(adap->udelay);
+ scllo(adap);
+}
+
+static void i2c_stop(const struct i2c_adapter *adap)
+{
+ /* Assert: SCL is low */
+ sdalo(adap); /* set SDA low */
+ sclhi(adap); /* set SCL to high */
+ setsda(adap, 1); /* set SDA to high */
+ delay_us(adap->udelay); /* delay */
+}
+
+static int i2c_outb(const struct i2c_adapter *adap, uint8_t outdata)
+{
+ uint8_t sb;
+ int ack, i;
+
+ /* Assert: SCL is low */
+ for (i = 7; i >= 0; i--) {
+ sb = (outdata >> i) & 1;
+ setsda(adap, sb);
+ delay_us((adap->udelay + 1) / 2);
+ if (sclhi(adap) < 0)
+ return -ERR_TIMEOUT;
+ scllo(adap);
+ }
+ sdahi(adap);
+ if (sclhi(adap) < 0)
+ return -ERR_TIMEOUT;
+ ack = !getsda(adap);
+ scllo(adap);
+ return ack;
+}
+
+static int i2c_inb(const struct i2c_adapter *adap)
+{
+ uint8_t indata = 0;
+ int i;
+
+ /* Assert: CSL is low */
+ sdahi(adap);
+ for (i = 0; i < 8; i++) {
+ /* SCL high */
+ if (sclhi(adap) < 0)
+ return -ERR_TIMEOUT;
+ indata = indata << 1;
+ if (getsda(adap))
+ indata |= 0x01;
+ setscl(adap, 0);
+ if (i == 7)
+ delay_us(adap->udelay / 2);
+ else
+ delay_us(adap->udelay);
+ }
+ /* Assert: SCL is low */
+ return indata;
+}
+
+/*! Single-byte register write. Assumes 8bit register address + 8bit values */
+int i2c_write_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg, uint8_t val)
+{
+ int rc;
+
+ i2c_start(adap);
+ rc = i2c_outb(adap, addr << 1);
+ if (rc < 0)
+ goto out_stop;
+ rc = i2c_outb(adap, reg);
+ if (rc < 0)
+ goto out_stop;
+ rc = i2c_outb(adap, val);
+out_stop:
+ i2c_stop(adap);
+ return rc;
+}
+
+/*! Single-byte register read. Assumes 8bit register address + 8bit values */
+int i2c_read_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg)
+{
+ int rc;
+
+ i2c_start(adap);
+ rc = i2c_outb(adap, addr << 1);
+ if (rc < 0)
+ goto out_stop;
+ rc = i2c_outb(adap, reg);
+ if (rc < 0)
+ goto out_stop;
+ i2c_repstart(adap);
+ rc = i2c_outb(adap, addr << 1 | 1);
+ if (rc < 0)
+ goto out_stop;
+ rc = i2c_inb(adap);
+out_stop:
+ i2c_stop(adap);
+ return rc;
+}
+
+/*! Initialize a given I2C adapter/bus */
+int i2c_init(const struct i2c_adapter *adap)
+{
+ gpio_set_pin_direction(adap->pin_sda, GPIO_DIRECTION_OUT);
+ gpio_set_pin_direction(adap->pin_scl, GPIO_DIRECTION_OUT);
+
+ /* Bring bus to a known state. Looks like STOP if bus is not free yet */
+ setscl(adap, 1);
+ delay_us(adap->udelay);
+ setsda(adap, 1);
+
+ return 0;
+}
diff --git a/sysmoOCTSIM/i2c_bitbang.h b/sysmoOCTSIM/i2c_bitbang.h
new file mode 100644
index 0000000..7157c2e
--- /dev/null
+++ b/sysmoOCTSIM/i2c_bitbang.h
@@ -0,0 +1,13 @@
+#pragma once
+#include <stdint.h>
+
+struct i2c_adapter {
+ uint8_t pin_scl;
+ uint8_t pin_sda;
+ uint32_t udelay;
+};
+
+int i2c_init(const struct i2c_adapter *adap);
+int i2c_write_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg, uint8_t val);
+int i2c_read_reg(const struct i2c_adapter *adap, uint8_t addr, uint8_t reg);
+int i2c_init(const struct i2c_adapter *adap);
diff --git a/sysmoOCTSIM/main.c b/sysmoOCTSIM/main.c
index d74153d..40f2147 100644
--- a/sysmoOCTSIM/main.c
+++ b/sysmoOCTSIM/main.c
@@ -19,6 +19,10 @@
#include "atmel_start.h"
#include "atmel_start_pins.h"
+#include "i2c_bitbang.h"
+#include "octsim_i2c.h"
+#include "ncn8025.h"
+
volatile static bool data_arrived = false;
static void tx_cb_UART_debug(const struct usart_async_descriptor *const io_descr)
@@ -34,6 +38,18 @@
data_arrived = true;
}
+static void board_init()
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ i2c_init(&i2c[i]);
+
+ /* only 7 slots, as last slot is debug uart! */
+ for (i = 0; i < 7; i++)
+ ncn8025_init(i);
+}
+
int main(void)
{
atmel_start_init();
@@ -44,6 +60,8 @@
usb_start();
+ board_init();
+
const char* welcome = "\r\n\r\nsysmocom sysmoOCTSIM\r\n";
while (io_write(&UART_debug.io, (const uint8_t*)welcome, strlen(welcome)) != strlen(welcome)); // print welcome message
while (true) { // main loop
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);
+}
diff --git a/sysmoOCTSIM/ncn8025.h b/sysmoOCTSIM/ncn8025.h
new file mode 100644
index 0000000..a392c5d
--- /dev/null
+++ b/sysmoOCTSIM/ncn8025.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <stdbool.h>
+
+enum ncn8025_sim_voltage {
+ SIM_VOLT_3V0 = 0,
+ SIM_VOLT_5V0 = 2,
+ SIM_VOLT_1V8 = 3
+};
+
+enum ncn8025_sim_clkdiv {
+ SIM_CLKDIV_1 = 1,
+ SIM_CLKDIV_2 = 3,
+ SIM_CLKDIV_4 = 2,
+ SIM_CLKDIV_8 = 0,
+};
+
+struct ncn8025_settings {
+ bool rstin; /* high: active */
+ bool cmdvcc; /* high: active */
+ bool simpres; /* high: active */
+ bool led; /* high: active */
+ enum ncn8025_sim_clkdiv clkdiv; /* raw 2bit value */
+ enum ncn8025_sim_voltage vsel; /* raw 2bit value */
+};
+
+int ncn8025_set(uint8_t slot, const struct ncn8025_settings *set);
+int ncn8025_get(uint8_t slot, struct ncn8025_settings *set);
+int ncn8025_init(unsigned int slot);
diff --git a/sysmoOCTSIM/octsim_i2c.c b/sysmoOCTSIM/octsim_i2c.c
new file mode 100644
index 0000000..5b8a30a
--- /dev/null
+++ b/sysmoOCTSIM/octsim_i2c.c
@@ -0,0 +1,43 @@
+#include "atmel_start_pins.h"
+#include "i2c_bitbang.h"
+
+/* FIXME: This somehow ends up with measured 125 kHz SCL speed ?!? We should probably
+ * switch away from using delay_us() and instead use some hardware timer? */
+#define I2C_DELAY_US 1
+
+#ifndef SDA1
+/* We should define those pins in Atmel START. Until they are, define them here */
+#define SDA1 GPIO(GPIO_PORTB, 15)
+#define SCL1 GPIO(GPIO_PORTB, 14)
+#define SDA2 GPIO(GPIO_PORTB, 3)
+#define SCL2 GPIO(GPIO_PORTB, 2)
+#define SDA3 GPIO(GPIO_PORTB, 7)
+#define SCL3 GPIO(GPIO_PORTB, 6)
+#define SDA4 GPIO(GPIO_PORTC, 28)
+#define SCL4 GPIO(GPIO_PORTC, 27)
+#endif
+
+/* Unfortunately the schematics count I2C busses from '1', not from '0' :(
+ * In software, we [obviously] count from '0' upwards. */
+const struct i2c_adapter i2c[4] = {
+ [0] = {
+ .pin_sda = SDA1,
+ .pin_scl = SCL1,
+ .udelay = I2C_DELAY_US,
+ },
+ [1] = {
+ .pin_sda = SDA2,
+ .pin_scl = SCL2,
+ .udelay = I2C_DELAY_US,
+ },
+ [2] = {
+ .pin_sda = SDA3,
+ .pin_scl = SCL3,
+ .udelay = I2C_DELAY_US,
+ },
+ [3] = {
+ .pin_sda = SDA4,
+ .pin_scl = SCL4,
+ .udelay = I2C_DELAY_US,
+ }
+};
diff --git a/sysmoOCTSIM/octsim_i2c.h b/sysmoOCTSIM/octsim_i2c.h
new file mode 100644
index 0000000..5e20ea0
--- /dev/null
+++ b/sysmoOCTSIM/octsim_i2c.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "i2c_bitbang.h"
+
+extern const struct i2c_adapter i2c[4];