/* * 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 LOG_MODULE_REGISTER(i2c_sifive); #include #include #include #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(struct device *dev) { const struct i2c_sifive_cfg *config = dev->config_info; return IS_SET(config, REG_STATUS, SF_STATUS_TIP); } static int i2c_sifive_send_addr(struct device *dev, uint16_t addr, uint16_t rw_flag) { const struct i2c_sifive_cfg *config = dev->config_info; 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(struct device *dev, struct i2c_msg *msg, uint16_t addr) { const struct i2c_sifive_cfg *config = dev->config_info; 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(struct device *dev, struct i2c_msg *msg, uint16_t addr) { const struct i2c_sifive_cfg *config = dev->config_info; 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(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_info; 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(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_info == 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(struct device *dev) { const struct i2c_sifive_cfg *config = dev->config_info; 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)