1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/lpc23xx/periph/i2c.c
Gunar Schorcht 007e29ebb5 cpu/periph/i2c: update implementations to new I2C API
Make all `spi_acquire` implementations return `void` and add assertions to check for valid device identifier where missing.
2021-11-29 06:35:25 +01:00

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_lpc23xx
* @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;
}
}
void i2c_acquire(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_lock(&ctx[dev].lock);
poweron(i2c_config[dev].dev);
}
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;
lpc23xx_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 */
/* cppcheck-suppress redundantAssignment
* (reason: writing 1 to volatile register to clear the interrupt) */
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*)&reg, (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*)&reg, (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