/* mbed Microcontroller Library * Copyright (c) 2018 GigaDevice Semiconductor Inc. * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mbed_assert.h" #include "i2c_api.h" #if DEVICE_I2C #include "cmsis.h" #include "pinmap.h" #include "PeripheralPins.h" #if DEVICE_I2C_ASYNCH #define I2C_S(obj) (struct i2c_s *) (&((obj)->i2c)) #else #define I2C_S(obj) (struct i2c_s *) (obj) #endif #define BUSY_TIMEOUT ((SystemCoreClock / obj_s->freq) * 2 * 10) #define FLAG_TIMEOUT (0x1000U) /** Reset I2C peripheral by hardware method. Most of the implementation enable RCU reset. * * @param obj The I2C object */ static void i2c_hw_reset(i2c_t *obj) { struct i2c_s *obj_s = I2C_S(obj); switch (obj_s->i2c) { case I2C_0: rcu_periph_reset_enable(RCU_I2C0RST); rcu_periph_reset_disable(RCU_I2C0RST); break; case I2C_1: rcu_periph_reset_enable(RCU_I2C1RST); rcu_periph_reset_disable(RCU_I2C1RST); break; } } /** Initialize the I2C peripheral. It sets the default parameters for I2C * peripheral, and configures its specifieds pins. * * @param obj The I2C object * @param sda The sda pin * @param scl The scl pin */ void i2c_init(i2c_t *obj, PinName sda, PinName scl) { struct i2c_s *obj_s = I2C_S(obj); /* find the I2C by pins */ uint32_t i2c_sda = pinmap_peripheral(sda, PinMap_I2C_SDA); uint32_t i2c_scl = pinmap_peripheral(scl, PinMap_I2C_SCL); obj_s->sda = sda; obj_s->scl = scl; obj_s->i2c = (I2CName)pinmap_merge(i2c_sda, i2c_scl); MBED_ASSERT(obj_s->i2c != (I2CName)NC); switch (obj_s->i2c) { case I2C_0: /* enable I2C0 clock and configure the pins of I2C0 */ obj_s->index = 0; rcu_periph_clock_enable(RCU_I2C0); break; case I2C_1: /* enable I2C1 clock and configure the pins of I2C1 */ obj_s->index = 1; rcu_periph_clock_enable(RCU_I2C1); break; default: break; } /* configure the pins of I2C */ pinmap_pinout(sda, PinMap_I2C_SDA); pinmap_pinout(scl, PinMap_I2C_SCL); /* 100 KHz as the default I2C frequence */ i2c_frequency(obj, 100000); obj_s->state = (operation_state_enum)I2C_STATE_NONE; obj_s->previous_state_mode = I2C_STATE_NONE; obj_s->global_trans_option = I2C_FIRST_AND_LAST_FRAME; #if DEVICE_I2CSLAVE /* I2C master by default */ obj_s->slave = 0; #endif } /** Configure the I2C frequency * * @param obj The I2C object * @param hz Frequency in Hz */ void i2c_frequency(i2c_t *obj, int hz) { int timeout; struct i2c_s *obj_s = I2C_S(obj); /* wait until I2C_FLAG_I2CBSY flag is reset */ timeout = BUSY_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_I2CBSY)) && (--timeout != 0)); /* reset to clear pending flags */ i2c_hw_reset(obj); /* disable I2C peripheral */ i2c_disable(obj_s->i2c); /* configure I2C frequence */ i2c_clock_config(obj_s->i2c, hz, I2C_DTCY_2); /* configure I2C address mode and slave address */ i2c_mode_addr_config(obj_s->i2c, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0); /* enable I2C peripheral */ i2c_enable(obj_s->i2c); } /** Reset I2C peripheral. TODO: The action here. Most of the implementation sends stop() * * @param obj The I2C object */ void i2c_reset(i2c_t *obj) { i2c_stop(obj); } /** Send START command * * @param obj The I2C object */ int i2c_start(i2c_t *obj) { int timeout; struct i2c_s *obj_s = I2C_S(obj); /* clear I2C_FLAG_AERR Flag */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_AERR); /* wait until I2C_FLAG_I2CBSY flag is reset */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_I2CBSY)) == SET) { if ((timeout--) == 0) { return (int)GD_BUSY; } } /* ensure the i2c has been stopped */ timeout = FLAG_TIMEOUT; while ((I2C_CTL0(obj_s->i2c) & I2C_CTL0_STOP) == I2C_CTL0_STOP) { if ((timeout--) == 0) { return (int)GD_ERROR; } } /* generate a START condition */ i2c_start_on_bus(obj_s->i2c); /* ensure the i2c has been started successfully */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_SBSEND)) == RESET) { if ((timeout--) == 0) { return (int)GD_ERROR; } } return (int)GD_OK; } /** Send STOP command * * @param obj The I2C object */ int i2c_stop(i2c_t *obj) { struct i2c_s *obj_s = I2C_S(obj); /* generate a STOP condition */ i2c_stop_on_bus(obj_s->i2c); /* wait for STOP bit reset */ while ((I2C_CTL0(obj_s->i2c) & I2C_CTL0_STOP)); return 0; } /** Read one byte * * @param obj The I2C object * @param last Acknoledge * @return The read byte */ int i2c_byte_read(i2c_t *obj, int last) { int timeout; struct i2c_s *obj_s = I2C_S(obj); if (last) { /* disable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); } else { /* enable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); } /* wait until the byte is received */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_RBNE)) == RESET) { if ((timeout--) == 0) { return -1; } } return (int)I2C_DATA(obj_s->i2c); } /** Write one byte * * @param obj The I2C object * @param data Byte to be written * @return 0 if NAK was received, 1 if ACK was received, 2 for timeout. */ int i2c_byte_write(i2c_t *obj, int data) { int timeout; struct i2c_s *obj_s = I2C_S(obj); I2C_DATA(obj_s->i2c) = (uint8_t)data; /* wait until the byte is transmitted */ timeout = FLAG_TIMEOUT; while (((i2c_flag_get(obj_s->i2c, I2C_FLAG_TBE)) == RESET) && ((i2c_flag_get(obj_s->i2c, I2C_FLAG_BTC)) == RESET)) { if ((timeout--) == 0) { return 2; } } return 1; } /** Blocking reading data * * @param obj The I2C object * @param address 7-bit address (last bit is 1) * @param data The buffer for receiving * @param length Number of bytes to read * @param stop Stop to be generated after the transfer is done * @return Number of read bytes */ int i2c_read(i2c_t *obj, int address, char *data, int length, int stop) { struct i2c_s *obj_s = I2C_S(obj); uint32_t count = 0U; int timeout = 0; if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_LAST_FRAME) { if (stop) { obj_s->global_trans_option = I2C_FIRST_AND_LAST_FRAME; } else { obj_s->global_trans_option = I2C_FIRST_FRAME; } } else if (obj_s->global_trans_option == I2C_FIRST_FRAME || obj_s->global_trans_option == I2C_NEXT_FRAME) { if (stop) { obj_s->global_trans_option = I2C_LAST_FRAME; } else { obj_s->global_trans_option = I2C_NEXT_FRAME; } } if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_FIRST_FRAME) { /* wait until I2C_FLAG_I2CBSY flag is reset */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_I2CBSY)) == SET) { if ((timeout--) == 0) { i2c_stop(obj); return I2C_ERROR_BUS_BUSY; } } } if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_FIRST_FRAME || obj_s->previous_state_mode != I2C_STATE_MASTER_BUSY_RX) { /* generate a START condition */ i2c_start_on_bus(obj_s->i2c); /* ensure the i2c has been started successfully */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_SBSEND)) == RESET) { if ((timeout--) == 0) { i2c_stop(obj); return I2C_ERROR_BUS_BUSY; } } /* send slave address */ i2c_master_addressing(obj_s->i2c, address, I2C_RECEIVER); if (1 == length) { /* disable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); /* send a stop condition to I2C bus*/ } else if (2 == length) { /* send a NACK for the next data byte which will be received into the shift register */ i2c_ackpos_config(obj_s->i2c, I2C_ACKPOS_NEXT); /* disable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); } else { /* enable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); } /* wait until I2C_FLAG_ADDSEND flag is set */ while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_ADDSEND)) { timeout++; if (timeout > 100000) { i2c_stop(obj); return I2C_ERROR_NO_SLAVE; } } /* clear ADDSEND */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_ADDSEND); } obj_s->state = (operation_state_enum)I2C_STATE_MASTER_BUSY_RX; for (count = 0; count < length; count++) { if (length > 2 && count == length - 3) { while (RESET == i2c_flag_get(obj_s->i2c, I2C_FLAG_BTC)); i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); } else if (2 == length && count == 0) { while (RESET == i2c_flag_get(obj_s->i2c, I2C_FLAG_BTC)); } while (RESET == i2c_flag_get(obj_s->i2c, I2C_FLAG_RBNE)); data[count] = i2c_data_receive(obj_s->i2c); } obj_s->previous_state_mode = obj_s->state; /* if not sequential read, then send stop */ if (stop) { i2c_stop(obj); } return count; } /** Blocking sending data * * @param obj The I2C object * @param address 7-bit address (last bit is 0) * @param data The buffer for sending * @param length Number of bytes to write * @param stop Stop to be generated after the transfer is done * @return * zero or non-zero - Number of written bytes * negative - I2C_ERROR_XXX status */ int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop) { struct i2c_s *obj_s = I2C_S(obj); gd_status_enum status = GD_OK; uint32_t count = 0; int timeout = 0; if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_LAST_FRAME) { if (stop) { obj_s->global_trans_option = I2C_FIRST_AND_LAST_FRAME; } else { obj_s->global_trans_option = I2C_FIRST_FRAME; } } else if (obj_s->global_trans_option == I2C_FIRST_FRAME || obj_s->global_trans_option == I2C_NEXT_FRAME) { if (stop) { obj_s->global_trans_option = I2C_LAST_FRAME; } else { obj_s->global_trans_option = I2C_NEXT_FRAME; } } if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_FIRST_FRAME) { /* wait until I2C_FLAG_I2CBSY flag is reset */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_I2CBSY)) == SET) { if ((timeout--) == 0) { i2c_stop(obj); return I2C_ERROR_BUS_BUSY; } } } if (obj_s->global_trans_option == I2C_FIRST_AND_LAST_FRAME || obj_s->global_trans_option == I2C_FIRST_FRAME || obj_s->previous_state_mode != I2C_STATE_MASTER_BUSY_TX) { /* generate a START condition */ i2c_start_on_bus(obj_s->i2c); /* ensure the i2c has been started successfully */ timeout = FLAG_TIMEOUT; while ((i2c_flag_get(obj_s->i2c, I2C_FLAG_SBSEND)) == RESET) { if ((timeout--) == 0) { i2c_stop(obj); return I2C_ERROR_BUS_BUSY; } } /* send slave address */ i2c_master_addressing(obj_s->i2c, address, I2C_TRANSMITTER); /* wait until I2C_FLAG_ADDSEND flag is set */ while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_ADDSEND)) { timeout++; if (timeout > 100000) { i2c_stop(obj); return I2C_ERROR_NO_SLAVE; } } /* clear ADDSEND */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_ADDSEND); } obj_s->state = (operation_state_enum)I2C_STATE_MASTER_BUSY_TX; for (count = 0; count < length; count++) { status = (gd_status_enum)i2c_byte_write(obj, data[count]); if (status != 1) { i2c_stop(obj); return count; } } obj_s->previous_state_mode = obj_s->state; /* if not sequential write, then send stop */ if (stop) { i2c_stop(obj); } return count; } #if DEVICE_I2CSLAVE /** Configure I2C address. * @param obj The I2C object * @param idx Currently not used * @param address The address to be set * @param mask Currently not used */ void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) { struct i2c_s *obj_s = I2C_S(obj); /* disable I2C peripheral */ i2c_disable(obj_s->i2c); /* I2C clock configure */ i2c_clock_config(obj_s->i2c, 100000, I2C_DTCY_2); /* I2C address configure */ i2c_mode_addr_config(obj_s->i2c, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, address); /* enable I2C0 */ i2c_enable(obj_s->i2c); /* enable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); } /** Configure I2C as slave or master. * @param obj The I2C object * @param enable_slave Enable i2c hardware so you can receive events with ::i2c_slave_receive * @return non-zero if a value is available */ void i2c_slave_mode(i2c_t *obj, int enable_slave) { struct i2c_s *obj_s = I2C_S(obj); if (enable_slave) { obj_s->slave = 1; } else { obj_s->slave = 0; } } /* the same as the definition in I2CSlave.h class I2CSlave */ #define NoData 0 /* the slave has not been addressed */ #define ReadAddressed 1 /* the master has requested a read from this slave (slave as transmitter) */ #define WriteGeneral 2 /* the master is writing to all slave */ #define WriteAddressed 3 /* the master is writing to this slave (slave as receiver) */ /** Check to see if the I2C slave has been addressed. * @param obj The I2C object * @return The status - 1 - read addresses, 2 - write to all slaves, * 3 write addressed, 0 - the slave has not been addressed */ int i2c_slave_receive(i2c_t *obj) { struct i2c_s *obj_s = I2C_S(obj); int ret = NoData; i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); if (i2c_flag_get(obj_s->i2c, I2C_FLAG_ADDSEND)) { i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); if (i2c_flag_get(obj_s->i2c, I2C_FLAG_RXGC)) { ret = WriteGeneral; } if (i2c_flag_get(obj_s->i2c, I2C_FLAG_TRS)) { ret = ReadAddressed; } else { ret = WriteAddressed; } } i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); return (ret); } /** Configure I2C as slave or master. * @param obj The I2C object * @param data The buffer for receiving * @param length Number of bytes to read * @return non-zero if a value is available */ int i2c_slave_read(i2c_t *obj, char *data, int length) { struct i2c_s *obj_s = I2C_S(obj); int count = 0; int timeout = 0; i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); /* wait until ADDSEND bit is set */ while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_ADDSEND)) { timeout++; if (timeout > 100000) { return -1; } } /* clear ADDSEND bit */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_ADDSEND); while (0 < length) { /* wait until the RBNE bit is set */ timeout = 0; while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_RBNE)) { timeout++; if (timeout > 100000) { return -1; } } *data = i2c_data_receive(obj_s->i2c); data++; length--; count++; } /* wait until the STPDET bit is set */ timeout = 0; while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_STPDET)) { timeout++; if (timeout > 100) { return count; } } /* clear the STPDET bit */ i2c_enable(obj_s->i2c); i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); return count; } /** Configure I2C as slave or master. * @param obj The I2C object * @param data The buffer for sending * @param length Number of bytes to write * @return non-zero if a value is available */ int i2c_slave_write(i2c_t *obj, const char *data, int length) { struct i2c_s *obj_s = I2C_S(obj); int count = 0; int timeout = 0; i2c_ack_config(obj_s->i2c, I2C_ACK_ENABLE); /* wait until ADDSEND bit is set */ while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_ADDSEND)) { timeout++; if (timeout > 100000) { return -1; } } /* clear ADDSEND bit */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_ADDSEND); while (length > 0) { /* wait until the TBE bit is set */ timeout = 0; while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_TBE)) { timeout++; if (timeout > 100000) { return -1; } } i2c_data_transmit(obj_s->i2c, *data); data++; length--; count++; } /* the master doesn't acknowledge for the last byte */ timeout = 0; while (!i2c_flag_get(obj_s->i2c, I2C_FLAG_AERR)) { timeout++; if (timeout > 100000) { return -1; } } /* clear the bit of AERR */ i2c_flag_clear(obj_s->i2c, I2C_FLAG_AERR); /* disable acknowledge */ i2c_ack_config(obj_s->i2c, I2C_ACK_DISABLE); return count; } #endif /* DEVICE_I2CSLAVE */ #endif /* DEVICE_I2C */