/* * Copyright (C) 2013 Uwe Bonnes(bon@elektron.ikp.physik.tu-darmstadt.de * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /*! * \file arch/cm3/dev/stm/stm32_i2cbus_v2.c * \brief I2C bus driver for I2C hardware in STM32F0 and STM32F3. * * This driver is in an early stage and has been tested on STM32F3 only. * It doesn't consider Slave operation yet * * It is intended that this driver replaces the current STM TWI driver, * which doesn't allow to have different types of busses in a single * application, for example TWI hardware and bit banging interfaces. * This new I2C driver layout allows to attach any I2C slave driver to * any I2C bus driver by calling NutRegisterI2cSlave(). * * \verbatim * $Id$ * \endverbatim */ /*! * \brief I2C bus driver for STM32F0/3 hardware. * * This is an interrupt driver, which supports master mode only. * No support for FM+ * No error handling */ #include #include #include #include #include #include #include #include #include #include #include #include /*! * \addtogroup xgI2cBusSTM32 */ /*@{*/ /*! * \brief Local data of the STM32 I2C bus driver. */ typedef struct _STM32_I2CCB { /*! \brief Register base. */ uptr_t icb_base; /*! \brief SDA_PIN. */ uint32_t sda_pin; /*! \brief SCL_PIN. */ uint32_t scl_pin; /*! \brief SMBA_PIN. */ uint32_t smba_pin; /*! \brief System event handler. */ IRQ_HANDLER *icb_sig_ev; /*! \brief System error handler. */ IRQ_HANDLER *icb_sig_er; /*! \brief I2C message. */ NUTI2C_MSG *icb_msg; /*! \brief Thread waiting for completion. */ HANDLE icb_queue; uint32_t errors; } STM32_I2CCB; /* * STM32V2 I2C Event interrupt function. */ static void I2cEventBusIrqHandler(void *arg) { STM32_I2CCB *icb = (STM32_I2CCB *) arg; NUTI2C_MSG *msg = icb->icb_msg; I2C_TypeDef *i2c; uint32_t cr2; i2c = (I2C_TypeDef *) icb->icb_base; cr2 = i2c->CR2; /* TX parts*/ if ((i2c->ISR & I2C_ISR_TCR) && !(cr2 & I2C_CR2_RD_WRN)) { uint32_t txbytes_left = msg->msg_wlen - msg->msg_widx; if (txbytes_left <= 0xff) { cr2 &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD); cr2 |= txbytes_left << 16; } /* else I2C_CR2_NBYTES and I2C_CR2_RELOAD already set*/ i2c->CR2 = cr2; /* Write to NBYTES clears TCR*/ } if ((i2c->ISR & I2C_ISR_TC) && !(cr2 & I2C_CR2_RD_WRN)) { i2c->CR1 &= ~I2C_CR1_TXIE; cr2 &= ~I2C_CR2_NBYTES; if (msg->msg_rsiz == 0) /* No read transaction, stop after write*/ { i2c->CR2 = cr2 | I2C_CR2_STOP ; } else { i2c->CR1 |= I2C_CR1_RXIE; cr2 |= I2C_CR2_RD_WRN | msg->msg_rsiz << 16; i2c->CR2 = cr2 | I2C_CR2_START ; } } if ((i2c->ISR & I2C_ISR_TXIS) && !(cr2 & I2C_CR2_RD_WRN)) { i2c->TXDR = msg->msg_wdat[msg->msg_widx]; msg->msg_widx++; } /* RX parts*/ if ((i2c->ISR & I2C_ISR_TCR) && (cr2 & I2C_CR2_RD_WRN)) { uint32_t rxbytes_left = msg->msg_rsiz - msg->msg_ridx; if (rxbytes_left <= 0xff) { cr2 &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD); cr2 |= rxbytes_left << 16; } /* else I2C_CR2_NBYTES and I2C_CR2_RELOAD already set*/ i2c->CR2 = cr2; /* Write to NBYTES clears TCR*/ } if ((i2c->ISR & I2C_ISR_TC) && (cr2 & I2C_CR2_RD_WRN)) { i2c->CR1 &= ~(I2C_CR1_TCIE | I2C_CR1_RXIE); i2c->CR2 = cr2 | I2C_CR2_STOP ; } if ((i2c->ISR & I2C_ISR_RXNE) && (cr2 & I2C_CR2_RD_WRN)) { msg->msg_rdat[msg->msg_ridx] = i2c->RXDR; msg->msg_ridx++; if (msg->msg_ridx == msg->msg_rsiz) /* No more RX Interrupts*/ i2c->CR1 &= ~I2C_CR1_RXIE; } if (i2c->ISR & I2C_ISR_STOPF) { i2c->CR1 &= ~I2C_CR1_STOPIE; i2c->ICR |= I2C_ICR_STOPCF; NutEventPostFromIrq(&icb->icb_queue); } if (i2c->ISR & I2C_ISR_NACKF) { /* Save error, but only exit through STOPF */ icb->errors |= I2C_ISR_NACKF; i2c->CR1 &= ~I2C_CR1_NACKIE; i2c->ICR |= I2C_ICR_NACKCF; } } /* * STM32V2 I2C Error interrupt function. */ static void I2cErrorBusIrqHandler(void *arg) { STM32_I2CCB *icb = (STM32_I2CCB *) arg; I2C_TypeDef *i2c; uint32_t isr; /* Fixme: More error handling! */ i2c = (I2C_TypeDef *) icb->icb_base; isr = i2c->ISR; if (isr & I2C_ISR_ALERT) i2c->ICR |= I2C_ICR_ALERTCF; if (isr & I2C_ISR_PECERR) i2c->ICR |= I2C_ICR_PECCF; if (isr & I2C_ISR_OVR) i2c->ICR |= I2C_ICR_OVRCF; if (isr & I2C_ISR_ARLO) i2c->ICR |= I2C_ICR_ARLOCF; if (isr & I2C_ISR_BERR) i2c->ICR |= I2C_ICR_BERRCF; if (isr & I2C_ISR_TIMEOUT) i2c->ICR |= I2C_ICR_TIMOUTCF; } /*! * \brief I2C bus transfer (STM I2C implementation). * * This function is called by the platform independent code via the * NUTI2C_BUS::bus_tran function pointer. */ static int I2cBusTran(NUTI2C_SLAVE *slave, NUTI2C_MSG *msg) { NUTI2C_BUS *bus; STM32_I2CCB *icb; I2C_TypeDef *i2c; uint32_t cr2; int rc = 0; bus = slave->slave_bus; NUTASSERT(bus != NULL); NUTASSERT(bus->bus_icb != NULL); icb = (STM32_I2CCB *) bus->bus_icb; icb->icb_msg = msg; icb->errors = 0; i2c = (I2C_TypeDef *) icb->icb_base; cr2 = i2c->CR2; cr2 &= 0xf8000000; /* Clean out */ cr2 |= slave->slave_address << 1; msg->msg_widx = 0; msg->msg_ridx = 0; /* are there bytes to write? */ if (msg->msg_wlen) { i2c->CR1 |= I2C_CR1_TXIE | I2C_CR1_TCIE | I2C_CR1_STOPIE | I2C_CR1_NACKIE; if (msg->msg_wlen > 0xff) cr2 |= I2C_CR2_NBYTES | I2C_CR2_RELOAD; else cr2 |= msg->msg_wlen << 16; } else if (msg->msg_rsiz) { i2c->CR1 |= I2C_CR1_RXIE | I2C_CR1_STOPIE | I2C_CR1_NACKIE; if (msg->msg_rsiz > 0xff) cr2 |= I2C_CR2_RD_WRN | I2C_CR2_NBYTES | I2C_CR2_RELOAD; else cr2 |= I2C_CR2_RD_WRN | msg->msg_rsiz << 16; } i2c->CR2 = cr2 | I2C_CR2_START; rc = NutEventWait(&icb->icb_queue, slave->slave_timeout); if ((icb->errors) || (rc)) msg->msg_ridx = -1; return msg->msg_ridx; } static int checkpin_and_config(STM32_I2CCB *icb) { uint32_t sda_port, scl_port; if (icb->icb_base == I2C1_BASE) { if ((icb->sda_pin != 7) && (icb->sda_pin != 9) && (icb->sda_pin != 14)) return -1; if ((icb->scl_pin != 6) && (icb->scl_pin != 8) && (icb->sda_pin != 15)) return -1; if ((icb->smba_pin != 5) && (icb->smba_pin != -1)) return -1; if (icb->sda_pin == 15) sda_port= NUTGPIO_PORTA; else sda_port= NUTGPIO_PORTB; if (icb->scl_pin == 15) scl_port= NUTGPIO_PORTA; else scl_port= NUTGPIO_PORTB; GpioPinConfigSet( sda_port, icb->sda_pin, GPIO_CFG_OUTPUT| GPIO_CFG_PERIPHAL| GPIO_CFG_MULTIDRIVE| GPIO_CFG_PULLUP | GPIO_CFG_SPEED_FAST); GpioPinConfigSet( scl_port ,icb->scl_pin, GPIO_CFG_OUTPUT| GPIO_CFG_PERIPHAL| GPIO_CFG_MULTIDRIVE| GPIO_CFG_PULLUP | GPIO_CFG_SPEED_FAST); GPIO_PinAFConfig((GPIO_TypeDef*) sda_port, icb->sda_pin, GPIO_AF_4); GPIO_PinAFConfig((GPIO_TypeDef*) scl_port, icb->scl_pin, GPIO_AF_4); if (icb->smba_pin == 5) { /* TODO: How should SMBA pin be set?*/ GpioPinConfigSet( NUTGPIO_PORTA, icb->scl_pin, GPIO_CFG_OUTPUT| GPIO_CFG_PERIPHAL| GPIO_CFG_MULTIDRIVE| GPIO_CFG_PULLUP | GPIO_CFG_SPEED_FAST); GPIO_PinAFConfig(GPIOA, icb->smba_pin, GPIO_AF_4); } RCC->APB1ENR &= ~RCC_APB1ENR_I2C1EN; /* Use HSI clock*/ RCC->CFGR3 &= ~RCC_CFGR3_I2C1SW; RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST; } else if (icb->icb_base == I2C2_BASE) { if ((icb->sda_pin != 0) && (icb->sda_pin != 10)&& (icb->sda_pin != 0)) return -1; if ((icb->scl_pin != 1) && (icb->scl_pin != 9) && (icb->sda_pin != 6) && (icb->sda_pin != 1)) return -1; if ((icb->smba_pin != 12) && (icb->smba_pin != 8) && (icb->smba_pin != -1)) return -1; if (icb->sda_pin == 10) sda_port= NUTGPIO_PORTA; else sda_port= NUTGPIO_PORTF; if (icb->scl_pin == 9) scl_port= NUTGPIO_PORTA; else /* Both other alternate pins on PORTF*/ scl_port= NUTGPIO_PORTF; /* Fixme: Handle SMNa Pin*/ GpioPinConfigSet( sda_port, icb->sda_pin, GPIO_CFG_OUTPUT| GPIO_CFG_PERIPHAL| GPIO_CFG_MULTIDRIVE| GPIO_CFG_PULLUP | GPIO_CFG_SPEED_FAST); GpioPinConfigSet( scl_port ,icb->scl_pin, GPIO_CFG_OUTPUT| GPIO_CFG_PERIPHAL| GPIO_CFG_MULTIDRIVE| GPIO_CFG_PULLUP | GPIO_CFG_SPEED_FAST); GPIO_PinAFConfig((GPIO_TypeDef*) sda_port, icb->sda_pin, GPIO_AF_4); GPIO_PinAFConfig((GPIO_TypeDef*) scl_port, icb->scl_pin, GPIO_AF_4); RCC->APB1ENR &= ~RCC_APB1ENR_I2C2EN; /* Use HSI clock*/ RCC->CFGR3 &= ~RCC_CFGR3_I2C2SW; RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; RCC->APB1RSTR |= RCC_APB1RSTR_I2C2RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C2RST; } else return -1; return 0; } /*! * \brief Configure the I2C bus controller (STM32V2 I2C implementation). * * This function is called by the platform independent code via the * NUTI2C_BUS::bus_conf function pointer. Most implementations will * also call this function during initialization to set the * default configuration. * * Right now only the bus clock rate is configurable, * and only at 10/100/400 kHz */ static int I2cBusConf(NUTI2C_BUS *bus) { STM32_I2CCB *icb; long rate; uint32_t timing; I2C_TypeDef *i2c; /* Check parameters. */ NUTASSERT(bus != NULL); NUTASSERT(bus->bus_icb != NULL); icb = (STM32_I2CCB *) bus->bus_icb; i2c = (I2C_TypeDef*) icb->icb_base; /* Get requested rate or use the default. */ rate = bus->bus_rate; if (rate == 0) { rate = 100000L; } if (rate > 400000) { /* Speed out of range */ return -1; } if(rate == 400000) timing = 0x0 <<28 |0x3 << 20 | 0x1 << 16| 0x03 << 8 | 0x09 << 0; else if (rate >= 100000) timing = 0x1 <<28 |0x4 << 20 | 0x2 << 16| 0x0f << 8 | 0x13 << 0; else if (rate >= 50000) timing = 0x1 <<28 |0x4 << 20 | 0x2 << 16| 0x23 << 8 | 0x27 << 0; else timing = 0x1 <<28 |0x4 << 20 | 0x2 << 16| 0xc3 << 8 | 0xc7 << 0; if ((i2c->TIMINGR & 0xf0ffffff) != timing) { /* Disable I2C and set new timing */ while(i2c->CR1 & I2C_CR1_PE) i2c->CR1 &= ~I2C_CR1_PE; i2c->TIMINGR = timing; } return 0; } /*! * \brief Initialize the I2C bus controller (STM32 implementation). * * This function is called by the platform independent code via the * NUTI2C_BUS::bus_init function pointer when the first slave is * attached to this bus. Note, that NUTI2C_BUS::bus_rate must be zero * initially. Otherwise no call to this function will take place. * * This function must do all required initializations so that the * driver will be ready to process transfers via NUTI2C_BUS::bus_tran. * * This function must return 0 on success or -1 otherwise. */ static int I2cBusInit(NUTI2C_BUS *bus) { I2C_TypeDef *i2c; STM32_I2CCB *icb; icb = (STM32_I2CCB *) bus->bus_icb; if (checkpin_and_config(icb)) return -1; /* Try to configure the bus*/ if (I2cBusConf(bus)) { return -1; } i2c = (I2C_TypeDef*) icb->icb_base; i2c->CR1 |= I2C_CR1_PE; if (NutRegisterIrqHandler(icb->icb_sig_ev, I2cEventBusIrqHandler, icb)) return -1; if (NutRegisterIrqHandler(icb->icb_sig_er, I2cErrorBusIrqHandler, icb)) return -1; NutIrqEnable(icb->icb_sig_ev); NutIrqDisable(icb->icb_sig_er); return 0; } /*! * \brief Probe the I2C bus for a specified slave address * (STM32V2 implementation). * * This function is called by the platform independent code via the * NUTI2C_BUS::bus_probe function pointer. This may happen even if no * slave device had been attached to the bus and thus without any * previous call to NUTI2C_BUS::bus_init. However, in that case * NUTI2C_BUS::bus_configure will have been called. * * */ static int I2cBusProbe(NUTI2C_BUS *bus, int sla) { STM32_I2CCB *icb; I2C_TypeDef *i2c; int ret; if ((bus->bus_flags & I2C_BF_INITIALIZED) == 0) { int res; res = I2cBusInit(bus); if (res) return res; } icb = (STM32_I2CCB *) bus->bus_icb; i2c = (I2C_TypeDef*) icb->icb_base; icb->errors = 0; i2c->CR1 |= I2C_CR1_NACKIE |I2C_CR1_STOPIE; i2c->CR2 = I2C_CR2_AUTOEND | 0*I2C_CR2_NBYTES| I2C_CR2_STOP | I2C_CR2_START| 0*I2C_CR2_ADD10 | 0* I2C_CR2_RD_WRN |sla<<1; NutEventWait(&icb->icb_queue, 10); if (icb->errors & I2C_ISR_NACKF) ret = -1; else ret = 0; return ret; } /* F30: AF4 I2C1 SDA PA14 PB7 PB9 I2C1_SCL PA15 PB6 PB8 I2C1_SMBA PB5 I2C2_SDA PA10 PF0 I2C2_SCL PA9 PF1 PF6 I2C2_SMBA PA8 PB12 F0: AF1 I2C1 SDA PB7 PB9 I2C1_SCL PB6 PB8 I2C1_SMBA PB5 (AF3) I2C2_SDA PB11 I2C2_SCL PB10 I2C2_SMBA NA */ static STM32_I2CCB i2c1cb = { I2C1_BASE, /* Register Base */ #if defined (I2C1_SDA_PIN) I2C1_SDA_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif #if defined (I2C1_SCL_PIN) I2C1_SCL_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif #if defined (I2C1_SMBA_PIN) I2C1_SMBA_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif &sig_TWI1_EV, /* Event signal */ &sig_TWI1_ER, /* Error signal */ NULL, /* icb_msg */ NULL /* icb_queue */ }; static STM32_I2CCB i2c2cb = { I2C2_BASE, /* Register Base */ #if defined (I2C2_SDA_PIN) I2C2_SDA_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif #if defined (I2C2_SCL_PIN) I2C2_SCL_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif #if defined (I2C2_SMBA_PIN) I2C2_SMBA_PIN, /* SDA Pin number */ #else -1, /* SDA Pin number */ #endif &sig_TWI2_EV, /* Event signal */ &sig_TWI2_ER, /* Error signal */ NULL, /* icb_msg */ NULL /* icb_queue */ }; NUTI2C_BUS i2cBus1Stm32 = { &i2c1cb, /* bus_icb */ I2cBusInit, /* bus_init */ I2cBusConf, /* bus_configure */ I2cBusProbe,/* bus_probe */ I2cBusTran, /* bus_transceive */ 100, /* bus_timeout */ 0, /* bus_rate */ 0, /* bus_flags */ NULL /* bus_mutex */ }; NUTI2C_BUS i2cBus2Stm32 = { &i2c2cb, /* bus_icb */ I2cBusInit, /* bus_init */ I2cBusConf, /* bus_configure */ I2cBusProbe,/* bus_probe */ I2cBusTran, /* bus_transceive */ 100, /* bus_timeout */ 0, /* bus_rate */ 0, /* bus_flags */ NULL /* bus_mutex */ };