1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/cc2538/periph/i2c.c
2016-03-03 11:04:39 -05:00

658 lines
14 KiB
C

/*
* Copyright (C) 2015 Loci Controls Inc.
*
* 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.
*/
/**
* @addtogroup driver_periph
* @{
*
* @file
* @brief Low-level I2C driver implementation
*
* @author Ian Martin <ian@locicontrols.com>
*
* @}
*/
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include "mutex.h"
#include "periph/i2c.h"
#include "sched.h"
#include "thread.h"
#include "xtimer.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/* guard this file in case no I2C device is defined */
#if I2C_NUMOF
#ifndef I2C_0_SCL_PIN
#define I2C_0_SCL_PIN i2c_config[I2C_0].scl_pin
#endif
#ifndef I2C_0_SDA_PIN
#define I2C_0_SDA_PIN i2c_config[I2C_0].sda_pin
#endif
#undef BIT
#define BIT(n) ( 1 << (n) )
/* Standard I2C Parameters */
#define DATA_BITS 8
#define ACK_BITS 1
/* I2CM_DR Bits */
#define RS BIT(0)
/* I2CM_CTRL Bits */
#define ACK BIT(3)
#define STOP BIT(2)
#define START BIT(1)
#define RUN BIT(0)
/* I2CM_STAT Bits */
#define BUSBSY BIT(6)
#define IDLE BIT(5)
#define ARBLST BIT(4)
#define DATACK BIT(3)
#define ADRACK BIT(2)
#define ERROR BIT(1)
#define BUSY BIT(0)
/* I2CM_CR Bits */
#define SFE BIT(5)
#define MFE BIT(4)
#define LPBK BIT(0)
#define ANY_ERROR (ARBLST | DATACK | ADRACK | ERROR)
#define SCL_LP 6 /**< SCL Low Period (fixed at 6). */
#define SCL_HP 4 /**< SCL High Period (fixed at 4). */
static mutex_t mutex = MUTEX_INIT;
static mutex_t i2c_wait_mutex = MUTEX_INIT;
static uint32_t speed_hz;
static uint32_t scl_delay;
#define bus_quiet() ( cc2538_gpio_read(I2C_0_SCL_PIN) && cc2538_gpio_read(I2C_0_SDA_PIN) )
#define WARN_IF(cond) if (cond) DEBUG("%s at %s:%u\n", #cond, RIOT_FILE_NOPATH, __LINE__)
void cc2538_i2c_init_master(uint32_t speed_hz);
static void i2cm_ctrl_write(uint_fast8_t value) {
WARN_IF(I2CM_STAT & BUSY);
I2CM_CTRL = value;
}
static void assert_scl(void) {
cc2538_gpio_clear(I2C_0_SCL_PIN);
IOC_PXX_OVER[I2C_0_SCL_PIN] |= IOC_OVERRIDE_OE;
gpio_dir_output(I2C_0_SCL_PIN);
gpio_software_control(I2C_0_SCL_PIN);
}
static void release_scl(void) {
IOC_PXX_OVER[I2C_0_SCL_PIN] &= ~(IOC_OVERRIDE_OE | IOC_OVERRIDE_PDE);
gpio_dir_input(I2C_0_SCL_PIN);
gpio_software_control(I2C_0_SCL_PIN);
}
static void release_sda(void) {
IOC_PXX_OVER[I2C_0_SDA_PIN] &= ~(IOC_OVERRIDE_OE | IOC_OVERRIDE_PDE);
gpio_dir_input(I2C_0_SDA_PIN);
gpio_software_control(I2C_0_SDA_PIN);
}
static void recover_i2c_bus(void) {
/* Switch to software GPIO mode for bus recovery */
release_sda();
release_scl();
if (!bus_quiet()) {
const uint_fast8_t try_limit = 200;
uint_fast8_t n;
for (n = 0; n < try_limit; n++) {
if (bus_quiet()) {
DEBUG("%s(): SDA released after%4u SCL pulses.\n", __FUNCTION__, n);
break;
}
assert_scl();
#ifdef MODULE_XTIMER
xtimer_usleep(scl_delay);
#else
thread_yield();
#endif
release_scl();
#ifdef MODULE_XTIMER
xtimer_usleep(scl_delay);
#else
thread_yield();
#endif
}
if (n >= try_limit) {
DEBUG("%s(): Failed to release SDA after%4u SCL pulses.\n", __FUNCTION__, n);
}
}
/* Return to hardware mode for the I2C pins */
gpio_hardware_control(I2C_0_SCL_PIN);
gpio_hardware_control(I2C_0_SDA_PIN);
}
#ifdef MODULE_XTIMER
static void callback(void *arg)
{
mutex_unlock(&i2c_wait_mutex);
}
#endif
static uint_fast8_t i2c_ctrl_blocking(uint_fast8_t flags)
{
#ifdef MODULE_XTIMER
const unsigned int xtimer_timeout = 3 * (DATA_BITS + ACK_BITS) * SEC_IN_USEC / speed_hz;
#endif
mutex_trylock(&i2c_wait_mutex);
assert(I2CM_IMR & 1);
i2cm_ctrl_write(flags);
#ifdef MODULE_XTIMER
/* Set a timeout at double the expected time to transmit a byte: */
xtimer_t xtimer = {.callback = callback};
xtimer_set(&xtimer, xtimer_timeout);
#endif
mutex_lock(&i2c_wait_mutex);
#ifdef MODULE_XTIMER
xtimer_remove(&xtimer);
#endif
if (I2CM_STAT & BUSY) {
/* If the controller is still busy, it probably will be forever */
DEBUG("Master is still BUSY after %u usec. Resetting.\n", xtimer_timeout);
cc2538_i2c_init_master(speed_hz);
}
WARN_IF(I2CM_STAT & BUSY);
return I2CM_STAT;
}
void isr_i2c(void)
{
/* Clear the interrupt flag */
I2CM_ICR = 1;
/* Unlock the wait mutex */
mutex_unlock(&i2c_wait_mutex);
if (sched_context_switch_request) {
thread_yield();
}
}
void cc2538_i2c_init_master(uint32_t speed_hz)
{
SYS_CTRL_RCGCI2C |= 1; /**< Enable the I2C0 clock. */
SYS_CTRL_SCGCI2C |= 1; /**< Enable the I2C0 clock. */
SYS_CTRL_DCGCI2C |= 1; /**< Enable the I2C0 clock. */
/* Reset I2C peripheral */
SYS_CTRL_SRI2C |= 1;
#ifdef MODULE_XTIMER
xtimer_usleep(50);
#else
thread_yield();
#endif
SYS_CTRL_SRI2C &= ~1;
/* Clear all pin override flags except PUE (Pull-Up Enable) */
IOC_PXX_OVER[I2C_0_SCL_PIN] &= IOC_OVERRIDE_PUE;
IOC_PXX_OVER[I2C_0_SDA_PIN] &= IOC_OVERRIDE_PUE;
IOC_PXX_SEL[I2C_0_SCL_PIN] = I2C_CMSSCL;
IOC_PXX_SEL[I2C_0_SDA_PIN] = I2C_CMSSDA;
IOC_I2CMSSCL = I2C_0_SCL_PIN;
IOC_I2CMSSDA = I2C_0_SDA_PIN;
gpio_hardware_control(I2C_0_SCL_PIN);
gpio_hardware_control(I2C_0_SDA_PIN);
/* Initialize the I2C master by setting the Master Function Enable bit */
I2CM_CR |= MFE;
/* Set the SCL clock speed */
uint32_t ps = sys_clock_freq();
uint32_t denom = 2 * (SCL_LP + SCL_HP) * speed_hz;
ps += denom / 2;
ps /= denom;
I2CM_TPR = ps - 1;
/* Enable I2C master interrupts */
NVIC_SetPriority(I2C_IRQn, I2C_IRQ_PRIO);
NVIC_EnableIRQ(I2C_IRQn);
i2cm_ctrl_write(STOP);
/* Enable I2C master interrupts */
I2CM_IMR = 1;
}
int i2c_init_master(i2c_t dev, i2c_speed_t speed)
{
switch (dev) {
#if I2C_0_EN
case I2C_0:
break;
#endif
default:
return -1;
}
switch (speed) {
case I2C_SPEED_LOW:
speed_hz = 10000;
break;
case I2C_SPEED_NORMAL:
speed_hz = 100000;
break;
case I2C_SPEED_FAST:
speed_hz = 400000;
break;
case I2C_SPEED_FAST_PLUS:
speed_hz = 1000000;
break;
case I2C_SPEED_HIGH:
speed_hz = 3400000;
break;
default:
return -2;
}
cc2538_i2c_init_master(speed_hz);
/* Pre-compute an SCL delay in microseconds */
scl_delay = SEC_IN_USEC;
scl_delay += speed_hz;
scl_delay /= 2 * speed_hz;
return 0;
}
int i2c_init_slave(i2c_t dev, uint8_t address)
{
/* Slave mode is not (yet) supported. */
return -1;
}
int i2c_acquire(i2c_t dev)
{
if (dev == I2C_0) {
mutex_lock(&mutex);
return 0;
}
else {
return -1;
}
}
int i2c_release(i2c_t dev)
{
if (dev == I2C_0) {
mutex_unlock(&mutex);
return 0;
}
else {
return -1;
}
}
static bool i2c_busy(void) {
if (I2CM_STAT & BUSY) {
cc2538_i2c_init_master(speed_hz);
return (I2CM_STAT & BUSY) != 0;
}
return false;
}
int i2c_read_byte(i2c_t dev, uint8_t address, char *data)
{
return i2c_read_bytes(dev, address, data, 1);
}
static int i2c_read_bytes_dumb(uint8_t address, char *data, int length)
{
int n = 0;
uint_fast8_t stat;
switch (length) {
case 0:
break;
case 1:
if (i2c_busy()) {
break;
}
I2CM_SA = (address << 1) | RS;
stat = i2c_ctrl_blocking(STOP | START | RUN);
if (stat & ANY_ERROR) {
break;
}
data[n] = I2CM_DR;
n++;
break;
default:
if (i2c_busy()) {
break;
}
I2CM_SA = (address << 1) | RS;
stat = i2c_ctrl_blocking(ACK | START | RUN);
if (stat & ARBLST) {
break;
}
else if (stat & ANY_ERROR) {
i2cm_ctrl_write(STOP);
break;
}
data[n] = I2CM_DR;
n++;
while (n < length) {
stat = i2c_ctrl_blocking((n < length - 1) ? (ACK | RUN) : (STOP | RUN));
if (stat & ARBLST) {
break;
}
else if (stat & ANY_ERROR) {
i2cm_ctrl_write(STOP);
break;
}
data[n] = I2CM_DR;
n++;
}
break;
}
return n;
}
int i2c_read_bytes(i2c_t dev, uint8_t address, char *data, int length)
{
switch (dev) {
#if I2C_0_EN
case I2C_0:
break;
#endif
default:
return -1;
}
WARN_IF(I2CM_STAT & BUSY);
if ( (length <= 0) || i2c_busy() ) {
return 0;
}
WARN_IF(I2CM_STAT & BUSBSY);
if (I2CM_STAT & BUSBSY) {
recover_i2c_bus();
if (I2CM_STAT & BUSBSY) {
return 0;
}
}
return i2c_read_bytes_dumb(address, data, length);
}
int i2c_read_reg(i2c_t dev, uint8_t address, uint8_t reg, char *data)
{
return i2c_read_regs(dev, address, reg, data, 1);
}
int i2c_read_regs(i2c_t dev, uint8_t address, uint8_t reg, char *data, int length)
{
uint_fast8_t stat;
if (dev != I2C_0) {
return -1;
}
/* Transmit reg byte to slave */
if (i2c_busy()) {
return 0;
}
WARN_IF(I2CM_STAT & BUSBSY);
if (I2CM_STAT & BUSBSY) {
recover_i2c_bus();
if (I2CM_STAT & BUSBSY) {
return 0;
}
}
I2CM_SA = address << 1;
I2CM_DR = reg;
stat = i2c_ctrl_blocking(START | RUN);
if (stat & ARBLST) {
return 0;
}
else if (stat & ANY_ERROR) {
i2cm_ctrl_write(STOP);
return 0;
}
else {
/* Receive data from slave */
return i2c_read_bytes_dumb(address, data, length);
}
}
int i2c_write_byte(i2c_t dev, uint8_t address, char data)
{
return i2c_write_bytes(dev, address, &data, 1);
}
int i2c_write_bytes(i2c_t dev, uint8_t address, char *data, int length)
{
int n = 0;
if (dev != I2C_0) {
return -1;
}
WARN_IF(I2CM_STAT & BUSBSY);
if (I2CM_STAT & BUSBSY) {
recover_i2c_bus();
if (I2CM_STAT & BUSBSY) {
return 0;
}
}
I2CM_SA = address << 1;
uint_fast8_t flags = START | RUN;
for (n = 0; n < length; n++) {
if (n >= length - 1) flags |= STOP;
WARN_IF(I2CM_STAT & BUSY);
I2CM_DR = data[n];
i2c_ctrl_blocking(flags);
WARN_IF(I2CM_STAT & ARBLST);
WARN_IF(I2CM_STAT & DATACK);
WARN_IF(I2CM_STAT & ADRACK);
WARN_IF(I2CM_STAT & ERROR);
if (I2CM_STAT & ARBLST) {
break;
}
else if (I2CM_STAT & ANY_ERROR) {
i2cm_ctrl_write(STOP);
break;
}
flags = RUN;
}
if (n < length) {
DEBUG("%s(%u, %p, %u): %u/%u bytes delivered.\n", __FUNCTION__, address, data, length, n, length);
}
return n;
}
int i2c_write_reg(i2c_t dev, uint8_t address, uint8_t reg, char data)
{
return i2c_write_regs(dev, address, reg, &data, 1);
}
int i2c_write_regs(i2c_t dev, uint8_t address, uint8_t reg, char *data, int length)
{
uint_fast8_t stat;
if (dev != I2C_0) {
return -1;
}
/* Transmit reg byte to slave */
if (i2c_busy()) {
return 0;
}
WARN_IF(I2CM_STAT & BUSBSY);
if (I2CM_STAT & BUSBSY) {
recover_i2c_bus();
if (I2CM_STAT & BUSBSY) {
return 0;
}
}
I2CM_SA = address << 1;
I2CM_DR = reg;
uint_fast8_t flags = (length > 0)? (START | RUN) : (STOP | START | RUN);
stat = i2c_ctrl_blocking(flags);
if (stat & ARBLST) {
return 0;
}
else if (stat & ANY_ERROR) {
i2cm_ctrl_write(STOP);
return 0;
}
else {
/* Transmit data to slave */
int n = 0;
flags &= ~START;
for (n = 0; n < length; n++) {
if (n >= length - 1) flags |= STOP;
WARN_IF(I2CM_STAT & BUSY);
I2CM_DR = data[n];
i2c_ctrl_blocking(flags);
WARN_IF(I2CM_STAT & ARBLST);
WARN_IF(I2CM_STAT & DATACK);
WARN_IF(I2CM_STAT & ADRACK);
WARN_IF(I2CM_STAT & ERROR);
if (I2CM_STAT & ARBLST) {
break;
}
else if (I2CM_STAT & ANY_ERROR) {
i2cm_ctrl_write(STOP);
break;
}
}
if (n < length) {
DEBUG(
"%s(%u, %u, %u, %p, %u): %u/%u bytes delivered.\n",
__FUNCTION__,
dev,
address,
reg,
data,
length,
n,
length
);
}
return n;
}
}
void i2c_poweron(i2c_t dev)
{
if (dev == I2C_0) {
SYS_CTRL_RCGCI2C |= 1; /**< Enable the I2C0 clock. */
SYS_CTRL_SCGCI2C |= 1; /**< Enable the I2C0 clock. */
SYS_CTRL_DCGCI2C |= 1; /**< Enable the I2C0 clock. */
I2CM_CR |= MFE; /**< I2C master function enable. */
/* Enable I2C master interrupts */
I2CM_IMR = 1;
}
}
void i2c_poweroff(i2c_t dev)
{
if (dev == I2C_0) {
/* Disable I2C master interrupts */
I2CM_IMR = 0;
NVIC_DisableIRQ(I2C_IRQn);
I2CM_CR &= ~MFE; /**< I2C master function enable. */
SYS_CTRL_RCGCI2C &= ~1; /**< Disable the I2C0 clock. */
SYS_CTRL_SCGCI2C &= ~1; /**< Disable the I2C0 clock. */
SYS_CTRL_DCGCI2C &= ~1; /**< Disable the I2C0 clock. */
}
}
#endif /* I2C_NUMOF */