blob: 9009b6b624688d8940148ce91f4c19da8b0330de [file]
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 Aurelien Jarno
*
* Authors:
* Aurelien Jarno <aurelien@aurel32.net>
*/
#include <sbi/riscv_io.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_heap.h>
#include <sbi/sbi_timer.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/i2c/fdt_i2c.h>
/* Controller registers */
#define ICR_OFFSET 0x00 /* I2C control register */
#define IDBR_OFFSET 0x0c /* I2C data buffer register */
/* Control register bits */
#define ICR_START BIT(0) /* start */
#define ICR_STOP BIT(1) /* stop */
#define ICR_ACKNAK BIT(2) /* ACK(0) or NAK(1) */
#define ICR_TB BIT(3) /* transfer byte */
#define ICR_UR BIT(10) /* unit reset */
#define ICR_SCLE BIT(13) /* SCL enable */
#define ICR_IUE BIT(14) /* unit enable */
/* Timing */
#define I2C_RESET_US 10
#define I2C_TIMEOUT_US 1000
struct spacemit_i2c_adapter {
unsigned long base;
struct i2c_adapter adapter;
};
static inline void spacemit_i2c_set_reg(struct spacemit_i2c_adapter *adap,
uint8_t reg, uint32_t val)
{
writel(val, (void *)adap->base + reg);
}
static inline uint32_t spacemit_i2c_get_reg(struct spacemit_i2c_adapter *adap,
uint32_t reg)
{
return readl((void *)adap->base + reg);
}
static void spacemit_i2c_reset(struct spacemit_i2c_adapter *adap)
{
/* disable unit */
spacemit_i2c_set_reg(adap, ICR_OFFSET, 0);
sbi_timer_udelay(I2C_RESET_US);
/* reset unit */
spacemit_i2c_set_reg(adap, ICR_OFFSET, ICR_UR);
sbi_timer_udelay(I2C_RESET_US);
/* clear reset and enable unit and SCL */
spacemit_i2c_set_reg(adap, ICR_OFFSET, ICR_IUE | ICR_SCLE);
}
static int spacemit_i2c_wait_xfer_done(struct spacemit_i2c_adapter *adap)
{
for (int i = 0; i < I2C_TIMEOUT_US; i++) {
uint32_t val = spacemit_i2c_get_reg(adap, ICR_OFFSET);
if (!(val & ICR_TB))
return 0;
sbi_timer_udelay(1);
};
return SBI_ETIMEDOUT;
}
static void spacemit_i2c_start_xfer(struct spacemit_i2c_adapter *adap,
uint32_t ctrl)
{
const uint32_t ctrl_mask = ICR_START | ICR_STOP | ICR_ACKNAK;
uint32_t val;
val = spacemit_i2c_get_reg(adap, ICR_OFFSET);
val &= ~ctrl_mask;
val |= (ctrl & ctrl_mask);
val |= ICR_TB;
spacemit_i2c_set_reg(adap, ICR_OFFSET, val);
}
static int spacemit_i2c_xfer_write(struct spacemit_i2c_adapter *adap,
uint8_t byte, uint32_t ctrl)
{
spacemit_i2c_set_reg(adap, IDBR_OFFSET, byte);
spacemit_i2c_start_xfer(adap, ctrl);
return spacemit_i2c_wait_xfer_done(adap);
}
static int spacemit_i2c_xfer_read(struct spacemit_i2c_adapter *adap,
uint8_t *byte, uint32_t ctrl)
{
int rc;
spacemit_i2c_start_xfer(adap, ctrl);
rc = spacemit_i2c_wait_xfer_done(adap);
if (rc)
return rc;
*byte = spacemit_i2c_get_reg(adap, IDBR_OFFSET);
return 0;
}
static int spacemit_i2c_adapter_write(struct i2c_adapter *ia, uint8_t addr,
uint8_t reg, uint8_t *buffer, int len)
{
struct spacemit_i2c_adapter *adap =
container_of(ia, struct spacemit_i2c_adapter, adapter);
int rc;
/* reset controller to a known state */
spacemit_i2c_reset(adap);
/* send device address (in write mode) */
rc = spacemit_i2c_xfer_write(adap, addr << 1, ICR_START);
if (rc)
return rc;
/* send register + data bytes */
for (int i = 0; i < len + 1; i++) {
uint32_t ctrl = (i == len) ? ICR_STOP : 0;
uint8_t byte = (i == 0) ? reg : buffer[i - 1];
rc = spacemit_i2c_xfer_write(adap, byte, ctrl);
if (rc)
return rc;
}
return 0;
}
static int spacemit_i2c_adapter_read(struct i2c_adapter *ia, uint8_t addr,
uint8_t reg, uint8_t *buffer, int len)
{
struct spacemit_i2c_adapter *adap =
container_of(ia, struct spacemit_i2c_adapter, adapter);
int rc;
/* reset controller to a known state */
spacemit_i2c_reset(adap);
/* send device address (in write mode) */
rc = spacemit_i2c_xfer_write(adap, addr << 1, ICR_START);
if (rc)
return rc;
/* send register */
rc = spacemit_i2c_xfer_write(adap, reg, 0);
if (rc)
return rc;
/* repeated start and send device address (in read mode) */
rc = spacemit_i2c_xfer_write(adap, (addr << 1) | 1, ICR_START);
if (rc)
return rc;
/* read data bytes */
for (int i = 0; i < len; i++) {
uint32_t ctrl = (i == len - 1) ? (ICR_ACKNAK | ICR_STOP) : 0;
rc = spacemit_i2c_xfer_read(adap, &buffer[i], ctrl);
if (rc)
return rc;
}
return 0;
}
static int spacemit_i2c_init(const void *fdt, int nodeoff,
const struct fdt_match *match)
{
struct spacemit_i2c_adapter *adapter;
uint64_t base;
int rc;
adapter = sbi_zalloc(sizeof(*adapter));
if (!adapter)
return SBI_ENOMEM;
rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &base, NULL);
if (rc) {
sbi_free(adapter);
return rc;
}
adapter->base = base;
adapter->adapter.id = nodeoff;
adapter->adapter.write = spacemit_i2c_adapter_write;
adapter->adapter.read = spacemit_i2c_adapter_read;
rc = i2c_adapter_add(&adapter->adapter);
if (rc) {
sbi_free(adapter);
return rc;
}
return 0;
}
static const struct fdt_match spacemit_i2c_match[] = {
{ .compatible = "spacemit,k1-i2c" },
{ },
};
const struct fdt_driver fdt_i2c_adapter_spacemit = {
.match_table = spacemit_i2c_match,
.init = spacemit_i2c_init,
};