1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/rpx0xx: Add PIO I2C implementation

This commit is contained in:
Fabian Hüßler 2021-12-16 15:05:28 +01:00 committed by Fabian Hüßler
parent 117a901d6f
commit 116c579cb5
17 changed files with 1156 additions and 0 deletions

View File

@ -13,6 +13,7 @@ config BOARD_RPI_PICO
default y
select CPU_MODEL_RP2040
select HAS_PERIPH_ADC
select HAS_PERIPH_I2C
select HAS_PERIPH_UART
select HAS_PERIPH_SPI

View File

@ -2,6 +2,7 @@ CPU := rpx0xx
# Put defined MCU peripherals here (in alphabetical order)
FEATURES_PROVIDED += periph_adc
FEATURES_PROVIDED += periph_i2c
FEATURES_PROVIDED += periph_spi
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_uart

View File

@ -28,6 +28,16 @@
extern "C" {
#endif
/**
* @brief Silences the warning when an unsigned value is compared to 0
*
* This can be deleted when I2C is properly implemented.
*/
static inline unsigned _periph_numof_is_unsigned_0(void)
{
return 0;
}
static const uart_conf_t uart_config[] = {
{
.dev = UART0,
@ -110,6 +120,16 @@ static const adc_conf_t adc_config[] = {
#define ADC_NUMOF ARRAY_SIZE(adc_config)
/** @} */
/**
* @name I2C configuration
* @{
*/
/**
* @brief Number of I2C interfaces
*/
#define I2C_NUMOF _periph_numof_is_unsigned_0()
/** @} */
/**
* @name PIO configuration
* @{
@ -136,6 +156,24 @@ static const pio_conf_t pio_config[] = {
#define PIO_1_ISR1 isr_pio11 /**< ISR name of PIO 1 IRQ 1 */
#define PIO_NUMOF ARRAY_SIZE(pio_config) /**< Number of PIOs */
#if defined(PIO_I2C_CONFIG) || defined(DOXYGEN)
/**
* @brief PIO I2C configuration
*
* PIO_I2C_CONFIG should be defined during the build process to fit
* the users pin selection.
*/
static const pio_i2c_conf_t pio_i2c_config[] = {
PIO_I2C_CONFIG
};
/**
* @brief Number of PIO I2C configurations
*/
#define PIO_I2C_NUMOF ARRAY_SIZE(pio_i2c_config)
#else
#define pio_i2c_config ((pio_i2c_conf_t *)NULL)
#endif
/** @} */
#ifdef __cplusplus

View File

@ -16,6 +16,7 @@ config CPU_FAM_RPX0XX
select HAS_PERIPH_TIMER_PERIODIC
select HAS_PERIPH_UART_MODECFG
select HAS_PERIPH_UART_RECONFIGURE
select HAS_PIO_I2C
config CPU_FAM
default "RPX0XX" if CPU_FAM_RPX0XX

View File

@ -2,4 +2,8 @@ MODULE = cpu
DIRS = $(RIOTCPU)/cortexm_common periph
ifneq (,$(filter pio_i2c,$(USEMODULE)))
DIRS += pio/i2c
endif
include $(RIOTBASE)/Makefile.base

View File

@ -2,4 +2,9 @@ ifneq (,$(filter pio_%,$(USEMODULE)))
USEMODULE += periph_pio
endif
ifneq (,$(filter pio_i2c periph_i2c,$(FEATURES_USED)))
DEFAULT_MODULE += pio_autostart_i2c
USEMODULE += pio_i2c
endif
include $(RIOTCPU)/cortexm_common/Makefile.dep

View File

@ -9,3 +9,4 @@ FEATURES_PROVIDED += periph_pio
FEATURES_PROVIDED += periph_timer_periodic
FEATURES_PROVIDED += periph_uart_reconfigure
FEATURES_PROVIDED += periph_uart_modecfg
FEATURES_PROVIDED += pio_i2c

View File

@ -24,6 +24,7 @@
#include "vendor/RP2040.h"
#include "io_reg.h"
#include "macros/units.h"
#include "periph/pio.h" /* pio_t */
#ifdef __cplusplus
extern "C" {
@ -440,6 +441,16 @@ typedef struct {
IRQn_Type irqn1; /**< PIO IRQ1 interrupt number */
} pio_conf_t;
/**
* @brief PIO I2C configuration type
*/
typedef struct {
pio_t pio; /**< PIO number of the PIO to run this configuration */
gpio_t sda; /**< Pin to use as SDA pin */
gpio_t scl; /**< Pin to use as SCL pin */
unsigned irq; /**< PIO IRQ line to use */
} pio_i2c_conf_t;
/**
* @brief Get the PAD control register for the given GPIO pin as word
*

129
cpu/rpx0xx/periph/i2c.c Normal file
View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2021 Otto-von-Guericke Universität Magdeburg
*
* 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_rpx0xx
* @ingroup drivers_periph_i2c
* @{
*
* @file
* @brief Low-level I2C driver implementation
*
* @note This driver is so far only a dummy implementation
* to test the PIO I2C interface.
*
* @author Fabian Hüßler <fabian.huessler@ovgu.de>
* @}
*/
#include <errno.h>
#include "periph_conf.h"
#include "periph/i2c.h"
#include "periph/pio/i2c.h"
#define ENABLE_DEBUG 0
#include "debug.h"
void i2c_init(i2c_t dev)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
assert(i2c);
pio_t pio = pio_i2c_config[dev - I2C_NUMOF].pio;
if (pio_i2c_init_program(pio)) {
DEBUG("[i2c] init: PIO program allocation failed\n");
return;
}
pio_sm_t sm = pio_i2c_sm_lock(pio, i2c);
if (sm < 0) {
DEBUG("[i2c] init: PIO state machine allocation failed\n");
return;
}
if (pio_i2c_init(i2c, pio_i2c_get_program(pio),
pio_i2c_config[dev - I2C_NUMOF].sda,
pio_i2c_config[dev - I2C_NUMOF].scl,
pio_i2c_config[dev - I2C_NUMOF].irq)) {
DEBUG("[i2c] init: PIO I2C initialization failed\n");
pio_i2c_sm_unlock(i2c);
return;
}
}
}
void i2c_acquire(i2c_t dev)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
if (i2c) {
pio_i2c_acquire(i2c);
}
}
}
void i2c_release(i2c_t dev)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
if (i2c) {
pio_i2c_release(i2c);
}
}
}
int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data,
size_t len, uint8_t flags)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
return i2c ? pio_i2c_read_bytes(i2c->pio, i2c->sm, addr, data, len, flags) : -EINVAL;
}
return -EIO;
}
int i2c_read_regs(i2c_t dev, uint16_t addr, uint16_t reg,
void *data, size_t len, uint8_t flags)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
return i2c ? pio_i2c_read_regs(i2c->pio, i2c->sm, addr, reg, data, len, flags) : -EINVAL;
}
return -EIO;
}
int i2c_read_reg(i2c_t dev, uint16_t addr, uint16_t reg,
void *data, uint8_t flags)
{
return i2c_read_regs(dev, addr, reg, data, 1, flags);
}
int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data,
size_t len, uint8_t flags)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
return i2c ? pio_i2c_write_bytes(i2c->pio, i2c->sm, addr, data, len, flags) : -EINVAL;
}
return -EIO;
}
int i2c_write_regs(i2c_t dev, uint16_t addr, uint16_t reg,
const void *data, size_t len, uint8_t flags)
{
if (IS_USED(MODULE_PIO_I2C) && dev >= I2C_NUMOF) {
pio_i2c_bus_t *i2c = pio_i2c_get(dev - I2C_NUMOF);
return i2c ? pio_i2c_write_regs(i2c->pio, i2c->sm, addr, reg, data, len, flags) : -EINVAL;
}
return -EIO;
}
int i2c_write_reg(i2c_t dev, uint16_t addr, uint16_t reg,
uint8_t data, uint8_t flags)
{
return i2c_write_regs(dev, addr, reg, &data, 1, flags);
}

View File

@ -28,6 +28,7 @@
#include "bitarithm.h"
#include "io_reg.h"
#include "pio/pio.h"
#include "periph/pio/i2c.h"
#define ENABLE_DEBUG 0
#include "debug.h"
@ -141,6 +142,9 @@ void pio_init(pio_t pio)
void pio_start_programs(void)
{
if (IS_USED(MODULE_PIO_AUTOSTART_I2C)) {
pio_i2c_start_programs();
}
}
pio_sm_t pio_sm_lock(pio_t pio)

View File

@ -0,0 +1,6 @@
MODULE = pio_i2c
include $(RIOTBASE)/Makefile.base
include $(RIOTMAKE)/pio.inc.mk
i2c.c: i2c.pio.h i2c_set_scl_sda.pio.h

478
cpu/rpx0xx/pio/i2c/i2c.c Normal file
View File

@ -0,0 +1,478 @@
/*
* Copyright (C) 2021 Otto-von-Guericke Universität Magdeburg
*
* 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_rpx0xx
* @{
*
* @file
* @brief PIO program to emulate an I2C interface
*
* @author Fabian Hüßler <fabian.huessler@ovgu.de>
*
* @}
*/
#include <errno.h>
#include "byteorder.h"
#include "periph_conf.h"
#include "periph/gpio.h"
#include "periph/pio/i2c.h"
#include "pio/pio.h"
#include "i2c_set_scl_sda.pio.h"
#include "i2c.pio.h"
#define I2C_PIO_IRQN 0 /* use irq flag 0 */
#define I2C_PIO_IRQN_SM(sm) PIO_IRQ_REL_MASK(I2C_PIO_IRQN, sm) /* IRQ is relative */
#define INSTR_I2C_SCL0_SDA0 (((pio_instr_t[])PIO_SET_SCL_SDA_PROGRAM)[0])
#define INSTR_I2C_SCL0_SDA1 (((pio_instr_t[])PIO_SET_SCL_SDA_PROGRAM)[1])
#define INSTR_I2C_SCL1_SDA0 (((pio_instr_t[])PIO_SET_SCL_SDA_PROGRAM)[2])
#define INSTR_I2C_SCL1_SDA1 (((pio_instr_t[])PIO_SET_SCL_SDA_PROGRAM)[3])
#define I2C_INSTR_FRAME(icount) ((uint32_t)(((uint32_t)(icount)) << 10))
#define I2C_INSTR(instr) ((uint32_t)(instr))
#define I2C_DATA_FRAME(final, data, nak) \
((uint32_t)((((uint32_t)(final)) << 9) | \
(((uint32_t)(data)) << 1) | \
((uint32_t)(nak))))
enum {
I2C_REPSTART = 0x40 /* make sure this flag is not used by the I2C driver */
};
#if defined(PIO_I2C_NUMOF) || defined(DOXYGEN)
/**
* @brief Internal PIO I2C buses initialized from PIO_I2C_CONFIG
*/
static pio_i2c_bus_t _bus[PIO_I2C_NUMOF];
#endif
/**
* @brief Shared programs between I2C buses per PIO
*/
static pio_program_i2c_t _prog[PIO_NUMOF];
/**
* @brief PIO interrupt mask for all state machines per PIO
*/
static volatile uint32_t _irq[PIO_NUMOF];
static void pio_i2c_init_pins(pio_t pio, pio_sm_t sm, gpio_t sda, gpio_t scl)
{
const gpio_pad_ctrl_t pad_ctrl = {
.pull_up_enable = 1,
.input_enable = 1,
.drive_strength = DRIVE_STRENGTH_4MA,
.schmitt_trig_enable = 1,
};
const gpio_io_ctrl_t io_ctrl = {
.function_select = pio ? FUNCTION_SELECT_PIO1 : FUNCTION_SELECT_PIO0,
.output_enable_override = OUTPUT_ENABLE_OVERRIDE_INVERT
};
/* Assume that SDA is mapped as the first set pin (bit 0),
and SCL is mapped as the second set pin (bit 1).
Try to avoid glitching the bus while connecting the IOs. Get things set
up so that pin is driven down when PIO asserts OE low, and pulled up
otherwise. */
gpio_set_pad_config(scl, pad_ctrl);
gpio_set_pad_config(sda, pad_ctrl);
pio_sm_set_pins_with_mask(pio, sm, (1u << sda) | (1u << scl), (1u << sda) | (1u << scl));
pio_sm_set_pindirs_with_mask(pio, sm, (1u << sda) | (1u << scl), (1u << sda) | (1u << scl));
gpio_set_io_config(scl, io_ctrl);
gpio_set_io_config(sda, io_ctrl);
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << sda) | (1u << scl));
}
static inline bool pio_i2c_check_error(pio_t pio, pio_sm_t sm)
{
bool error;
uint32_t status = irq_disable();
error = !!(_irq[pio] & I2C_PIO_IRQN_SM(sm));
irq_restore(status);
return error;
}
static inline void pio_i2c_clear_error(pio_t pio, pio_sm_t sm)
{
uint32_t status = irq_disable();
_irq[pio] &= ~I2C_PIO_IRQN_SM(sm);
irq_restore(status);
}
static void pio_i2c_resume_after_error(pio_t pio, pio_sm_t sm)
{
PIO0_Type *dev = pio_config[pio].dev;
pio_sm_ctrl_regs_t *ctrl = &PIO_SM_CTRL_BASE(dev)[sm];
uint32_t jmp_addr = (ctrl->execctrl & PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Msk)
>> PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Pos;
pio_sm_exec(pio, sm, pio_inst_jmp(PIO_INST_JMP_COND_NONE, jmp_addr));
}
static inline bool pio_i2c_check_idle(pio_t pio, pio_sm_t sm)
{
PIO0_Type *dev = pio_config[pio].dev;
pio_sm_ctrl_regs_t *ctrl = &PIO_SM_CTRL_BASE(dev)[sm];
uint32_t idle = (ctrl->execctrl & PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Msk)
>> PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Pos;
return ctrl->addr == idle;
}
static inline void _pio_i2c_put(pio_t pio, pio_sm_t sm, uint32_t word)
{
while (pio_sm_transmit_word(pio, sm, word)) {
/* do not block on unexpected receive */
pio_sm_receive_word(pio, sm, NULL);
}
}
static void pio_i2c_start(pio_t pio, pio_sm_t sm)
{
_pio_i2c_put(pio, sm, I2C_INSTR_FRAME(1) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL1_SDA0) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL0_SDA0) << 16);
}
static void pio_i2c_stop(pio_t pio, pio_sm_t sm)
{
_pio_i2c_put(pio, sm, I2C_INSTR_FRAME(2) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL0_SDA0) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL1_SDA0) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL1_SDA1) << 16);
}
static void pio_i2c_repstart(pio_t pio, pio_sm_t sm)
{
_pio_i2c_put(pio, sm, I2C_INSTR_FRAME(3) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL0_SDA1) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL1_SDA1) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL1_SDA0) << 16);
_pio_i2c_put(pio, sm, I2C_INSTR(INSTR_I2C_SCL0_SDA0) << 16);
}
static void _irq_sm(pio_t pio, unsigned irq)
{
_irq[pio] |= PIO_IRQ_MASK(irq);
pio_sm_t sm = pio_irq_rel_sm(I2C_PIO_IRQN, irq);
pio_i2c_resume_after_error(pio, sm);
}
static pio_isr_sm_vec_t _vec = {
.sm = _irq_sm,
};
pio_i2c_bus_t *pio_i2c_get(pio_i2c_t id)
{
#ifdef PIO_I2C_NUMOF
return (id < PIO_I2C_NUMOF) ? &_bus[id] : NULL;
#endif
(void)id;
return NULL;
}
unsigned pio_i2c_numof(void)
{
#ifdef PIO_I2C_NUMOF
return PIO_I2C_NUMOF;
#endif
return 0;
}
const pio_program_i2c_t *pio_i2c_get_program(pio_t pio)
{
assert(pio < PIO_NUMOF);
return &_prog[pio];
}
int pio_i2c_init_program(pio_t pio)
{
int ret;
if (!_prog[pio].base.instr_numof) {
_prog[pio].base = pio_i2c_create_program();
}
if (_prog[pio].base.location == PIO_PROGRAM_NOT_LOADED) {
if ((ret = pio_alloc_program(pio, &_prog[pio].base))) {
return ret;
}
if (!_prog[pio].base.written) {
if ((ret = pio_i2c_write_program(pio, &_prog[pio]))) {
return ret;
}
}
}
return 0;
}
void pio_i2c_deinit_program(pio_t pio)
{
pio_free_program(pio, &_prog[pio].base);
}
pio_sm_t pio_i2c_sm_lock(pio_t pio, pio_i2c_bus_t *i2c)
{
pio_sm_t sm = i2c->sm;
if (!(_prog[pio].ref_mask & PIO_SM_MASK(sm))) {
if ((sm = pio_sm_lock(pio)) >= 0) {
_prog[pio].ref_mask |= PIO_SM_MASK(sm);
i2c->pio = pio;
i2c->sm = sm;
mutex_init(&i2c->mtx);
}
}
return sm;
}
void pio_i2c_sm_unlock(pio_i2c_bus_t *i2c)
{
_prog[i2c->pio].ref_mask &= ~PIO_SM_MASK(i2c->sm);
pio_sm_unlock(i2c->pio, i2c->sm);
}
void pio_i2c_start_programs(void)
{
for (int i = 0; i < (int)pio_i2c_numof(); i++) {
pio_i2c_bus_t *i2c = pio_i2c_get(i);
if (i2c) {
pio_sm_start(i2c->pio, i2c->sm);
}
}
}
void pio_i2c_stop_programs(void)
{
for (int i = 0; i < (int)pio_i2c_numof(); i++) {
pio_i2c_bus_t *i2c = pio_i2c_get(i);
if (i2c) {
pio_sm_stop(i2c->pio, i2c->sm);
}
}
}
int pio_i2c_write_program(pio_t pio, pio_program_i2c_t *pro)
{
pio_instr_t instr[] = PIO_I2C_PROGRAM;
int ret;
if ((ret = pio_write_program(pio, &pro->base, instr))) {
return ret;
}
return 0;
}
int pio_i2c_init(pio_i2c_bus_t *bus,
const pio_program_i2c_t *pro,
gpio_t sda, gpio_t scl, unsigned irq)
{
pio_program_conf_t conf = PIO_I2C_PROGRAM_CONF;
pio_t pio = bus->pio;
pio_sm_t sm = bus->sm;
int ret;
if (sda >= 32 || scl >= 32 || irq >= 2) {
return -ENOTSUP;
}
if ((ret = pio_sm_init_common(pio, sm, &pro->base, &conf))) {
return ret;
}
pio_sm_set_out_pins(pio, sm, sda, 1);
pio_sm_set_set_pins(pio, sm, sda, 2);
pio_sm_set_in_pins(pio, sm, sda);
pio_sm_set_sideset_pins(pio, sm, scl);
pio_sm_set_out_shift(pio, sm, false, true, 16);
pio_sm_set_in_shift(pio, sm, false, true, 8);
pio_sm_set_jmp_pin(pio, sm, sda);
pio_sm_set_clkdiv(pio, sm, pio_sm_clkdiv(3200000));
pio_i2c_init_pins(pio, sm, sda, scl);
pio_irq_clear(pio, pio_irq_rel_index(I2C_PIO_IRQN, sm));
/* Given the absolute interrupt index I2C_PIO_IRQN
and the relative interrupt instruction in the PIO program,
sm will raise the interrupt flag pio_irq_rel_index(I2C_PIO_IRQN, sm)
in the IRQ register. */
pio_set_isr_sm_vec(pio, pio_irq_rel_index(I2C_PIO_IRQN, sm), &_vec);
pio_irq_enable(pio, irq, PIO_IRQ_SM_0 << pio_irq_rel_index(I2C_PIO_IRQN, sm));
pio_sm_clear_fifos(pio, sm);
pio_sm_restart(pio, sm);
return 0;
}
void pio_i2c_acquire(pio_i2c_bus_t *bus)
{
assert(bus);
mutex_lock(&bus->mtx);
}
void pio_i2c_release(pio_i2c_bus_t *bus)
{
assert(bus);
mutex_unlock(&bus->mtx);
}
int pio_i2c_read_regs(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, void *data, size_t len, uint8_t flags)
{
if (flags & (I2C_NOSTOP | I2C_NOSTART)) {
return -EOPNOTSUPP;
}
reg = htons(reg);
int error;
if ((error = pio_i2c_write_bytes(pio, sm, addr,
(flags & I2C_REG16) ? ((uint8_t *)&reg)
: ((uint8_t *)&reg) + 1,
(flags & I2C_REG16) ? 2 : 1,
flags | I2C_NOSTOP))) {
return error;
}
if ((error = pio_i2c_read_bytes(pio, sm, addr, data, len,
flags | I2C_REPSTART))) {
return error;
}
return 0;
}
int pio_i2c_read_bytes(pio_t pio, pio_sm_t sm, uint16_t addr,
void *data, size_t len, uint8_t flags)
{
bool first = true;
unsigned len_rx = len;
uint32_t word;
int error = 0; /* potential error */
int status = 0; /* actual error */
pio_i2c_clear_error(pio, sm);
if (flags & I2C_ADDR10) {
return -EOPNOTSUPP;
}
addr = (uint8_t)addr;
if (!(flags & I2C_NOSTART)) {
if (flags & I2C_REPSTART) {
pio_i2c_repstart(pio, sm);
}
else {
pio_i2c_start(pio, sm);
}
}
while (!pio_sm_receive_word(pio, sm, NULL)) {}
if (!(flags & I2C_NOSTART)) {
_pio_i2c_put(pio, sm, I2C_DATA_FRAME(0, (addr << 1) | I2C_READ, 1) << 16);
error = -ENXIO;
len_rx += 1;
while (!pio_sm_tx_fifo_empty(pio, sm) || !pio_i2c_check_idle(pio, sm)) {}
if ((status = pio_i2c_check_error(pio, sm))) {
pio_sm_clear_fifos(pio, sm);
}
}
while (!pio_sm_tx_fifo_empty(pio, sm) || !pio_i2c_check_idle(pio, sm)) {}
while ((len_rx || len) && !status) {
if ((status = pio_i2c_check_error(pio, sm))) {
pio_sm_clear_fifos(pio, sm);
break;
}
/* len == 1 means final (last byte in transfer) */
/* 0xff is a dummy data byte */
/* Any NAK except for the last byte will cause the transfer to fail */
/* see frame documentation in i2c.pio */
if (len && !pio_sm_transmit_word(pio, sm, I2C_DATA_FRAME(len == 1, 0xff, len == 1) << 16)) {
len--;
error = -EIO;
}
/* read a byte for every byte we sent */
if (len_rx && !pio_sm_receive_word(pio, sm, &word)) {
if (!first) {
*((uint8_t *)data) = (uint8_t)word;
data = ((uint8_t *)data) + 1;
}
else {
first = false;
}
len_rx--;
}
}
while (!pio_sm_tx_fifo_empty(pio, sm)) {}
if (!(flags & I2C_NOSTOP) || status || (status = pio_i2c_check_error(pio, sm))) {
pio_i2c_stop(pio, sm);
}
while (!pio_sm_tx_fifo_empty(pio, sm)) {}
return status ? error : (pio_i2c_check_error(pio, sm) ? -EIO : 0);
}
int pio_i2c_write_bytes(pio_t pio, pio_sm_t sm, uint16_t addr,
const void *data, size_t len, uint8_t flags)
{
unsigned len_rx = len;
int error = 0; /* potential error */
int status = 0; /* actual error */
pio_i2c_clear_error(pio, sm);
if (flags & I2C_ADDR10) {
return -EOPNOTSUPP;
}
addr = (uint8_t)addr;
if (!(flags & I2C_NOSTART)) {
if (flags & I2C_REPSTART) {
pio_i2c_repstart(pio, sm);
}
else {
pio_i2c_start(pio, sm);
}
}
while (!pio_sm_receive_word(pio, sm, NULL)) {}
if (!(flags & I2C_NOSTART)) {
_pio_i2c_put(pio, sm, I2C_DATA_FRAME(0, (addr << 1), 1) << 16);
error = -ENXIO;
len_rx += 1;
while (!pio_sm_tx_fifo_empty(pio, sm) || !pio_i2c_check_idle(pio, sm)) {}
if ((status = pio_i2c_check_error(pio, sm))) {
pio_sm_clear_fifos(pio, sm);
}
}
while (!pio_sm_tx_fifo_empty(pio, sm) || !pio_i2c_check_idle(pio, sm)) {}
while ((len_rx || len) && !status) {
if ((status = pio_i2c_check_error(pio, sm))) {
pio_sm_clear_fifos(pio, sm);
break;
}
/* len == 1 means final (last byte in transfer) */
/* transmit next data byte */
/* don´t abort on NAK */
/* see frame documentation in i2c.pio */
if (len && !pio_sm_transmit_word(pio, sm, I2C_DATA_FRAME(len == 1, *(uint8_t *)data, 1) << 16)) {
len--;
data = ((uint8_t *)data) + 1;
error = -EIO;
}
/* dummy receive for every sent byte */
if (len_rx && !pio_sm_receive_word(pio, sm, NULL)) {
/* https://github.com/raspberrypi/pico-examples/issues/168
* Switching autopush on for Rx and off for Tx seems to bring a mysterious bug with it.
* So instead we also drain the Rx FIFO while transmitting. */
len_rx--;
}
}
while (!pio_sm_tx_fifo_empty(pio, sm)) {}
if (!(flags & I2C_NOSTOP) || status || (status = pio_i2c_check_error(pio, sm))) {
pio_i2c_stop(pio, sm);
}
while (!pio_sm_tx_fifo_empty(pio, sm)) {}
return status ? error : (pio_i2c_check_error(pio, sm) ? -EIO : 0);
}
int pio_i2c_write_regs(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, const void *data, size_t len, uint8_t flags)
{
if (flags & (I2C_NOSTOP | I2C_NOSTART)) {
return -EOPNOTSUPP;
}
reg = htons(reg);
int error;
if ((error = pio_i2c_write_bytes(pio, sm, addr,
(flags & I2C_REG16) ? ((uint8_t *)&reg)
: ((uint8_t *)&reg) + 1,
(flags & I2C_REG16) ? 2 : 1,
flags | I2C_NOSTOP))) {
return error;
}
if ((error = pio_i2c_write_bytes(pio, sm, addr, data, len, flags | I2C_NOSTART))) {
return error;
}
return 0;
}

121
cpu/rpx0xx/pio/i2c/i2c.pio Normal file
View File

@ -0,0 +1,121 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program i2c
.side_set 1 opt pindirs
; TX Encoding:
; | 15:10 | 9 | 8:1 | 0 |
; | Instr | Final | Data | NAK |
;
; If Instr has a value n > 0, then this FIFO word has no
; data payload, and the next n + 1 words will be executed as instructions.
; Otherwise, shift out the 8 data bits, followed by the ACK bit.
;
; The Instr mechanism allows stop/start/repstart sequences to be programmed
; by the processor, and then carried out by the state machine at defined points
; in the datastream.
;
; The "Final" field should be set for the final byte in a transfer.
; This tells the state machine to ignore a NAK: if this field is not
; set, then any NAK will cause the state machine to halt and interrupt.
;
; Autopull should be enabled, with a threshold of 16.
; Autopush should be enabled, with a threshold of 8.
; The TX FIFO should be accessed with halfword writes, to ensure
; the data is immediately available in the OSR.
;
; Pin mapping:
; - Input pin 0 is SDA, 1 is SCL (if clock stretching used)
; - Jump pin is SDA
; - Side-set pin 0 is SCL
; - Set pin 0 is SDA
; - OUT pin 0 is SDA
; - SCL must be SDA + 1 (for wait mapping)
;
; The OE outputs should be inverted in the system IO controls!
; (It's possible for the inversion to be done in this program,
; but costs 2 instructions: 1 for inversion, and one to cope
; with the side effect of the MOV on TX shift counter.)
do_nack:
jmp y-- entry_point ; Continue if NAK was expected
irq wait 0 rel ; Otherwise stop, ask for help
do_byte:
set x, 7 ; Loop 8 times
bitloop:
out pindirs, 1 [7] ; Serialise write data (all-ones if reading)
nop side 1 [2] ; SCL rising edge
wait 1 pin, 1 [4] ; Allow clock to be stretched
in pins, 1 [7] ; Sample read data in middle of SCL pulse
jmp x-- bitloop side 0 [7] ; SCL falling edge
; Handle ACK pulse
out pindirs, 1 [7] ; On reads, we provide the ACK.
nop side 1 [7] ; SCL rising edge
wait 1 pin, 1 [7] ; Allow clock to be stretched
jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK
public entry_point:
.wrap_target
out x, 6 ; Unpack Instr count
out y, 1 ; Unpack the NAK ignore bit
jmp !x do_byte ; Instr == 0, this is a data record.
out null, 32 ; Instr > 0, remainder of this OSR is invalid
do_exec:
out exec, 16 ; Execute one instruction per FIFO word
jmp x-- do_exec ; Repeat n + 1 times
.wrap
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
assert(pin_scl == pin_sda + 1);
pio_sm_config c = i2c_program_get_default_config(offset);
// IO mapping
sm_config_set_out_pins(&c, pin_sda, 1);
sm_config_set_set_pins(&c, pin_sda, 1);
sm_config_set_in_pins(&c, pin_sda);
sm_config_set_sideset_pins(&c, pin_scl);
sm_config_set_jmp_pin(&c, pin_sda);
sm_config_set_out_shift(&c, false, true, 16);
sm_config_set_in_shift(&c, false, true, 8);
float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
sm_config_set_clkdiv(&c, div);
// Try to avoid glitching the bus while connecting the IOs. Get things set
// up so that pin is driven down when PIO asserts OE low, and pulled up
// otherwise.
gpio_pull_up(pin_scl);
gpio_pull_up(pin_sda);
uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
pio_gpio_init(pio, pin_sda);
gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
pio_gpio_init(pio, pin_scl);
gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
// Clear IRQ flag before starting
hw_clear_bits(&pio->inte0, 1u << sm);
hw_clear_bits(&pio->inte1, 1u << sm);
pio->irq = 1u << sm;
// Configure and start SM
pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@ -0,0 +1,27 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program set_scl_sda
.side_set 1 opt
; Assemble a table of instructions which software can select from, and pass
; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as
; a complete program.
set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0
set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1
set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0
set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1
% c-sdk {
// Define order of our instruction table
enum {
I2C_SC0_SD0 = 0,
I2C_SC0_SD1,
I2C_SC1_SD0,
I2C_SC1_SD1
};
%}

View File

@ -0,0 +1,318 @@
/*
* Copyright (C) 2021 Otto-von-Guericke Universität Magdeburg
*
* 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.
*/
/**
* @defgroup drivers_periph_pio_i2c PIO I2C program
* @ingroup drivers_periph
* @brief PIO I2C program interface
*
* @experimental This API is experimental and in an early state -
* expect changes!
* @{
* @file
* @brief PIO I2C program interface
*
* @author Fabian Hüßler <fabian.huessler@ovgu.de>
*/
#ifndef PERIPH_PIO_I2C_H
#define PERIPH_PIO_I2C_H
#include "periph/i2c.h"
#include "periph/pio.h"
#include "mutex.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief PIO I2C descriptor type compatible with @ref i2c_t
*/
typedef i2c_t pio_i2c_t;
/**
* @brief PIO I2C program type
*/
typedef struct pio_program_i2c {
pio_program_t base; /**< PIO base program */
unsigned ref_mask; /**< Mask of referencing PIO state machines */
} pio_program_i2c_t;
/**
* @brief PIO I2C emulated bus type
*/
typedef struct pio_i2c_bus {
pio_t pio; /**< PIO index */
pio_sm_t sm; /**< State machine index */
mutex_t mtx; /**< Mutex to protect the bus from concurrent accesses */
} pio_i2c_bus_t;
/**
* @brief Get access to a PIO I2C instance configured with PIO_I2C_CONFIG
*
* @param[in] id PIO I2C ID
*
* @return PIO I2C objects
*/
pio_i2c_bus_t *pio_i2c_get(pio_i2c_t id);
/**
* @brief Query the number of PIO I2C instances configured with PIO_I2C_CONFIG
*
* @return Number of PIO I2C instances
*/
unsigned pio_i2c_numof(void);
/**
* @brief Get const I2C program reference
*
* @param[in] pio PIO index
*
* @return PIO I2C program allocated to PIO @p pio
*/
const pio_program_i2c_t *pio_i2c_get_program(pio_t pio);
/**
* @brief Create, allocate, and write a PIO I2C program
*
* This function does nothing if the program is already
* created, allocated, and written.
*
* @param[in] pio PIO index
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_init_program(pio_t pio);
/**
* @brief Free a PIO I2C program
*
* @param[in] pio PIO index
*/
void pio_i2c_deinit_program(pio_t pio);
/**
* @brief Acquire a PIO state machine of PIO @p pio to run the PIO I2C program
*
* @param[in] pio PIO index
* @param[out] i2c PIO I2C bus
*
* @return A valid state machine index or a negative number on error
*/
pio_sm_t pio_i2c_sm_lock(pio_t pio, pio_i2c_bus_t *i2c);
/**
* @brief Release a PIO state machine of PIO @p pio
*
* @param[in, out] i2c PIO I2C bus
*/
void pio_i2c_sm_unlock(pio_i2c_bus_t *i2c);
/**
* @brief Start PIO I2C programs configured with PIO_I2C_CONFIG
*
* @note No execution is started if
* "DISABLE_MODULE += pio_autostart_i2c" is set
*/
void pio_i2c_start_programs(void);
/**
* @brief Stop PIO I2C programs configured with PIO_I2C_CONFIG
*/
void pio_i2c_stop_programs(void);
/**
* @brief Write a PIO I2C program to instruction memory
*
* @param[in] pio PIO index
* @param[in, out] pro Allocated PIO I2C program
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_write_program(pio_t pio, pio_program_i2c_t *pro);
/**
* @brief Setup a state machine to run the I2C program
*
* @pre The program @p pro must have been allocated.
*
* @param[out] bus PIO I2C bus
* @param[in] pro Shared program base
* @param[in] sda SDA pin
* @param[in] scl SCL pin
* @param[in] irq IRQ line, 0 or 1
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_init(pio_i2c_bus_t *bus,
const pio_program_i2c_t *pro,
gpio_t sda, gpio_t scl, unsigned irq);
/**
* @brief Get exclusive access to the emulated I2C bus
*
* @param[in] bus PIO I2C bus
*/
void pio_i2c_acquire(pio_i2c_bus_t *bus);
/**
* @brief Release emulated I2C bus
*
* @param[in] bus PIO I2C bus
*/
void pio_i2c_release(pio_i2c_bus_t *bus);
/**
* @brief Emulate @ref i2c_read_regs
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] reg Register address to read from (8- or 16-bit right-aligned)
* @param[out] data Memory location to store received data
* @param[in] len The number of bytes to read into @p data
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_read_regs(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, void *data, size_t len, uint8_t flags);
/**
* @brief Emulate @ref i2c_read_reg
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] reg Register address to read from (8- or 16-bit right-aligned)
* @param[out] data Memory location to store received data
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
static inline int pio_i2c_read_reg(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, void *data, uint8_t flags)
{
return pio_i2c_read_regs(pio, sm, addr, reg, data, 1, flags);
}
/**
* @brief Emulate @ref i2c_read_bytes
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[out] data Memory location to store received data
* @param[in] len The number of bytes to read into @p data
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_read_bytes(pio_t pio, pio_sm_t sm, uint16_t addr,
void *data, size_t len, uint8_t flags);
/**
* @brief Emulate @ref i2c_read_byte
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[out] data Memory location to store received data
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
static inline int pio_i2c_read_byte(pio_t pio, pio_sm_t sm, uint16_t addr,
void *data, uint8_t flags)
{
return pio_i2c_read_bytes(pio, sm, addr, data, 1, flags);
}
/**
* @brief Emulate @ref i2c_write_bytes
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] data Array holding the bytes to write to the device
* @param[in] len The number of bytes to write
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_write_bytes(pio_t pio, pio_sm_t sm, uint16_t addr,
const void *data, size_t len, uint8_t flags);
/**
* @brief Emulate @ref i2c_write_byte
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] data Byte to write to the device
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
static inline int pio_i2c_write_byte(pio_t pio, pio_sm_t sm, uint16_t addr,
uint8_t data, uint8_t flags)
{
return pio_i2c_write_bytes(pio, sm, addr, &data, 1, flags);
}
/**
* @brief Emulate @ref i2c_write_regs
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] reg register address to read from (8- or 16-bit, right-aligned)
* @param[in] data Array holding the bytes to write to the device
* @param[in] len The number of bytes to write
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
int pio_i2c_write_regs(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, const void *data, size_t len, uint8_t flags);
/**
* @brief Emulate @ref i2c_write_reg
*
* @param[in] pio PIO index
* @param[in] sm PIO state machine index
* @param[in] addr 7-bit or 10-bit device address (right-aligned)
* @param[in] reg register address to read from (8- or 16-bit, right-aligned)
* @param[in] data Array holding the bytes to write to the device
* @param[in] flags Optional flags (see @ref i2c_flags_t)
*
* @return Success: 0
* Failure: != 0
*/
static inline int pio_i2c_write_reg(pio_t pio, pio_sm_t sm, uint16_t addr,
uint16_t reg, uint8_t data, uint8_t flags)
{
return pio_i2c_write_regs(pio, sm, addr, reg, &data, 1, flags);
}
#ifdef __cplusplus
}
#endif
#endif /* PERIPH_PIO_I2C_H */
/** @} */

View File

@ -23,6 +23,7 @@
#define USB_H_USER_IS_RIOT_INTERNAL
#include "kernel_defines.h"
#include "periph_conf.h"
#include "periph_cpu.h"
#ifdef MODULE_PERIPH_INIT
@ -124,6 +125,11 @@ void periph_init(void)
for (int i = 0; i < (int)PIO_NUMOF; i++) {
pio_init(PIO_DEV(i));
}
#if defined(MODULE_PERIPH_INIT_I2C) && defined(PIO_I2C_NUMOF)
for (unsigned i = 0; i < PIO_I2C_NUMOF; i++) {
i2c_init(I2C_DEV(I2C_NUMOF + i));
}
#endif
pio_start_programs();
#endif

View File

@ -465,6 +465,11 @@ config HAS_PICOLIBC
help
Indicates that the picolibc C library is available for the platform.
config HAS_PIO_I2C
bool
help
Indicates that there is a PIO program to provide emulated I2C
config HAS_NEWLIB
bool
help