From f9b2ca10b4ba986f221537826c591bf17de4fc3d Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 11 Apr 2021 15:41:57 +0200 Subject: [PATCH] import librfm3 from https://github.com/daniel-thompson/i2c-star.git Daniel Thompson has loads of useful code out there :) Reflects 07eff5c0b7333cfcc3d98ddc6ebf26e34c9c02ef of that repo. --- librfm3/include/librfm3.h | 38 ++++ librfm3/include/librfm3/i2c_ctx.h | 151 +++++++++++++ librfm3/src/i2c_ctx.c | 338 ++++++++++++++++++++++++++++++ mk/librfm3.rules.mk | 19 ++ 4 files changed, 546 insertions(+) create mode 100644 librfm3/include/librfm3.h create mode 100644 librfm3/include/librfm3/i2c_ctx.h create mode 100644 librfm3/src/i2c_ctx.c create mode 100644 mk/librfm3.rules.mk diff --git a/librfm3/include/librfm3.h b/librfm3/include/librfm3.h new file mode 100644 index 0000000..fb3023f --- /dev/null +++ b/librfm3/include/librfm3.h @@ -0,0 +1,38 @@ +/* + * Part of librfm3 (a utility library built on librfn and libopencm3) + * + * Copyright (C) 2014 Daniel Thompson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef RF_LIBRFM3_H_ + +/*! +\mainpage + +librfm3 is a collection of utility libraries that fit together nicely +with librfn and libopencm3. It is a collection of tools and toys for +ARM Cortex-M microcontrollers, especially the STM32 family. + +librfm3 is free software licensed under the GNU Lesser General Public License, +v3 or later. + +Source code can be downloaded from https://github.com/daniel-thompson/i2c-star + +*/ + +#include + +#endif // RF_LIBRFM3_H_ diff --git a/librfm3/include/librfm3/i2c_ctx.h b/librfm3/include/librfm3/i2c_ctx.h new file mode 100644 index 0000000..21d316e --- /dev/null +++ b/librfm3/include/librfm3/i2c_ctx.h @@ -0,0 +1,151 @@ +/* + * Part of librfm3 (a utility library built on librfn and libopencm3) + * + * Copyright (C) 2014 Daniel Thompson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef RF_I2C_CTX_H_ +#define RF_I2C_CTX_H_ + +#include +#include +#include + +/*! + * \defgroup librfm3_i2c_ctx I2C context manager + * + * \brief Protothreaded I2C driver for libopencm3. + * + * @{ + */ + +typedef struct i2c_ctx { + pt_t pt; //!< Protothread state for high-level functions + pt_t leaf; //!< Protothread state for low-level functions + + int err; //!< Error reporting (becomes non-zero on error) + bool verbose; //!< Automatically print error reports + + uint32_t i2c; //!< \private + uint32_t timeout; //!< \private + uint8_t bytes_remaining; //!< \private + int i; //!< \private +} i2c_ctx_t; + +typedef struct i2c_device_map { + uint16_t devices[8]; //!< Bitmap recording detected devices +} i2c_device_map_t; + +/*! + * \brief Initialize the context structure ready for a single I2C transaction. + * + * The context must be reinitialized before each I2C transaction otherwise + * the transaction may timeout immediately. + */ +void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c); + +/*! + * \brief Reset and reinitialize the I2C bus. + */ +void i2c_ctx_reset(i2c_ctx_t *c); + +/*! + * \brief Send a start condition. + * + * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). + */ +pt_state_t i2c_ctx_start(i2c_ctx_t *c); + +/*! + * \brief Send the target bus address. + * + * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). + * + * If bytes_to_read is zero then launch a write transaction, otherwise + * launch a read and manage the ACK/NACK and STOP generation based on + * the number of bytes to transfer. + */ +pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr, uint8_t bytes_to_read); + +/*! + * \brief Write a single byte. + * + * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). + */ +pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data); + +/*! + * \brief Read a single byte. + * + * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). + */ +pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data); + +/*! + * \brief Send a stop condition. + * + * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). + * + * Complete a low-level I2C write transaction. This is not required for + * reads because the drivers have to request the stop prior to reading + * the final bytes (so the stop is issued automatically). + * + */ +pt_state_t i2c_ctx_stop(i2c_ctx_t *c); + +/*! + * \brief Detect attached I2C devices. + * + * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). + */ +pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map); + +/*! + * \brief Write to an I2C register. + * + * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). + * + * \todo API leaves space for 16-bit addresses and registers but this + * is not implemented. + */ +pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, + uint8_t val); + +/*! + * \brief Read from an I2C register. + * + * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). + * + * \todo API leaves space for 16-bit addresses and registers but this + * is not implemented. + */ +pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, + uint8_t *val); + +/*! + * \brief Burst write to an I2C device. + * + * Primarily used for writes to memory devices or to initialize devices + * whose registers can be written to continuously (without a stop/restart). + * + * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). + */ +pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data, + uint8_t len); + +/*! @} */ + +#endif // RF_I2C_CTX_H_ diff --git a/librfm3/src/i2c_ctx.c b/librfm3/src/i2c_ctx.c new file mode 100644 index 0000000..e4316ac --- /dev/null +++ b/librfm3/src/i2c_ctx.c @@ -0,0 +1,338 @@ +/* + * Part of librfm3 (a utility library built on librfn and libopencm3) + * + * Copyright (C) 2014 Daniel Thompson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define D(x) { #x, x } +static const regdump_desc_t i2c_sr1_desc[] = { { "I2C_SR1", 0 }, + D(I2C_SR1_SMBALERT), + D(I2C_SR1_TIMEOUT), + D(I2C_SR1_PECERR), + D(I2C_SR1_OVR), + D(I2C_SR1_AF), + D(I2C_SR1_ARLO), + D(I2C_SR1_BERR), + D(I2C_SR1_TxE), + D(I2C_SR1_RxNE), + D(I2C_SR1_STOPF), + D(I2C_SR1_ADD10), + D(I2C_SR1_BTF), + D(I2C_SR1_ADDR), + D(I2C_SR1_SB), + { NULL, 0 } }; +#undef D + +static bool i2c_ctx_is_timed_out(i2c_ctx_t *c) +{ + if (cyclecmp32(time_now(), c->timeout) > 0) { + if (c->verbose) { + printf("I2C TRANSACTION TIMED OUT\n"); + regdump(I2C_SR1(c->i2c), i2c_sr1_desc); + } + + return true; + } + + return false; +} + +void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c) +{ + memset(c, 0, sizeof(*c)); + + c->i2c = pi2c; + c->timeout = time_now() + 100000; +} + +void i2c_ctx_reset(i2c_ctx_t *c) +{ + /* TODO: Latest opencm3 has this code built in */ + switch (c->i2c) { + case I2C1: + rcc_periph_reset_pulse(RST_I2C1); + rcc_periph_reset_pulse(RST_I2C1); + break; + case I2C2: + rcc_periph_reset_pulse(RST_I2C2); + rcc_periph_reset_pulse(RST_I2C2); + break; +#ifdef STM32F4 + case I2C3: + rcc_periph_reset_pulse(RST_I2C3); + rcc_periph_reset_pulse(RST_I2C3); + break; +#endif + } + + /* freq's numeric value ends up in MHz (i.e. in this case, 30) */ +#ifdef STM32F4 + uint16_t freq = I2C_CR2_FREQ_30MHZ; +#else + uint16_t freq = 36; +#endif + + /* CCR is the number of APB bus cycles in *half* an I2C bus + * cycle. For Sm (100Khz) this ends up as: + * freq * 1MHz / 2 * 100KHz + * freq * 1000000 / 200000 + * freq * 5 + * + * Similar trise is the number of APB bus cycles in the rise + * time (plus 1). For Sm (1us) this ends up as: + * freq * 1Mhz / (1/1us) + 1 + * freq * 1MHz / 1MHz + 1 + * freq + 1 + */ + + /* peripheral configuration */ + i2c_peripheral_disable(c->i2c); + i2c_set_clock_frequency(c->i2c, freq); + i2c_set_ccr(c->i2c, freq * 5); + i2c_set_trise(c->i2c, freq + 1); + i2c_set_own_7bit_slave_address(c->i2c, 0x32); + i2c_peripheral_enable(c->i2c); +} + +pt_state_t i2c_ctx_start(i2c_ctx_t *c) +{ + PT_BEGIN(&c->leaf); + + i2c_send_start(c->i2c); + + while (!i2c_ctx_is_timed_out(c) && + !((I2C_SR1(c->i2c) & I2C_SR1_SB) & + (I2C_SR2(c->i2c) & (I2C_SR2_MSL | I2C_SR2_BUSY)))) + PT_YIELD(); + + if (!I2C_SR1(c->i2c) & I2C_SR1_SB) { + i2c_ctx_reset(c); + c->err = EIO; + } + + PT_END(); +} + +pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr, + uint8_t bytes_to_read) +{ + PT_BEGIN(&c->leaf); + + c->bytes_remaining = bytes_to_read; + + i2c_send_7bit_address(c->i2c, addr, !!bytes_to_read); + + while (!i2c_ctx_is_timed_out(c) && // bad + !(I2C_SR1(c->i2c) & I2C_SR1_AF) && // bad + !(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) // good + PT_YIELD(); + + if (!(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) { + i2c_ctx_reset(c); + c->err = EIO; + } + + /* If we are only reading one byte we must get ready to NACK the + * final byte. + */ + if (c->bytes_remaining == 1) + I2C_CR1(c->i2c) &= ~I2C_CR1_ACK; + else if (c->bytes_remaining >= 2) + I2C_CR1(c->i2c) |= I2C_CR1_ACK; + + /* Read sequence has side effect or clearing I2C_SR1_ADDR */ + uint32_t reg32 __attribute__((unused)); + reg32 = I2C_SR2(c->i2c); + + if (c->bytes_remaining == 1) + i2c_send_stop(c->i2c); + + PT_END(); +} + +pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data) +{ + PT_BEGIN(&c->leaf); + + i2c_send_data(c->i2c, data); + + while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_BTF)) + PT_YIELD(); + + if (!(I2C_SR1(c->i2c) & I2C_SR1_BTF)) { + i2c_ctx_reset(c); + c->err = EIO; + } + + PT_END(); +} + +pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data) +{ + PT_BEGIN(&c->leaf); + + while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_RxNE)) + PT_YIELD(); + + if (!(I2C_SR1(c->i2c) & I2C_SR1_RxNE)) { + i2c_ctx_reset(c); + c->err = EIO; + PT_EXIT(); + } + + /* Need to NACK the final byte and STOP the transaction */ + if (--c->bytes_remaining == 1) { + I2C_CR1(c->i2c) &= ~I2C_CR1_ACK; + i2c_send_stop(c->i2c); + } + + *data = i2c_get_data(c->i2c); + + PT_END(); +} + +pt_state_t i2c_ctx_stop(i2c_ctx_t *c) +{ + PT_BEGIN(&c->leaf); + + while (!i2c_ctx_is_timed_out(c) && + !(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE))) + PT_YIELD(); + + if (!(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE))) { + i2c_ctx_reset(c); + c->err = EIO; + PT_EXIT(); + } + + i2c_send_stop(c->i2c); + + /* TODO: is it safe to just drop out of this */ + + PT_END(); +} + +pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map) +{ + PT_BEGIN(&c->pt); + + memset(map, 0, sizeof(*map)); + + for (c->i = 0; c->i < 0x80; c->i++) { + c->timeout = time_now() + 10000; + c->err = 0; + + PT_SPAWN(&c->leaf, i2c_ctx_start(c)); + if (c->err) + continue; + + PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, c->i, I2C_WRITE)); + if (c->err) + continue; + + PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); + if (c->err) + continue; + + map->devices[c->i / 16] |= 1 << (c->i % 16); + } + + PT_END(); +} + +pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, + uint8_t val) +{ + PT_BEGIN(&c->pt); + + PT_SPAWN(&c->leaf, i2c_ctx_start(c)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, val)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); + + PT_END(); +} + +pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, + uint8_t *val) +{ + PT_BEGIN(&c->pt); + + PT_SPAWN(&c->leaf, i2c_ctx_start(c)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_start(c)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_READ)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_getdata(c, val)); + + /* For reads STOP is generated automatically by sendaddr and/or + * getdata + */ + + PT_END(); +} + +pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data, + uint8_t len) +{ + PT_BEGIN(&c->pt); + + PT_SPAWN(&c->leaf, i2c_ctx_start(c)); + PT_EXIT_ON(c->err); + + PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); + PT_EXIT_ON(c->err); + + for (c->i = 0; c->i < len; c->i++) { + PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, data[c->i])); + PT_EXIT_ON(c->err); + } + + PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); + + PT_END(); +} diff --git a/mk/librfm3.rules.mk b/mk/librfm3.rules.mk new file mode 100644 index 0000000..4855268 --- /dev/null +++ b/mk/librfm3.rules.mk @@ -0,0 +1,19 @@ +# +# librfm3.rules.mk +# +# This file is part of the i2c-star project. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# + +LIBRFM3_DIR = ../../librfm3 + +OBJS += \ + i2c_ctx.o + +vpath %.c $(LIBRFM3_DIR)/src + +CPPFLAGS += -I$(LIBRFM3_DIR)/include