diff --git a/boards/mcb2388/Makefile.features b/boards/mcb2388/Makefile.features index b18154210e92..95cb8ca31195 100644 --- a/boards/mcb2388/Makefile.features +++ b/boards/mcb2388/Makefile.features @@ -3,6 +3,7 @@ CPU_MODEL = lpc2388 # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_adc +FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer diff --git a/boards/mcb2388/include/periph_conf.h b/boards/mcb2388/include/periph_conf.h index 9ca887bf128b..ef37190cadab 100644 --- a/boards/mcb2388/include/periph_conf.h +++ b/boards/mcb2388/include/periph_conf.h @@ -101,6 +101,33 @@ static const adc_conf_t adc_config[] = { }; #define ADC_NUMOF (1) + +/** + * @name I2C configuration + * @{ + */ +static const i2c_conf_t i2c_config[] = { + { + .dev = I2C0, + .speed = I2C_SPEED_NORMAL, + .irq_prio = 5, + .pinsel_sda = 1, + .pinsel_scl = 1, + .pinsel_msk_sda = BIT22, /* P0.27 */ + .pinsel_msk_scl = BIT24, /* P0.28 */ + }, + { + .dev = I2C1, + .speed = I2C_SPEED_NORMAL, + .irq_prio = 5, + .pinsel_sda = 1, + .pinsel_scl = 1, + .pinsel_msk_sda = BIT6 | BIT7, /* P0.19 */ + .pinsel_msk_scl = BIT8 | BIT9, /* P0.20 */ + }, +}; + +#define I2C_NUMOF (2) /** @} */ #ifdef __cplusplus diff --git a/boards/msba2/Makefile.features b/boards/msba2/Makefile.features index 1f563e7c87fc..7d6f4b673dff 100644 --- a/boards/msba2/Makefile.features +++ b/boards/msba2/Makefile.features @@ -2,6 +2,7 @@ CPU = lpc2387 # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_adc +FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_pwm FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi diff --git a/boards/msba2/include/periph_conf.h b/boards/msba2/include/periph_conf.h index 59108bfd4dd6..b75ff5a0b1b7 100644 --- a/boards/msba2/include/periph_conf.h +++ b/boards/msba2/include/periph_conf.h @@ -155,6 +155,25 @@ static const adc_conf_t adc_config[] = { #define ADC_NUMOF ARRAY_SIZE(adc_config) /** @} */ +/** + * @name I2C configuration + * @{ + */ +static const i2c_conf_t i2c_config[] = { + { /* JP3 */ + .dev = I2C2, + .speed = I2C_SPEED_NORMAL, + .irq_prio = 5, + .pinsel_sda = 0, + .pinsel_scl = 0, + .pinsel_msk_sda = BIT21, /* P0.10 */ + .pinsel_msk_scl = BIT23, /* P0.11 */ + }, +}; + +/* used in arithmetic preprocessor expression, so no ARRAY_SIZE() */ +#define I2C_NUMOF (1) +/** @} */ #ifdef __cplusplus } diff --git a/cpu/lpc2387/include/periph_cpu.h b/cpu/lpc2387/include/periph_cpu.h index c0d6fa058f7f..7d48a143e8b5 100644 --- a/cpu/lpc2387/include/periph_cpu.h +++ b/cpu/lpc2387/include/periph_cpu.h @@ -195,7 +195,40 @@ typedef struct { uint32_t pinsel_msk; /**< PINSEL Mask for ADC pin */ } adc_conf_t; +/** + * @brief Override I2C clock speed values + * @{ + */ +#define HAVE_I2C_SPEED_T +typedef enum { + I2C_SPEED_LOW = 10000, /**< low speed mode: ~10 kbit/s */ + I2C_SPEED_NORMAL = 100000, /**< normal mode: ~100 kbit/s */ + I2C_SPEED_FAST = 400000, /**< fast mode: ~400 kbit/s */ +} i2c_speed_t; /* @} */ + +/** + * @brief I2C device configuration + */ +typedef struct { + lpc23xx_i2c_t *dev; /**< pointer to the I2C device */ + i2c_speed_t speed; /**< I2C bus speed */ + uint8_t irq_prio; /**< priority of the I2C IRQ */ + uint8_t pinsel_sda; /**< PINSEL# of the SDA pin */ + uint8_t pinsel_scl; /**< PINSEL# of the SCL pin */ + uint32_t pinsel_msk_sda;/**< SDA PINSEL Mask */ + uint32_t pinsel_msk_scl;/**< SCL PINSEL Mask */ +} i2c_conf_t; +/* @} */ + +/** + * @name Use shared I2C functions + * @{ + */ +#define PERIPH_I2C_NEED_READ_REG +#define PERIPH_I2C_NEED_WRITE_REG +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/lpc2387/include/vendor/lpc23xx.h b/cpu/lpc2387/include/vendor/lpc23xx.h index 60dc70e1f63f..0189bf3d145f 100644 --- a/cpu/lpc2387/include/vendor/lpc23xx.h +++ b/cpu/lpc2387/include/vendor/lpc23xx.h @@ -771,8 +771,35 @@ typedef struct { #define U3FDR (*(volatile unsigned long *)(UART3_BASE_ADDR + 0x28)) #define U3TER (*(volatile unsigned long *)(UART3_BASE_ADDR + 0x30)) +/** + * @brief Generic I2C register map + */ +typedef struct { + REG32 CONSET; /**< Control Set Register */ + REG32 STAT; /**< Status Register */ + REG32 DAT; /**< Data Register */ + REG32 ADR; /**< Slave Address Register */ + REG32 SCLH; /**< Duty Cycle High Half Word */ + REG32 SCLL; /**< Duty Cycle Low Half Word */ + REG32 CONCLR; /**< Control Clear Register */ +} lpc23xx_i2c_t; + +/* I2C Control Set Register */ +#define I2CONSET_AA 0x00000004 /**< Assert acknowledge */ +#define I2CONSET_SI 0x00000008 /**< Interrupt flag */ +#define I2CONSET_STO 0x00000010 /**< STOP flag */ +#define I2CONSET_STA 0x00000020 /**< START flag */ +#define I2CONSET_I2EN 0x00000040 /**< Interface enable */ + +/* I2C Control clear Register */ +#define I2CONCLR_AAC 0x00000004 /**< clear Assert ACK */ +#define I2CONCLR_SIC 0x00000008 /**< clear Interrupt */ +#define I2CONCLR_STAC 0x00000020 /**< clear START flag */ +#define I2CONCLR_I2ENC 0x00000040 /**< Interface disable */ + /* I2C Interface 0 */ #define I2C0_BASE_ADDR 0xE001C000 +#define I2C0 ((lpc23xx_i2c_t *)I2C0_BASE_ADDR) #define I20CONSET (*(volatile unsigned long *)(I2C0_BASE_ADDR + 0x00)) #define I20STAT (*(volatile unsigned long *)(I2C0_BASE_ADDR + 0x04)) #define I20DAT (*(volatile unsigned long *)(I2C0_BASE_ADDR + 0x08)) @@ -783,6 +810,7 @@ typedef struct { /* I2C Interface 1 */ #define I2C1_BASE_ADDR 0xE005C000 +#define I2C1 ((lpc23xx_i2c_t *)I2C1_BASE_ADDR) #define I21CONSET (*(volatile unsigned long *)(I2C1_BASE_ADDR + 0x00)) #define I21STAT (*(volatile unsigned long *)(I2C1_BASE_ADDR + 0x04)) #define I21DAT (*(volatile unsigned long *)(I2C1_BASE_ADDR + 0x08)) @@ -793,6 +821,7 @@ typedef struct { /* I2C Interface 2 */ #define I2C2_BASE_ADDR 0xE0080000 +#define I2C2 ((lpc23xx_i2c_t *)I2C2_BASE_ADDR) #define I22CONSET (*(volatile unsigned long *)(I2C2_BASE_ADDR + 0x00)) #define I22STAT (*(volatile unsigned long *)(I2C2_BASE_ADDR + 0x04)) #define I22DAT (*(volatile unsigned long *)(I2C2_BASE_ADDR + 0x08)) diff --git a/cpu/lpc2387/periph/i2c.c b/cpu/lpc2387/periph/i2c.c new file mode 100644 index 000000000000..fa61ffbe8b8a --- /dev/null +++ b/cpu/lpc2387/periph/i2c.c @@ -0,0 +1,484 @@ +/* + * 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 + * + * @} + */ + +#include +#include +#include + +#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 diff --git a/drivers/at24mac/at24mac.c b/drivers/at24mac/at24mac.c index d23e8e03d45a..e377bea3dc94 100644 --- a/drivers/at24mac/at24mac.c +++ b/drivers/at24mac/at24mac.c @@ -19,6 +19,7 @@ */ #include +#include "kernel_defines.h" #include "at24mac.h" #include "at24mac_params.h"