| /* 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 <stdio.h> |
| #include <utils_assert.h> |
| #include <utils.h> |
| #include "atmel_start_pins.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; |
| } |
| |
| static const uint8_t slot2int_pin(unsigned int slot) |
| { |
| static const uint8_t slot2pin[8] = { SIM0_INT, SIM1_INT, SIM2_INT, SIM3_INT, |
| SIM4_INT, SIM5_INT, SIM6_INT, SIM7_INT }; |
| ASSERT(slot < ARRAY_SIZE(slot2pin)); |
| return slot2pin[slot]; |
| } |
| |
| bool ncn8025_interrupt_active(uint8_t slot) |
| { |
| uint8_t pin = slot2int_pin(slot); |
| return !gpio_get_pin_level(pin); |
| } |
| |
| |
| /*! 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; |
| rc = ncn8025_decode(rc, set); |
| set->interrupt = ncn8025_interrupt_active(slot); |
| return rc; |
| } |
| |
| /*! 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); |
| } |
| |
| static const char *volt_str[] = { |
| [SIM_VOLT_3V0] = "3.0", |
| [SIM_VOLT_5V0] = "5.0", |
| [SIM_VOLT_1V8] = "1.8", |
| }; |
| |
| static const unsigned int div_val[] = { |
| [SIM_CLKDIV_1] = 1, |
| [SIM_CLKDIV_2] = 2, |
| [SIM_CLKDIV_4] = 4, |
| [SIM_CLKDIV_8] = 8, |
| }; |
| |
| void ncn8025_dump(const struct ncn8025_settings *set) |
| { |
| printf("VOLT=%s, CLKDIV=%u", volt_str[set->vsel], div_val[set->clkdiv]); |
| if (set->rstin) |
| printf(", RST"); |
| if (set->cmdvcc) |
| printf(", VCC"); |
| if (set->interrupt) |
| printf(", INT"); |
| if (set->simpres) |
| printf(", SIMPRES"); |
| if (set->led) |
| printf(", LED"); |
| } |