diff --git a/boards/rpi-pico/Kconfig b/boards/rpi-pico/Kconfig index c83fc56a7e..d1244d9b03 100644 --- a/boards/rpi-pico/Kconfig +++ b/boards/rpi-pico/Kconfig @@ -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 diff --git a/boards/rpi-pico/Makefile.features b/boards/rpi-pico/Makefile.features index a8ac488425..f68cf169c8 100644 --- a/boards/rpi-pico/Makefile.features +++ b/boards/rpi-pico/Makefile.features @@ -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 diff --git a/boards/rpi-pico/include/periph_conf.h b/boards/rpi-pico/include/periph_conf.h index 9c403adffb..76d5abfa02 100644 --- a/boards/rpi-pico/include/periph_conf.h +++ b/boards/rpi-pico/include/periph_conf.h @@ -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 diff --git a/cpu/rpx0xx/Kconfig b/cpu/rpx0xx/Kconfig index 472e687303..27d3872af7 100644 --- a/cpu/rpx0xx/Kconfig +++ b/cpu/rpx0xx/Kconfig @@ -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 diff --git a/cpu/rpx0xx/Makefile b/cpu/rpx0xx/Makefile index 5943e03a39..e1812042c6 100644 --- a/cpu/rpx0xx/Makefile +++ b/cpu/rpx0xx/Makefile @@ -2,4 +2,8 @@ MODULE = cpu DIRS = $(RIOTCPU)/cortexm_common periph +ifneq (,$(filter pio_i2c,$(USEMODULE))) + DIRS += pio/i2c +endif + include $(RIOTBASE)/Makefile.base diff --git a/cpu/rpx0xx/Makefile.dep b/cpu/rpx0xx/Makefile.dep index d500ccfebb..71f7903107 100644 --- a/cpu/rpx0xx/Makefile.dep +++ b/cpu/rpx0xx/Makefile.dep @@ -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 diff --git a/cpu/rpx0xx/Makefile.features b/cpu/rpx0xx/Makefile.features index 8e0919b8cd..c589873656 100644 --- a/cpu/rpx0xx/Makefile.features +++ b/cpu/rpx0xx/Makefile.features @@ -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 diff --git a/cpu/rpx0xx/include/periph_cpu.h b/cpu/rpx0xx/include/periph_cpu.h index cc32dc2322..a977e0eeda 100644 --- a/cpu/rpx0xx/include/periph_cpu.h +++ b/cpu/rpx0xx/include/periph_cpu.h @@ -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 * diff --git a/cpu/rpx0xx/periph/i2c.c b/cpu/rpx0xx/periph/i2c.c new file mode 100644 index 0000000000..754611b0cb --- /dev/null +++ b/cpu/rpx0xx/periph/i2c.c @@ -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 + * @} + */ + +#include + +#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); +} diff --git a/cpu/rpx0xx/periph/pio.c b/cpu/rpx0xx/periph/pio.c index 0aa62d1e8e..ae87e421aa 100644 --- a/cpu/rpx0xx/periph/pio.c +++ b/cpu/rpx0xx/periph/pio.c @@ -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) diff --git a/cpu/rpx0xx/pio/i2c/Makefile b/cpu/rpx0xx/pio/i2c/Makefile new file mode 100644 index 0000000000..bfa8c3d8f6 --- /dev/null +++ b/cpu/rpx0xx/pio/i2c/Makefile @@ -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 diff --git a/cpu/rpx0xx/pio/i2c/i2c.c b/cpu/rpx0xx/pio/i2c/i2c.c new file mode 100644 index 0000000000..10611073ee --- /dev/null +++ b/cpu/rpx0xx/pio/i2c/i2c.c @@ -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 + * + * @} + */ +#include +#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 *)®) + : ((uint8_t *)®) + 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 *)®) + : ((uint8_t *)®) + 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; +} diff --git a/cpu/rpx0xx/pio/i2c/i2c.pio b/cpu/rpx0xx/pio/i2c/i2c.pio new file mode 100644 index 0000000000..dd286ca5ae --- /dev/null +++ b/cpu/rpx0xx/pio/i2c/i2c.pio @@ -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); +} + +%} diff --git a/cpu/rpx0xx/pio/i2c/i2c_set_scl_sda.pio b/cpu/rpx0xx/pio/i2c/i2c_set_scl_sda.pio new file mode 100644 index 0000000000..b7e0f11ecc --- /dev/null +++ b/cpu/rpx0xx/pio/i2c/i2c_set_scl_sda.pio @@ -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 +}; +%} diff --git a/drivers/include/periph/pio/i2c.h b/drivers/include/periph/pio/i2c.h new file mode 100644 index 0000000000..c86b270195 --- /dev/null +++ b/drivers/include/periph/pio/i2c.h @@ -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 + */ + +#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 */ +/** @} */ diff --git a/drivers/periph_common/init.c b/drivers/periph_common/init.c index d1f9e62240..c8b4ea57b6 100644 --- a/drivers/periph_common/init.c +++ b/drivers/periph_common/init.c @@ -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 diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 68f4585207..80736af39b 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -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