osmo-opencm3-projects/librfm3/src/i2c_ctx.c

339 lines
7.3 KiB
C
Raw Normal View History

/*
* Part of librfm3 (a utility library built on librfn and libopencm3)
*
* Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <librfm3/i2c_ctx.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/rcc.h>
#include <librfn/regdump.h>
#include <librfn/time.h>
#include <librfn/util.h>
#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();
}