blob: fa8e225a73d72c5853805314b444db48b38eba90 [file] [log] [blame]
/*
* Copyright (c) 2018 SiFive Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT sifive_i2c0
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(i2c_sifive);
#include <device.h>
#include <drivers/i2c.h>
#include <sys/sys_io.h>
#include "i2c-priv.h"
/* Macros */
#define I2C_REG(config, reg) ((mem_addr_t) ((config)->base + reg))
#define IS_SET(config, reg, value) (sys_read8(I2C_REG(config, reg)) & (value))
/* Register Offsets */
#define REG_PRESCALE_LOW 0x00
#define REG_PRESCALE_HIGH 0x04
#define REG_CONTROL 0x08
/* Transmit on write, receive on read */
#define REG_TRANSMIT 0x0c
#define REG_RECEIVE 0x0c
/* Command on write, status on read */
#define REG_COMMAND 0x10
#define REG_STATUS 0x10
/* Values */
#define SF_CONTROL_EN (1 << 7)
#define SF_CONTROL_IE (1 << 6)
#define SF_TX_WRITE (0 << 0)
#define SF_TX_READ (1 << 0)
#define SF_CMD_START (1 << 7)
#define SF_CMD_STOP (1 << 6)
#define SF_CMD_READ (1 << 5)
#define SF_CMD_WRITE (1 << 4)
#define SF_CMD_ACK (1 << 3)
#define SF_CMD_IACK (1 << 0)
#define SF_STATUS_RXACK (1 << 7)
#define SF_STATUS_BUSY (1 << 6)
#define SF_STATUS_AL (1 << 5)
#define SF_STATUS_TIP (1 << 1)
#define SF_STATUS_IP (1 << 0)
/* Structure declarations */
struct i2c_sifive_cfg {
uint32_t base;
uint32_t f_sys;
uint32_t f_bus;
};
/* Helper functions */
static inline bool i2c_sifive_busy(const struct device *dev)
{
const struct i2c_sifive_cfg *config = dev->config;
return IS_SET(config, REG_STATUS, SF_STATUS_TIP);
}
static int i2c_sifive_send_addr(const struct device *dev,
uint16_t addr,
uint16_t rw_flag)
{
const struct i2c_sifive_cfg *config = dev->config;
uint8_t command = 0U;
/* Wait for a previous transfer to complete */
while (i2c_sifive_busy(dev)) {
}
/* Set transmit register to address with read/write flag */
sys_write8((addr | rw_flag), I2C_REG(config, REG_TRANSMIT));
/* Addresses are always written */
command = SF_CMD_WRITE | SF_CMD_START;
/* Write the command register to start the transfer */
sys_write8(command, I2C_REG(config, REG_COMMAND));
while (i2c_sifive_busy(dev)) {
}
if (IS_SET(config, REG_STATUS, SF_STATUS_RXACK)) {
LOG_ERR("I2C Rx failed to acknowledge\n");
return -EIO;
}
return 0;
}
static int i2c_sifive_write_msg(const struct device *dev,
struct i2c_msg *msg,
uint16_t addr)
{
const struct i2c_sifive_cfg *config = dev->config;
int rc = 0;
uint8_t command = 0U;
rc = i2c_sifive_send_addr(dev, addr, SF_TX_WRITE);
if (rc != 0) {
LOG_ERR("I2C failed to write message\n");
return rc;
}
for (uint32_t i = 0; i < msg->len; i++) {
/* Wait for a previous transfer */
while (i2c_sifive_busy(dev)) {
}
/* Put data in transmit reg */
sys_write8((msg->buf)[i], I2C_REG(config, REG_TRANSMIT));
/* Generate command byte */
command = SF_CMD_WRITE;
/* On the last byte of the message */
if (i == (msg->len - 1)) {
/* If the stop bit is requested, set it */
if (msg->flags & I2C_MSG_STOP) {
command |= SF_CMD_STOP;
}
}
/* Write command reg */
sys_write8(command, I2C_REG(config, REG_COMMAND));
/* Wait for a previous transfer */
while (i2c_sifive_busy(dev)) {
}
if (IS_SET(config, REG_STATUS, SF_STATUS_RXACK)) {
LOG_ERR("I2C Rx failed to acknowledge\n");
return -EIO;
}
}
return 0;
}
static int i2c_sifive_read_msg(const struct device *dev,
struct i2c_msg *msg,
uint16_t addr)
{
const struct i2c_sifive_cfg *config = dev->config;
uint8_t command = 0U;
i2c_sifive_send_addr(dev, addr, SF_TX_READ);
while (i2c_sifive_busy(dev)) {
}
for (int i = 0; i < msg->len; i++) {
/* Generate command byte */
command = SF_CMD_READ;
/* On the last byte of the message */
if (i == (msg->len - 1)) {
/* Set NACK to end read */
command |= SF_CMD_ACK;
/* If the stop bit is requested, set it */
if (msg->flags & I2C_MSG_STOP) {
command |= SF_CMD_STOP;
}
}
/* Write command reg */
sys_write8(command, I2C_REG(config, REG_COMMAND));
/* Wait for the read to complete */
while (i2c_sifive_busy(dev)) {
}
/* Store the received byte */
(msg->buf)[i] = sys_read8(I2C_REG(config, REG_RECEIVE));
}
return 0;
}
/* API Functions */
static int i2c_sifive_configure(const struct device *dev, uint32_t dev_config)
{
const struct i2c_sifive_cfg *config = NULL;
uint32_t i2c_speed = 0U;
uint16_t prescale = 0U;
/* Check for NULL pointers */
if (dev == NULL) {
LOG_ERR("Device handle is NULL");
return -EINVAL;
}
config = dev->config;
if (config == NULL) {
LOG_ERR("Device config is NULL");
return -EINVAL;
}
/* Disable the I2C peripheral */
sys_write8(0, I2C_REG(config, REG_CONTROL));
/* Configure bus frequency */
switch (I2C_SPEED_GET(dev_config)) {
case I2C_SPEED_STANDARD:
i2c_speed = 100000U; /* 100 KHz */
break;
case I2C_SPEED_FAST:
i2c_speed = 400000U; /* 400 KHz */
break;
case I2C_SPEED_FAST_PLUS:
case I2C_SPEED_HIGH:
case I2C_SPEED_ULTRA:
default:
LOG_ERR("Unsupported I2C speed requested");
return -ENOTSUP;
}
/* Calculate prescale value */
prescale = (config->f_sys / (i2c_speed * 5U)) - 1;
/* Configure peripheral with calculated prescale */
sys_write8((uint8_t) (0xFF & prescale), I2C_REG(config, REG_PRESCALE_LOW));
sys_write8((uint8_t) (0xFF & (prescale >> 8)),
I2C_REG(config, REG_PRESCALE_HIGH));
/* Support I2C Master mode only */
if (!(dev_config & I2C_MODE_MASTER)) {
LOG_ERR("I2C only supports operation as master");
return -ENOTSUP;
}
/*
* Driver does not support 10-bit addressing. This can be added
* in the future when needed.
*/
if (dev_config & I2C_ADDR_10_BITS) {
LOG_ERR("I2C driver does not support 10-bit addresses");
return -ENOTSUP;
}
/* Enable the I2C peripheral */
sys_write8(SF_CONTROL_EN, I2C_REG(config, REG_CONTROL));
return 0;
}
static int i2c_sifive_transfer(const struct device *dev,
struct i2c_msg *msgs,
uint8_t num_msgs,
uint16_t addr)
{
int rc = 0;
/* Check for NULL pointers */
if (dev == NULL) {
LOG_ERR("Device handle is NULL");
return -EINVAL;
}
if (dev->config == NULL) {
LOG_ERR("Device config is NULL");
return -EINVAL;
}
if (msgs == NULL) {
return -EINVAL;
}
for (int i = 0; i < num_msgs; i++) {
if (msgs[i].flags & I2C_MSG_READ) {
rc = i2c_sifive_read_msg(dev, &(msgs[i]), addr);
} else {
rc = i2c_sifive_write_msg(dev, &(msgs[i]), addr);
}
if (rc != 0) {
LOG_ERR("I2C failed to transfer messages\n");
return rc;
}
}
return 0;
};
static int i2c_sifive_init(const struct device *dev)
{
const struct i2c_sifive_cfg *config = dev->config;
uint32_t dev_config = 0U;
int rc = 0;
dev_config = (I2C_MODE_MASTER | i2c_map_dt_bitrate(config->f_bus));
rc = i2c_sifive_configure(dev, dev_config);
if (rc != 0) {
LOG_ERR("Failed to configure I2C on init");
return rc;
}
return 0;
}
static struct i2c_driver_api i2c_sifive_api = {
.configure = i2c_sifive_configure,
.transfer = i2c_sifive_transfer,
};
/* Device instantiation */
#define I2C_SIFIVE_INIT(n) \
static struct i2c_sifive_cfg i2c_sifive_cfg_##n = { \
.base = DT_INST_REG_ADDR(n), \
.f_sys = DT_INST_PROP(n, input_frequency), \
.f_bus = DT_INST_PROP(n, clock_frequency), \
}; \
I2C_DEVICE_DT_INST_DEFINE(n, \
i2c_sifive_init, \
NULL, \
NULL, \
&i2c_sifive_cfg_##n, \
POST_KERNEL, \
CONFIG_I2C_INIT_PRIORITY, \
&i2c_sifive_api);
DT_INST_FOREACH_STATUS_OKAY(I2C_SIFIVE_INIT)