diff options
author | Nikita Shubin <n.shubin@yadro.com> | 2021-11-10 12:42:29 +0300 |
---|---|---|
committer | Anup Patel <anup@brainfault.org> | 2021-11-12 07:59:08 +0300 |
commit | 13a1158d24f6462ecf17d63e0c2325994180deac (patch) | |
tree | 68ea338eb2daa5903f1e45f61fe03727b61bf201 /lib/utils/i2c/fdt_i2c_sifive.c | |
parent | 6ca6bcafb74d2dc43a5082c1b7caf6b2aa453c2d (diff) | |
download | opensbi-13a1158d24f6462ecf17d63e0c2325994180deac.tar.xz |
lib: utils/i2c: Add minimal SiFive I2C driver
Minimum SiFive I2C driver to read/send bytes over I2C bus.
This allows querying information and perform operation of onboard PMIC,
as well as power-off and reset.
Tested-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
Reviewed-by: Alexandre Ghiti <alexandre.ghiti@canonical.com>
Tested-by: Alexandre Ghiti <alexandre.ghiti@canonical.com>
Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
Diffstat (limited to 'lib/utils/i2c/fdt_i2c_sifive.c')
-rw-r--r-- | lib/utils/i2c/fdt_i2c_sifive.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/lib/utils/i2c/fdt_i2c_sifive.c b/lib/utils/i2c/fdt_i2c_sifive.c new file mode 100644 index 0000000..871241a --- /dev/null +++ b/lib/utils/i2c/fdt_i2c_sifive.c @@ -0,0 +1,277 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 YADRO + * + * Authors: + * Nikita Shubin <n.shubin@yadro.com> + */ + +#include <sbi/riscv_io.h> +#include <sbi/sbi_error.h> +#include <sbi/sbi_timer.h> +#include <sbi_utils/fdt/fdt_helper.h> +#include <sbi_utils/i2c/fdt_i2c.h> + +#define SIFIVE_I2C_ADAPTER_MAX 2 + +#define SIFIVE_I2C_PRELO 0x00 +#define SIFIVE_I2C_PREHI 0x04 +#define SIFIVE_I2C_CTR 0x08 +#define SIFIVE_I2C_TXR 0x00c +#define SIFIVE_I2C_RXR SIFIVE_I2C_TXR +#define SIFIVE_I2C_CR 0x010 +#define SIFIVE_I2C_SR SIFIVE_I2C_CR + +#define SIFIVE_I2C_CTR_IEN (1 << 6) +#define SIFIVE_I2C_CTR_EN (1 << 7) + +#define SIFIVE_I2C_CMD_IACK (1 << 0) +#define SIFIVE_I2C_CMD_ACK (1 << 3) +#define SIFIVE_I2C_CMD_WR (1 << 4) +#define SIFIVE_I2C_CMD_RD (1 << 5) +#define SIFIVE_I2C_CMD_STO (1 << 6) +#define SIFIVE_I2C_CMD_STA (1 << 7) + +#define SIFIVE_I2C_STATUS_IF (1 << 0) +#define SIFIVE_I2C_STATUS_TIP (1 << 1) +#define SIFIVE_I2C_STATUS_AL (1 << 5) +#define SIFIVE_I2C_STATUS_BUSY (1 << 6) +#define SIFIVE_I2C_STATUS_RXACK (1 << 7) + +#define SIFIVE_I2C_WRITE_BIT (0 << 0) +#define SIFIVE_I2C_READ_BIT (1 << 0) + +struct sifive_i2c_adapter { + unsigned long addr; + struct i2c_adapter adapter; +}; + +static unsigned int sifive_i2c_adapter_count; +static struct sifive_i2c_adapter + sifive_i2c_adapter_array[SIFIVE_I2C_ADAPTER_MAX]; + +extern struct fdt_i2c_adapter fdt_i2c_adapter_sifive; + +static inline void sifive_i2c_setreg(struct sifive_i2c_adapter *adap, + uint8_t reg, uint8_t value) +{ + writel(value, (volatile void *)adap->addr + reg); +} + +static inline uint8_t sifive_i2c_getreg(struct sifive_i2c_adapter *adap, + uint8_t reg) +{ + return readl((volatile void *)adap->addr + reg); +} + +static int sifive_i2c_adapter_rxack(struct sifive_i2c_adapter *adap) +{ + uint8_t val = sifive_i2c_getreg(adap, SIFIVE_I2C_SR); + + if (val & SIFIVE_I2C_STATUS_RXACK) + return SBI_EIO; + + return 0; +} + +static int sifive_i2c_adapter_poll(struct sifive_i2c_adapter *adap, + uint32_t mask) +{ + unsigned int timeout = 1; // [msec] + int count = 0; + uint8_t val; + + sbi_timer_udelay(80); // worst case if bus speed is 100 kHz + + do { + val = sifive_i2c_getreg(adap, SIFIVE_I2C_SR); + if (!(val & mask)) + return 0; + + sbi_timer_udelay(1); + count += 1; + if (count == (timeout * 1000)) + return SBI_ETIMEDOUT; + } while (1); +} + +#define sifive_i2c_adapter_poll_tip(adap) \ + sifive_i2c_adapter_poll(adap, SIFIVE_I2C_STATUS_TIP) +#define sifive_i2c_adapter_poll_busy(adap) \ +sifive_i2c_adapter_poll(adap, SIFIVE_I2C_STATUS_BUSY) + +static int sifive_i2c_adapter_start(struct sifive_i2c_adapter *adap, + uint8_t addr, uint8_t bit) +{ + uint8_t val = (addr << 1) | bit; + + sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, val); + val = SIFIVE_I2C_CMD_STA | SIFIVE_I2C_CMD_WR | SIFIVE_I2C_CMD_IACK; + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, val); + + return sifive_i2c_adapter_poll_tip(adap); +} + +static int sifive_i2c_adapter_write(struct i2c_adapter *ia, uint8_t addr, + uint8_t reg, uint8_t *buffer, int len) +{ + struct sifive_i2c_adapter *adap = + container_of(ia, struct sifive_i2c_adapter, adapter); + int rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_WRITE_BIT); + + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + /* set register address */ + sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, reg); + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR | + SIFIVE_I2C_CMD_IACK); + rc = sifive_i2c_adapter_poll_tip(adap); + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + /* set value */ + while (len) { + sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, *buffer); + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR | + SIFIVE_I2C_CMD_IACK); + + rc = sifive_i2c_adapter_poll_tip(adap); + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + buffer++; + len--; + } + + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_STO | + SIFIVE_I2C_CMD_IACK); + + /* poll BUSY instead of ACK*/ + rc = sifive_i2c_adapter_poll_busy(adap); + if (rc) + return rc; + + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_IACK); + + return 0; +} + +static int sifive_i2c_adapter_read(struct i2c_adapter *ia, uint8_t addr, + uint8_t reg, uint8_t *buffer, int len) +{ + struct sifive_i2c_adapter *adap = + container_of(ia, struct sifive_i2c_adapter, adapter); + int rc; + + rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_WRITE_BIT); + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, reg); + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR | + SIFIVE_I2C_CMD_IACK); + + rc = sifive_i2c_adapter_poll_tip(adap); + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + /* setting addr with high 0 bit */ + rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_READ_BIT); + if (rc) + return rc; + + rc = sifive_i2c_adapter_rxack(adap); + if (rc) + return rc; + + while (len) { + if (len == 1) + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, + SIFIVE_I2C_CMD_ACK | + SIFIVE_I2C_CMD_RD | + SIFIVE_I2C_CMD_IACK); + else + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, + SIFIVE_I2C_CMD_RD | + SIFIVE_I2C_CMD_IACK); + + rc = sifive_i2c_adapter_poll_tip(adap); + if (rc) + return rc; + + *buffer = sifive_i2c_getreg(adap, SIFIVE_I2C_RXR); + buffer++; + len--; + } + + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_STO | + SIFIVE_I2C_CMD_IACK); + rc = sifive_i2c_adapter_poll_busy(adap); + if (rc) + return rc; + + sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_IACK); + + return 0; +} + +static int sifive_i2c_init(void *fdt, int nodeoff, + const struct fdt_match *match) +{ + int rc; + struct sifive_i2c_adapter *adapter; + uint64_t addr; + + if (sifive_i2c_adapter_count >= SIFIVE_I2C_ADAPTER_MAX) + return SBI_ENOSPC; + + adapter = &sifive_i2c_adapter_array[sifive_i2c_adapter_count]; + + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &addr, NULL); + if (rc) + return rc; + + adapter->addr = addr; + adapter->adapter.driver = &fdt_i2c_adapter_sifive; + adapter->adapter.id = nodeoff; + adapter->adapter.write = sifive_i2c_adapter_write; + adapter->adapter.read = sifive_i2c_adapter_read; + rc = i2c_adapter_add(&adapter->adapter); + if (rc) + return rc; + + sifive_i2c_adapter_count++; + return 0; +} + +static const struct fdt_match sifive_i2c_match[] = { + { .compatible = "sifive,i2c0" }, + { }, +}; + +struct fdt_i2c_adapter fdt_i2c_adapter_sifive = { + .match_table = sifive_i2c_match, + .init = sifive_i2c_init, +}; |