mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
294 lines
7.2 KiB
C
294 lines
7.2 KiB
C
/*
|
|
* Copyright (C) 2019 Inria
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_fe310
|
|
* @ingroup drivers_periph_i2c
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level I2C driver implementation
|
|
*
|
|
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
#include "clk.h"
|
|
#include "cpu.h"
|
|
#include "mutex.h"
|
|
|
|
#include "periph/i2c.h"
|
|
#include "periph_conf.h"
|
|
|
|
#include "vendor/i2c.h"
|
|
#include "vendor/platform.h"
|
|
#include "vendor/prci_driver.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#define I2C_BUSY_TIMEOUT (0xffff)
|
|
static const uint16_t _fe310_i2c_speed[2] = { 100U, 400U };
|
|
|
|
static inline int _wait_busy(i2c_t dev, uint32_t max_timeout_counter);
|
|
static inline int _start(i2c_t dev, uint16_t address);
|
|
static inline int _read(i2c_t dev, uint8_t *data, int length, uint8_t stop);
|
|
static inline int _write(i2c_t dev, const uint8_t *data, int length,
|
|
uint8_t stop);
|
|
|
|
/**
|
|
* @brief Initialized bus locks
|
|
*/
|
|
static mutex_t locks[I2C_NUMOF];
|
|
|
|
void i2c_init(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
/* Initialize mutex */
|
|
mutex_init(&locks[dev]);
|
|
|
|
/* Select IOF0 */
|
|
GPIO_REG(GPIO_IOF_SEL) &=
|
|
~((1 << i2c_config[dev].scl) | (1 << i2c_config[dev].sda));
|
|
/* Enable IOF */
|
|
GPIO_REG(GPIO_IOF_EN) |=
|
|
((1 << i2c_config[dev].scl) | (1 << i2c_config[dev].sda));
|
|
|
|
_REG32(i2c_config[dev].addr,
|
|
I2C_CONTROL) &= ~(I2C_CONTROL_IE | I2C_CONTROL_EN);
|
|
|
|
/* Compute prescale: presc = (CORE_CLOCK / (5 * I2C_SPEED)) - 1 */
|
|
uint16_t presc =
|
|
((uint16_t)(coreclk() / 1000) /
|
|
(5 * _fe310_i2c_speed[i2c_config[dev].speed])) - 1;
|
|
|
|
DEBUG("[i2c] init: computed prescale: %i (0x%02X|0x%02X)\n", presc,
|
|
(presc >> 8), (presc & 0xFF));
|
|
|
|
_REG32(i2c_config[dev].addr, I2C_PRESCALE_LO) = (presc & 0xFF);
|
|
_REG32(i2c_config[dev].addr, I2C_PRESCALE_HI) = (presc >> 8);
|
|
|
|
DEBUG("[i2c] init: stored prescale: (0x%02X|0x%02X)\n",
|
|
(uint8_t)(_REG32(i2c_config[dev].addr, I2C_PRESCALE_HI) & 0xFF),
|
|
(uint8_t)(_REG32(i2c_config[dev].addr, I2C_PRESCALE_LO) & 0xFF));
|
|
|
|
_REG32(i2c_config[dev].addr, I2C_CONTROL) = I2C_CONTROL_EN;
|
|
|
|
DEBUG("[i2c] init: control reg 0x%08X)\n",
|
|
(unsigned)_REG32(i2c_config[dev].addr, I2C_CONTROL));
|
|
|
|
DEBUG("[i2c] initialization done\n");
|
|
}
|
|
|
|
void i2c_acquire(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
mutex_lock(&locks[dev]);
|
|
}
|
|
|
|
void i2c_release(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
mutex_unlock(&locks[dev]);
|
|
}
|
|
|
|
int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length,
|
|
uint8_t flags)
|
|
{
|
|
assert(length > 0);
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
if (flags & I2C_ADDR10) {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || length == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUG("[i2c] read bytes\n");
|
|
|
|
int ret = 0;
|
|
|
|
if (!(flags & I2C_NOSTART)) {
|
|
ret = _start(dev, ((address << 1) | I2C_READ));
|
|
if (ret < 0) {
|
|
DEBUG("[i2c] Error: start command failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* read data and issue stop if needed */
|
|
ret = _read(dev, data, length, (flags & I2C_NOSTOP) ? 0 : 1);
|
|
if (ret < 0) {
|
|
DEBUG("[i2c] Error: read command failed\n");
|
|
return ret;
|
|
}
|
|
|
|
_wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
|
|
return length;
|
|
}
|
|
|
|
int i2c_write_bytes(i2c_t dev, uint16_t address, const void *data,
|
|
size_t length,
|
|
uint8_t flags)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
int ret = 0;
|
|
|
|
/* Check for unsupported operations */
|
|
if (flags & I2C_ADDR10) {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || length == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUG("[i2c] write bytes\n");
|
|
|
|
if (!(flags & I2C_NOSTART)) {
|
|
ret = _start(dev, (address << 1));
|
|
if (ret < 0) {
|
|
DEBUG("[i2c] error: start command failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = _write(dev, data, length, (flags & I2C_NOSTOP) ? 0 : 1);
|
|
if (ret < 0) {
|
|
DEBUG("[i2c] error: write command failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int _wait_busy(i2c_t dev, uint32_t max_timeout_counter)
|
|
{
|
|
uint32_t timeout_counter = 0;
|
|
|
|
DEBUG("[i2c] wait for transfer\n");
|
|
while (_REG32(i2c_config[dev].addr, I2C_STATUS) & I2C_STATUS_TIP) {
|
|
if (++timeout_counter >= max_timeout_counter) {
|
|
DEBUG("[i2c] transfer timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
else if ((_REG32(i2c_config[dev].addr,
|
|
I2C_STATUS) & I2C_STATUS_ALOST) == I2C_STATUS_ALOST) {
|
|
/* Arbitration lost */
|
|
DEBUG("[i2c] error: Arbitration lost\n");
|
|
return -EAGAIN;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int _start(i2c_t dev, uint16_t address)
|
|
{
|
|
_wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
|
|
/* start transmission */
|
|
DEBUG("[i2c] write slave address, 0x%02X\n", address);
|
|
_REG32(i2c_config[dev].addr, I2C_DATA) = address;
|
|
|
|
DEBUG("[i2c] send start condition\n");
|
|
_REG32(i2c_config[dev].addr, I2C_CMD) = (I2C_CMD_STA | I2C_CMD_WR);
|
|
|
|
/* Ensure all bytes has been read */
|
|
int ret = _wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int _read(i2c_t dev, uint8_t *data, int length, uint8_t stop)
|
|
{
|
|
uint8_t count = 0;
|
|
|
|
/* Read data buffer. */
|
|
while (length--) {
|
|
uint8_t command = I2C_CMD_RD;
|
|
|
|
/* Wait for hardware module to sync */
|
|
int ret = _wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (length == 0) {
|
|
/* Send NACK before STOP */
|
|
command |= I2C_CMD_ACK;
|
|
/* Prepare stop command */
|
|
if (stop) {
|
|
command |= I2C_CMD_STO;
|
|
}
|
|
}
|
|
|
|
_REG32(i2c_config[dev].addr, I2C_CMD) = command;
|
|
|
|
/* Wait for response on bus. */
|
|
ret = _wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
data[count] = (uint32_t)(_REG32(i2c_config[dev].addr, I2C_DATA));
|
|
DEBUG("[i2c] read byte #%i, 0x%02X\n", count, data[count]);
|
|
|
|
count++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int _write(i2c_t dev, const uint8_t *data, int length,
|
|
uint8_t stop)
|
|
{
|
|
uint8_t count = 0;
|
|
|
|
/* Write data buffer until the end. */
|
|
while (length--) {
|
|
/* Wait for hardware module to sync */
|
|
int ret = _wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
uint8_t command = I2C_CMD_WR;
|
|
|
|
DEBUG("[i2c] write byte #%i, 0x%02X\n", count, data[count]);
|
|
_REG32(i2c_config[dev].addr, I2C_DATA) = data[count++];
|
|
|
|
/* Check if this is the last byte to read */
|
|
if ((length == 0) && stop) {
|
|
command |= I2C_CMD_STO;
|
|
}
|
|
|
|
_REG32(i2c_config[dev].addr, I2C_CMD) = command;
|
|
|
|
ret = _wait_busy(dev, I2C_BUSY_TIMEOUT);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|