mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
937c954d92
The lpc23xx MCU has up to three I2C interfaces.
This adds a driver for it.
The peripheral works in interrupt mode, each change of the state machine
will generate an interrupt.
The response to the states are laid out in the data sheet.
This replaces the old driver that was removed in c560e28eb6
485 lines
11 KiB
C
485 lines
11 KiB
C
/*
|
|
* Copyright (C) 2020 Beuth Hochschule für Technik Berlin
|
|
*
|
|
* 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_lpc2387
|
|
* @ingroup drivers_periph_i2c
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level I2C driver implementation for lpc23xx
|
|
*
|
|
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#include "cpu.h"
|
|
#include "board.h"
|
|
#include "byteorder.h"
|
|
#include "periph_conf.h"
|
|
#include "periph/i2c.h"
|
|
|
|
#include "sched.h"
|
|
#include "thread.h"
|
|
#include "mutex.h"
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
#include "debug.h"
|
|
|
|
#if I2C_NUMOF > 0
|
|
static void I2C0_IRQHandler(void) __attribute__((interrupt("IRQ")));
|
|
#endif
|
|
#if I2C_NUMOF > 1
|
|
static void I2C1_IRQHandler(void) __attribute__((interrupt("IRQ")));
|
|
#endif
|
|
#if I2C_NUMOF > 2
|
|
static void I2C2_IRQHandler(void) __attribute__((interrupt("IRQ")));
|
|
#endif
|
|
|
|
/**
|
|
* We only ever need two buffers for read/write reg
|
|
*/
|
|
#define TRX_BUFS_MAX (2)
|
|
static struct i2c_ctx {
|
|
mutex_t lock;
|
|
mutex_t tx_done;
|
|
uint8_t *buf[TRX_BUFS_MAX];
|
|
uint8_t *buf_end[TRX_BUFS_MAX];
|
|
uint8_t *cur;
|
|
uint8_t *end;
|
|
int res;
|
|
uint8_t addr[TRX_BUFS_MAX];
|
|
uint8_t buf_num;
|
|
uint8_t buf_cur;
|
|
} ctx[I2C_NUMOF] = {
|
|
#if I2C_NUMOF > 0
|
|
{ .lock = MUTEX_INIT, .tx_done = MUTEX_INIT_LOCKED },
|
|
#endif
|
|
#if I2C_NUMOF > 1
|
|
{ .lock = MUTEX_INIT, .tx_done = MUTEX_INIT_LOCKED },
|
|
#endif
|
|
#if I2C_NUMOF > 2
|
|
{ .lock = MUTEX_INIT, .tx_done = MUTEX_INIT_LOCKED },
|
|
#endif
|
|
};
|
|
|
|
static void poweron(lpc23xx_i2c_t *i2c)
|
|
{
|
|
switch ((uint32_t)i2c) {
|
|
case I2C0_BASE_ADDR:
|
|
PCONP |= PCI2C0;
|
|
break;
|
|
case I2C1_BASE_ADDR:
|
|
PCONP |= PCI2C1;
|
|
break;
|
|
case I2C2_BASE_ADDR:
|
|
PCONP |= PCI2C2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void poweroff(lpc23xx_i2c_t *i2c)
|
|
{
|
|
switch ((uint32_t)i2c) {
|
|
case I2C0_BASE_ADDR:
|
|
PCONP &= ~PCI2C0;
|
|
break;
|
|
case I2C1_BASE_ADDR:
|
|
PCONP &= ~PCI2C1;
|
|
break;
|
|
case I2C2_BASE_ADDR:
|
|
PCONP &= ~PCI2C2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int i2c_acquire(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
mutex_lock(&ctx[dev].lock);
|
|
poweron(i2c_config[dev].dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void i2c_release(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
poweroff(i2c_config[dev].dev);
|
|
mutex_unlock(&ctx[dev].lock);
|
|
}
|
|
|
|
static void _set_baud(lpc23xx_i2c_t *i2c, uint32_t baud)
|
|
{
|
|
uint32_t pclksel, prescale;
|
|
lpc2387_pclk_scale(CLOCK_CORECLOCK, baud, &pclksel, &prescale);
|
|
|
|
switch ((uint32_t)i2c) {
|
|
case I2C0_BASE_ADDR:
|
|
PCLKSEL0 &= ~(BIT14 | BIT15);
|
|
PCLKSEL0 |= pclksel << 14;
|
|
I20SCLL = prescale / 2;
|
|
I20SCLH = prescale / 2;
|
|
break;
|
|
case I2C1_BASE_ADDR:
|
|
PCLKSEL1 &= ~(BIT6 | BIT7);
|
|
PCLKSEL1 |= pclksel << 6;
|
|
I21SCLL = prescale / 2;
|
|
I21SCLH = prescale / 2;
|
|
break;
|
|
case I2C2_BASE_ADDR:
|
|
PCLKSEL1 &= ~(BIT20 | BIT21);
|
|
PCLKSEL1 |= pclksel << 20;
|
|
I22SCLL = prescale / 2;
|
|
I22SCLH = prescale / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static unsigned _get_irq(i2c_t dev)
|
|
{
|
|
switch ((uint32_t)i2c_config[dev].dev) {
|
|
case I2C0_BASE_ADDR:
|
|
return I2C0_INT;
|
|
case I2C1_BASE_ADDR:
|
|
return I2C1_INT;
|
|
case I2C2_BASE_ADDR:
|
|
return I2C2_INT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _install_irq(i2c_t dev)
|
|
{
|
|
switch (dev) {
|
|
#if I2C_NUMOF > 0
|
|
case 0:
|
|
install_irq(_get_irq(dev), I2C0_IRQHandler, i2c_config[dev].irq_prio);
|
|
break;
|
|
#endif
|
|
#if I2C_NUMOF > 1
|
|
case 1:
|
|
install_irq(_get_irq(dev), I2C1_IRQHandler, i2c_config[dev].irq_prio);
|
|
break;
|
|
#endif
|
|
#if I2C_NUMOF > 2
|
|
case 2:
|
|
install_irq(_get_irq(dev), I2C2_IRQHandler, i2c_config[dev].irq_prio);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void i2c_init(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
|
|
const i2c_conf_t *cfg = &i2c_config[dev];
|
|
lpc23xx_i2c_t *i2c = cfg->dev;
|
|
|
|
poweron(i2c);
|
|
|
|
/* configure SDA & SCL pins */
|
|
*(&PINSEL0 + cfg->pinsel_sda) |= cfg->pinsel_msk_sda;
|
|
*(&PINSEL0 + cfg->pinsel_scl) |= cfg->pinsel_msk_scl;
|
|
|
|
/* clear control register */
|
|
i2c->CONCLR = I2CONCLR_AAC | I2CONCLR_SIC | I2CONCLR_STAC
|
|
| I2CONCLR_I2ENC;
|
|
|
|
_set_baud(i2c, cfg->speed);
|
|
|
|
_install_irq(dev);
|
|
|
|
/* enable the interface */
|
|
i2c->CONSET = I2CONSET_I2EN;
|
|
|
|
poweroff(i2c);
|
|
}
|
|
|
|
static void _end_tx(i2c_t dev, unsigned res)
|
|
{
|
|
ctx[dev].res = res;
|
|
mutex_unlock(&ctx[dev].tx_done);
|
|
}
|
|
|
|
static void _next_buffer(i2c_t dev)
|
|
{
|
|
/* We only need two buffers max.
|
|
This can only be called for the first buffer
|
|
as there is no next buffer for the second buffer */
|
|
assert(ctx[dev].buf_cur == 0);
|
|
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
/* if mode (read/write) changed, send START again */
|
|
if (ctx[dev].addr[0] != ctx[dev].addr[1]) {
|
|
i2c->CONSET = I2CONSET_STA;
|
|
}
|
|
|
|
ctx[dev].cur = ctx[dev].buf[1];
|
|
ctx[dev].end = ctx[dev].buf_end[1];
|
|
ctx[dev].buf_cur = 1;
|
|
}
|
|
|
|
static void irq_handler(i2c_t dev)
|
|
{
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
unsigned stat = i2c->STAT;
|
|
|
|
DEBUG("[i2c] STAT: %x\n", stat);
|
|
|
|
switch (stat) {
|
|
case 0x00: /* Bus Error */
|
|
DEBUG("[i2c] Bus Error\n");
|
|
_end_tx(dev, -EIO);
|
|
break;
|
|
|
|
case 0x08: /* A Start Condition is issued. */
|
|
case 0x10: /* A repeated Start Condition is issued */
|
|
ctx[dev].cur = ctx[dev].buf[ctx[dev].buf_cur];
|
|
i2c->DAT = ctx[dev].addr[ctx[dev].buf_cur];
|
|
i2c->CONSET = I2CONSET_AA;
|
|
i2c->CONCLR = I2CONCLR_STAC;
|
|
break;
|
|
|
|
case 0x20: /* Address NACK (write) */
|
|
case 0x48: /* Address NACK (read) */
|
|
/* slave did not ACK address - send STOP */
|
|
i2c->CONSET = I2CONSET_STO | I2CONSET_AA;
|
|
_end_tx(dev, -ENXIO);
|
|
break;
|
|
|
|
case 0x18: /* Master Transmit, SLA_R has been sent */
|
|
i2c->DAT = *ctx[dev].cur++;
|
|
i2c->CONSET = I2CONSET_AA;
|
|
break;
|
|
|
|
case 0x28: /* Data byte has been transmitted */
|
|
|
|
if (ctx[dev].cur == ctx[dev].end) {
|
|
/* we transmitted all buffers */
|
|
if (ctx[dev].buf_cur == ctx[dev].buf_num) {
|
|
i2c->CONSET = I2CONSET_STO | I2CONSET_AA;
|
|
_end_tx(dev, 0);
|
|
break;
|
|
} else {
|
|
_next_buffer(dev);
|
|
}
|
|
}
|
|
|
|
i2c->DAT = *ctx[dev].cur++;
|
|
i2c->CONSET = I2CONSET_AA;
|
|
break;
|
|
|
|
case 0x30: /* Data NACK */
|
|
i2c->CONSET = I2CONSET_STO | I2CONSET_AA;
|
|
_end_tx(dev, 0);
|
|
break;
|
|
|
|
case 0x38: /* Arbitration has been lost */
|
|
i2c->CONSET = I2CONSET_STA | I2CONSET_AA;
|
|
break;
|
|
|
|
case 0x40: /* Master Receive, SLA_R has been sent */
|
|
|
|
/* if we only want to read one byte, send NACK already */
|
|
if (ctx[dev].cur + 1 == ctx[dev].end) {
|
|
i2c->CONCLR = I2CONCLR_AAC;
|
|
} else {
|
|
i2c->CONSET = I2CONSET_AA;
|
|
}
|
|
break;
|
|
|
|
case 0x50: /* Data byte has been received */
|
|
|
|
*ctx[dev].cur++ = i2c->DAT;
|
|
|
|
if (ctx[dev].cur == ctx[dev].end) {
|
|
i2c->CONCLR = I2CONCLR_AAC;
|
|
} else {
|
|
i2c->CONSET = I2CONSET_AA;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x58: /* Data received, NACK */
|
|
*ctx[dev].cur = i2c->DAT;
|
|
|
|
if (ctx[dev].buf_cur != ctx[dev].buf_num) {
|
|
i2c->CONSET = I2CONSET_AA;
|
|
_next_buffer(dev);
|
|
} else {
|
|
i2c->CONSET = I2CONSET_AA | I2CONSET_STO;
|
|
_end_tx(dev, 0);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
/* clear interrupt flag */
|
|
i2c->CONCLR = I2CONCLR_SIC;
|
|
}
|
|
|
|
static void _init_buffer(i2c_t dev, uint8_t idx, uint8_t addr,
|
|
uint8_t *data, size_t len)
|
|
{
|
|
ctx[dev].addr[idx] = addr;
|
|
ctx[dev].buf[idx] = data;
|
|
ctx[dev].buf_end[idx] = data + len;
|
|
|
|
ctx[dev].buf_num = idx;
|
|
|
|
ctx[dev].buf_cur = 0;
|
|
ctx[dev].cur = ctx[dev].buf[0];
|
|
ctx[dev].end = ctx[dev].buf_end[0];
|
|
}
|
|
|
|
int i2c_read_bytes(i2c_t dev, uint16_t addr,
|
|
void *data, size_t len, uint8_t flags)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: 10 bit addresses */
|
|
if (flags) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
_init_buffer(dev, 0, 1 | (addr << 1), (void*)data, len);
|
|
|
|
/* set Start flag */
|
|
i2c->CONSET = I2CONSET_STA;
|
|
|
|
mutex_lock(&ctx[dev].tx_done);
|
|
return ctx[dev].res;
|
|
}
|
|
|
|
int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len,
|
|
uint8_t flags)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: 10 bit addresses */
|
|
if (flags) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
_init_buffer(dev, 0, addr << 1, (void*)data, len);
|
|
|
|
/* set Start flag */
|
|
i2c->CONSET = I2CONSET_STA;
|
|
|
|
mutex_lock(&ctx[dev].tx_done);
|
|
return ctx[dev].res;
|
|
}
|
|
|
|
int i2c_read_regs(i2c_t dev, uint16_t addr, uint16_t reg,
|
|
void *data, size_t len, uint8_t flags)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: 10 bit addresses */
|
|
if (flags & ~I2C_REG16) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Handle endianness of register if 16 bit */
|
|
if (flags & I2C_REG16) {
|
|
reg = htons(reg); /* Make sure register is in big-endian on I2C bus */
|
|
}
|
|
|
|
_init_buffer(dev, 0, addr << 1, (void*)®, (flags & I2C_REG16) ? 2 : 1);
|
|
_init_buffer(dev, 1, 1 | (addr << 1), (void*)data, len);
|
|
|
|
/* set Start flag */
|
|
i2c->CONSET = I2CONSET_STA;
|
|
|
|
mutex_lock(&ctx[dev].tx_done);
|
|
return ctx[dev].res;
|
|
}
|
|
|
|
int i2c_write_regs(i2c_t dev, uint16_t addr, uint16_t reg,
|
|
const void *data, size_t len, uint8_t flags)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
lpc23xx_i2c_t *i2c = i2c_config[dev].dev;
|
|
|
|
/* Check for wrong arguments given */
|
|
if (data == NULL || len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: 10 bit addresses */
|
|
if (flags & ~I2C_REG16) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Handle endianness of register if 16 bit */
|
|
if (flags & I2C_REG16) {
|
|
reg = htons(reg); /* Make sure register is in big-endian on I2C bus */
|
|
}
|
|
|
|
_init_buffer(dev, 0, addr << 1, (void*)®, (flags & I2C_REG16) ? 2 : 1);
|
|
_init_buffer(dev, 1, addr << 1, (void*)data, len);
|
|
|
|
/* set Start flag */
|
|
i2c->CONSET = I2CONSET_STA;
|
|
|
|
mutex_lock(&ctx[dev].tx_done);
|
|
return ctx[dev].res;
|
|
}
|
|
|
|
#if I2C_NUMOF > 0
|
|
static void I2C0_IRQHandler(void)
|
|
{
|
|
irq_handler(0);
|
|
VICVectAddr = 0; /* Acknowledge Interrupt */
|
|
}
|
|
#endif
|
|
#if I2C_NUMOF > 1
|
|
static void I2C1_IRQHandler(void)
|
|
{
|
|
irq_handler(1);
|
|
VICVectAddr = 0; /* Acknowledge Interrupt */
|
|
}
|
|
#endif
|
|
#if I2C_NUMOF > 2
|
|
static void I2C2_IRQHandler(void)
|
|
{
|
|
irq_handler(2);
|
|
VICVectAddr = 0; /* Acknowledge Interrupt */
|
|
}
|
|
#endif
|