|  | /* | 
|  | * 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), \ | 
|  | }; \ | 
|  | DEVICE_AND_API_INIT(i2c_##n, \ | 
|  | DT_INST_LABEL(n), \ | 
|  | i2c_sifive_init, \ | 
|  | NULL, \ | 
|  | &i2c_sifive_cfg_##n, \ | 
|  | POST_KERNEL, \ | 
|  | CONFIG_I2C_INIT_PRIORITY, \ | 
|  | &i2c_sifive_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(I2C_SIFIVE_INIT) |