/******************************************************************************* * Copyright 2019-2021 Microchip FPGA Embedded Systems Solutions. * * SPDX-License-Identifier: MIT * * PolarFire SoC Microprocessor Subsystem(MSS) I2C bare metal software driver * implementation. * */ #include "mpfs_hal/mss_hal.h" #include "mss_i2c_regs.h" #include "mss_i2c.h" #ifdef __cplusplus extern "C" { #endif /*------------------------------------------------------------------------------ * I2C transaction direction. */ #define WRITE_DIR 0u #define READ_DIR 1u /* -- Transactions types -- */ #define NO_TRANSACTION 0u #define MASTER_WRITE_TRANSACTION 1u #define MASTER_READ_TRANSACTION 2u #define MASTER_RANDOM_READ_TRANSACTION 3u #define WRITE_SLAVE_TRANSACTION 4u #define READ_SLAVE_TRANSACTION 5u /* -- SMBUS h/w states -- */ /* -- Master states -- */ #define ST_BUS_ERROR 0x00u /* Bus error during MST or selected slave * modes */ #define ST_I2C_IDLE 0xF8u /* No activity and no interrupt either.*/ #define ST_START 0x08u /* start condition sent */ #define ST_RESTART 0x10u /* repeated start */ #define ST_SLAW_ACK 0x18u /* SLA+W sent, ack received */ #define ST_SLAW_NACK 0x20u /* SLA+W sent, nack received */ #define ST_TX_DATA_ACK 0x28u /* Data sent, ACK'ed */ #define ST_TX_DATA_NACK 0x30u /* Data sent, NACK'ed */ #define ST_LOST_ARB 0x38u /* Master lost arbitration */ #define ST_SLAR_ACK 0x40u /* SLA+R sent, ACK'ed */ #define ST_SLAR_NACK 0x48u /* SLA+R sent, NACK'ed */ #define ST_RX_DATA_ACK 0x50u /* Data received, ACK sent */ #define ST_RX_DATA_NACK 0x58u /* Data received, NACK sent */ #define ST_RESET_ACTIVATED 0xD0u /* Master reset is activated */ /* -- Slave states -- */ #define ST_SLAVE_SLAW 0x60u /* SLA+W received */ #define ST_SLAVE_SLAR_ACK 0xA8u /* SLA+R received, ACK returned */ #define ST_SLV_LA 0x68u /* Slave lost arbitration */ #define ST_GCA 0x70u /* GCA received */ #define ST_GCA_LA 0x78u /* GCA lost arbitration */ #define ST_RDATA 0x80u /* Data received */ #define ST_SLA_NACK 0x88u /* Slave addressed, NACK returned */ #define ST_GCA_ACK 0x90u /* Previously addresses with GCA, data * ACKed */ #define ST_GCA_NACK 0x98u /* GCA addressed, NACK returned */ #define ST_RSTOP 0xA0u /* Stop received */ #define ST_SLARW_LA 0xB0u /* Arbitration lost */ #define ST_RACK 0xB8u /* Byte sent, ACK received */ #define ST_SLAVE_RNACK 0xC0u /* Byte sent, NACK received */ #define ST_FINAL 0xC8u /* Final byte sent, ACK received */ #define ST_SLV_RST 0xD8u /* Slave reset state */ /* * Maximum address offset length in slave write-read transactions. * A maximum of two bytes will be interpreted as address offset within the slave * tx buffer. */ #define MAX_OFFSET_LENGTH 2u uint8_t g_i2c_axi_pos = 0x0u; /*------------------------------------------------------------------------------ * Local Function */ static void mss_i2c_isr( mss_i2c_instance_t * this_i2c ); static void enable_slave_if_required( mss_i2c_instance_t * this_i2c ); static void global_init( mss_i2c_instance_t * this_i2c ); /*------------------------------------------------------------------------------ * I2C instances *------------------------------------------------------------------------------ */ #define MSS_I2C0_LO_BASE (I2C_TypeDef*)0x2010A000u #define MSS_I2C1_LO_BASE (I2C_TypeDef*)0x2010B000u #define MSS_I2C0_HI_BASE (I2C_TypeDef*)0x2810A000u #define MSS_I2C1_HI_BASE (I2C_TypeDef*)0x2810B000u mss_i2c_instance_t g_mss_i2c0_lo; mss_i2c_instance_t g_mss_i2c1_lo; mss_i2c_instance_t g_mss_i2c0_hi; mss_i2c_instance_t g_mss_i2c1_hi; /*------------------------------------------------------------------------------ * MSS_I2C_init() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_init ( mss_i2c_instance_t * this_i2c, uint8_t ser_address, mss_i2c_clock_divider_t ser_clock_speed ) { uint32_t primask; uint_fast16_t clock_speed = (uint_fast16_t)ser_clock_speed; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); /* * Initialize all items of the this_i2c data structure to zero. This * initializes all state variables to their init value. It relies on * the fact that NO_TRANSACTION, I2C_SUCCESS and I2C_RELEASE_BUS all * have an actual value of zero. */ primask = disable_interrupts(); global_init(this_i2c); /* Update serial address of the device */ this_i2c->ser_address = (uint_fast8_t)ser_address << 1u; this_i2c->hw_reg->CTRL |= (uint8_t)((((clock_speed >> 2u) & 0x01u) << CR2) & CR2_MASK); this_i2c->hw_reg->CTRL |= (uint8_t)((((clock_speed >> 1u) & 0x01u) << CR1) & CR1_MASK); this_i2c->hw_reg->CTRL |= (uint8_t)(((clock_speed & (uint8_t)0x01u) << CR0) & CR0_MASK); this_i2c->hw_reg->ADDR = (uint8_t)this_i2c->ser_address; this_i2c->hw_reg->CTRL |= ENS1_MASK; /* Set enable bit */ this_i2c->transfer_completion_handler = NULL; restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS_I2C_write() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_write ( mss_i2c_instance_t * this_i2c, uint8_t serial_addr, const uint8_t * write_buffer, uint16_t write_size, uint8_t options ) { uint32_t primask; volatile uint8_t stat_ctrl; mss_i2c_status_t stat_slave = this_i2c->slave_status; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); /* Update the transaction only when there is no transaction going on I2C */ if (this_i2c->transaction == NO_TRANSACTION) { this_i2c->transaction = MASTER_WRITE_TRANSACTION; } /* Update the Pending transaction information so that transaction can * restarted */ this_i2c->pending_transaction = MASTER_WRITE_TRANSACTION ; /* Update target address */ this_i2c->target_addr = (uint_fast8_t)serial_addr << 1u; this_i2c->dir = WRITE_DIR; this_i2c->master_tx_buffer = write_buffer; this_i2c->master_tx_size = write_size; this_i2c->master_tx_idx = 0u; /* Set I2C status in progress */ this_i2c->master_status = MSS_I2C_IN_PROGRESS; this_i2c->options = options; if (MSS_I2C_IN_PROGRESS == stat_slave) { this_i2c->is_transaction_pending = 1u; } else { this_i2c->hw_reg->CTRL |= STA_MASK; } /* * Clear interrupts if required (depends on repeated starts). * Since the Bus is on hold, only then prior status needs to * be cleared. */ if (MSS_I2C_HOLD_BUS == this_i2c->bus_status) { this_i2c->hw_reg->CTRL &= ~SI_MASK; stat_ctrl = this_i2c->hw_reg->STATUS; } /* Enable the interrupt. (Re-enable) */ PLIC_EnableIRQ(this_i2c->irqn); restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS_I2C_read() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_read ( mss_i2c_instance_t * this_i2c, uint8_t serial_addr, uint8_t * read_buffer, uint16_t read_size, uint8_t options ) { uint32_t primask; volatile uint8_t stat_ctrl; mss_i2c_status_t stat_slave = this_i2c->slave_status; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); /* Update the transaction only when there is no transaction going on I2C */ if (this_i2c->transaction == NO_TRANSACTION) { this_i2c->transaction = MASTER_READ_TRANSACTION; } /* Update the Pending transaction information so that transaction can * restarted */ this_i2c->pending_transaction = MASTER_READ_TRANSACTION ; /* Update target address */ this_i2c->target_addr = (uint_fast8_t)serial_addr << 1u; this_i2c->dir = READ_DIR; this_i2c->master_rx_buffer = read_buffer; this_i2c->master_rx_size = read_size; this_i2c->master_rx_idx = 0u; /* Set I2C status in progress */ this_i2c->master_status = MSS_I2C_IN_PROGRESS; this_i2c->options = options; if (MSS_I2C_IN_PROGRESS == stat_slave) { this_i2c->is_transaction_pending = 1u; } else { this_i2c->hw_reg->CTRL |= STA_MASK; } /* * Clear interrupts if required (depends on repeated starts). * Since the Bus is on hold, only then prior status needs to * be cleared. */ if (MSS_I2C_HOLD_BUS == this_i2c->bus_status) { this_i2c->hw_reg->CTRL &= ~SI_MASK; stat_ctrl = this_i2c->hw_reg->STATUS; } /* Enable the interrupt. ( Re-enable) */ PLIC_EnableIRQ(this_i2c->irqn); restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS_I2C_write_read() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_write_read ( mss_i2c_instance_t * this_i2c, uint8_t serial_addr, const uint8_t * addr_offset, uint16_t offset_size, uint8_t * read_buffer, uint16_t read_size, uint8_t options ) { mss_i2c_status_t stat_slave = this_i2c->slave_status; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); ASSERT(offset_size > 0u); ASSERT(addr_offset != (const uint8_t *)0); ASSERT(read_size > 0u); ASSERT(read_buffer != (uint8_t *)0); if ((read_size > 0u) && (offset_size > 0u)) { uint32_t primask; volatile uint8_t stat_ctrl; primask = disable_interrupts(); /* Update the transaction only when there is no transaction going on * I2C. */ if (this_i2c->transaction == NO_TRANSACTION) { this_i2c->transaction = MASTER_RANDOM_READ_TRANSACTION; } /* Update the Pending transaction information so that transaction can * restarted. */ this_i2c->pending_transaction = MASTER_RANDOM_READ_TRANSACTION ; /* Update target address */ this_i2c->target_addr = (uint_fast8_t)serial_addr << 1u; this_i2c->dir = WRITE_DIR; this_i2c->master_tx_buffer = addr_offset; this_i2c->master_tx_size = offset_size; this_i2c->master_tx_idx = 0u; this_i2c->master_rx_buffer = read_buffer; this_i2c->master_rx_size = read_size; this_i2c->master_rx_idx = 0u; /* Set I2C status in progress */ this_i2c->master_status = MSS_I2C_IN_PROGRESS; this_i2c->options = options; if (MSS_I2C_IN_PROGRESS == stat_slave) { this_i2c->is_transaction_pending = 1u; } else { this_i2c->hw_reg->CTRL |= STA_MASK; } /* * Clear interrupts if required (depends on repeated starts). * Since the Bus is on hold, only then prior status needs to * be cleared. */ if (MSS_I2C_HOLD_BUS == this_i2c->bus_status) { this_i2c->hw_reg->CTRL &= ~SI_MASK; stat_ctrl = this_i2c->hw_reg->STATUS; } /* Enable the interrupt. ( Re-enable) */ PLIC_EnableIRQ(this_i2c->irqn); restore_interrupts(primask); } } /*------------------------------------------------------------------------------ * MSS_I2C_get_status() * See "mss_i2c.h" for details of how to use this function. */ mss_i2c_status_t MSS_I2C_get_status ( mss_i2c_instance_t * this_i2c ) { mss_i2c_status_t i2c_status; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); i2c_status = this_i2c->master_status; return i2c_status; } /*------------------------------------------------------------------------------ * MSS_I2C_wait_complete() * See "mss_i2c.h" for details of how to use this function. */ mss_i2c_status_t MSS_I2C_wait_complete ( mss_i2c_instance_t * this_i2c, uint32_t timeout_ms ) { mss_i2c_status_t i2c_status; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); this_i2c->master_timeout_ms = timeout_ms; /* Run the loop until state returns I2C_FAILED or I2C_SUCESS */ do { i2c_status = this_i2c->master_status; } while(MSS_I2C_IN_PROGRESS == i2c_status); return i2c_status; } /*------------------------------------------------------------------------------ * MSS_I2C_system_tick() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_system_tick ( mss_i2c_instance_t * this_i2c, uint32_t ms_since_last_tick ) { if (this_i2c->master_timeout_ms != MSS_I2C_NO_TIMEOUT) { if (this_i2c->master_timeout_ms > ms_since_last_tick) { this_i2c->master_timeout_ms -= ms_since_last_tick; } else { /* * Mark current transaction as having timed out. */ this_i2c->master_status = MSS_I2C_TIMED_OUT; this_i2c->transaction = NO_TRANSACTION; this_i2c->is_transaction_pending = (uint8_t)0; /* * Make sure we do not incorrectly signal a timeout for subsequent * transactions. */ this_i2c->master_timeout_ms = MSS_I2C_NO_TIMEOUT; } } } /*------------------------------------------------------------------------------ * MSS_I2C_set_slave_tx_buffer() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_slave_tx_buffer ( mss_i2c_instance_t * this_i2c, const uint8_t * tx_buffer, uint16_t tx_size ) { uint32_t primask; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); this_i2c->slave_tx_buffer = tx_buffer; this_i2c->slave_tx_size = tx_size; this_i2c->slave_tx_idx = 0u; restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS_I2C_set_slave_rx_buffer() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_slave_rx_buffer ( mss_i2c_instance_t * this_i2c, uint8_t * rx_buffer, uint16_t rx_size ) { uint32_t primask; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); this_i2c->slave_rx_buffer = rx_buffer; this_i2c->slave_rx_size = rx_size; this_i2c->slave_rx_idx = 0u; restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS_I2C_set_slave_mem_offset_length() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_slave_mem_offset_length ( mss_i2c_instance_t * this_i2c, uint8_t offset_length ) { ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); ASSERT(offset_length <= MAX_OFFSET_LENGTH); if (offset_length > MAX_OFFSET_LENGTH) { this_i2c->slave_mem_offset_length = MAX_OFFSET_LENGTH; } else { this_i2c->slave_mem_offset_length = offset_length; } } /*------------------------------------------------------------------------------ * MSS_I2C_register_transfer_completion_handler() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_register_transfer_completion_handler ( mss_i2c_instance_t * this_i2c, mss_i2c_transfer_completion_t completion_handler ) { ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); if (completion_handler == NULL) { this_i2c->transfer_completion_handler = 0u; } else { this_i2c->transfer_completion_handler = completion_handler; } } /*------------------------------------------------------------------------------ * MSS_I2C_register_write_handler() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_register_write_handler ( mss_i2c_instance_t * this_i2c, mss_i2c_slave_wr_handler_t handler ) { ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); this_i2c->slave_write_handler = handler; } /*------------------------------------------------------------------------------ * MSS_I2C_enable_slave() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_enable_slave ( mss_i2c_instance_t * this_i2c ) { uint32_t primask; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); /* Set the assert acknowledge bit. */ this_i2c->hw_reg->CTRL |= AA_MASK; /* Enable slave */ this_i2c->is_slave_enabled = 1u; restore_interrupts(primask); /* Enable Interrupt */ PLIC_EnableIRQ(this_i2c->irqn); } /*------------------------------------------------------------------------------ * MSS_I2C_disable_slave() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_disable_slave ( mss_i2c_instance_t * this_i2c ) { uint32_t primask; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); primask = disable_interrupts(); /* Reset the assert acknowledge bit. */ this_i2c->hw_reg->CTRL &= ~AA_MASK; /* Disable slave */ this_i2c->is_slave_enabled = 0u; restore_interrupts(primask); } /*------------------------------------------------------------------------------ * MSS Slave enable function */ static void enable_slave_if_required ( mss_i2c_instance_t * this_i2c ) { if(this_i2c->is_slave_enabled) { this_i2c->hw_reg->CTRL |= AA_MASK; } } /*------------------------------------------------------------------------------ * MSS I2C interrupt service routine. *------------------------------------------------------------------------------ * Parameters: * * mss_i2c_instance_t * this_i2c: * Pointer to the mss_i2c_instance_t data structure holding all data related to * the MSS I2C instance that generated the interrupt. */ static void mss_i2c_isr ( mss_i2c_instance_t * this_i2c ) { volatile uint8_t status; uint8_t data; uint8_t hold_bus; uint8_t clear_irq = 1u; ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); status = this_i2c->hw_reg->STATUS; switch( status ) { /*---------------------------------------------------------------------- * Master transmitter / Receiver */ case ST_START: /* Start has been xmt'd */ case ST_RESTART: /* Repeated start has been xmt'd */ this_i2c->hw_reg->CTRL &= ~STA_MASK; this_i2c->hw_reg->DATA = (uint8_t)(this_i2c->target_addr | (this_i2c->dir & DATA_DIR_MASK)); if (this_i2c->dir == WRITE_DIR) { this_i2c->master_tx_idx = 0u; } else if (this_i2c->dir == READ_DIR) { this_i2c->master_rx_idx = 0u; } else { ; /* To avoid LDRA warning. */ } /* * Clear the pending transaction. This condition will be true if the * slave has acquired the bus to carry out pending master * transaction which it had received during its slave transmission * or reception mode. */ if (this_i2c->is_transaction_pending) { this_i2c->is_transaction_pending = 0u; } /* * Make sure to update proper transaction after master START * or RESTART */ if (this_i2c->transaction != this_i2c->pending_transaction) { this_i2c->transaction = this_i2c->pending_transaction; } break; case ST_LOST_ARB: /* Set start bit. Let's keep trying! Don't give up! */ this_i2c->hw_reg->CTRL |= STA_MASK; break; /*---------------------------------------------------------------------- * Master transmitter */ case ST_SLAW_NACK: /* SLA+W has been transmitted; not ACK has been received - let's stop. */ this_i2c->hw_reg->CTRL |= STO_MASK; this_i2c->master_status = MSS_I2C_FAILED; this_i2c->transaction = NO_TRANSACTION; enable_slave_if_required(this_i2c); break; case ST_SLAW_ACK: case ST_TX_DATA_ACK: /* Data byte has been xmt'd with ACK, time to send stop bit or * repeated start. */ if (this_i2c->master_tx_idx < this_i2c->master_tx_size) { this_i2c->hw_reg->DATA = this_i2c->master_tx_buffer[this_i2c->master_tx_idx]; this_i2c->master_tx_idx++; } else if (this_i2c->transaction == MASTER_RANDOM_READ_TRANSACTION) { /* We are finished sending the address offset part of a random * read transaction. * It is is time to send a restart in order to change direction. */ this_i2c->dir = READ_DIR; this_i2c->hw_reg->CTRL |= STA_MASK; } else /* Done sending. let's stop */ { /* * Set the transaction back to NO_TRANSACTION to allow user to * do further transaction */ this_i2c->transaction = NO_TRANSACTION; hold_bus = this_i2c->options & MSS_I2C_HOLD_BUS; /* Store the information of current I2C bus status in the * bus_status. */ this_i2c->bus_status = hold_bus; if (hold_bus == 0u) { /* Transmit stop condition. */ this_i2c->hw_reg->CTRL |= STO_MASK; enable_slave_if_required(this_i2c); } else { PLIC_DisableIRQ(this_i2c->irqn); clear_irq = 0u; } this_i2c->master_status = MSS_I2C_SUCCESS; } break; case ST_TX_DATA_NACK: /* data byte SENT, ACK to be received * In fact, this means we've received a NACK (This may not be * obvious, but if we've rec'd an ACK then we would be in state * 0x28!) hence, let's send a stop bit */ this_i2c->hw_reg->CTRL |= STO_MASK; this_i2c->master_status = MSS_I2C_FAILED; /* * Set the transaction back to NO_TRANSACTION to allow user to do * further transaction. */ this_i2c->transaction = NO_TRANSACTION; enable_slave_if_required(this_i2c); break; /*---------------------------------------------------------------------- * Master (or slave?) receiver */ /* STATUS codes 08H, 10H, 38H are all covered in MTX mode */ case ST_SLAR_ACK: /* SLA+R tx'ed. */ /* Let's make sure we ACK the first data byte received (set AA bit * in CTRL) unless the next byte is the last byte of the read transaction. */ if (this_i2c->master_rx_size > 1u) { this_i2c->hw_reg->CTRL |= AA_MASK; } else if (1u == this_i2c->master_rx_size) { this_i2c->hw_reg->CTRL &= ~AA_MASK; } else /* this_i2c->master_rx_size == 0u */ { this_i2c->hw_reg->CTRL |= AA_MASK; this_i2c->hw_reg->CTRL |= STO_MASK; this_i2c->master_status = MSS_I2C_SUCCESS; this_i2c->transaction = NO_TRANSACTION; } break; case ST_SLAR_NACK: /* SLA+R tx'ed; let's release the bus (send a stop condition) */ this_i2c->hw_reg->CTRL |= STO_MASK; this_i2c->master_status = MSS_I2C_FAILED; /* * Set the transaction back to NO_TRANSACTION to allow user to do * further transaction. */ this_i2c->transaction = NO_TRANSACTION; enable_slave_if_required(this_i2c); break; case ST_RX_DATA_ACK: /* Data byte received, ACK returned */ /* First, get the data */ this_i2c->master_rx_buffer[this_i2c->master_rx_idx] = this_i2c->hw_reg->DATA; this_i2c->master_rx_idx++; if (this_i2c->master_rx_idx >= (this_i2c->master_rx_size - 1u)) { /* If we're at the second last byte, let's set AA to 0 so * we return a NACK at the last byte. */ this_i2c->hw_reg->CTRL &= ~AA_MASK; } break; case ST_RX_DATA_NACK: /* Data byte received, NACK returned */ /* Get the data, then send a stop condition */ this_i2c->master_rx_buffer[this_i2c->master_rx_idx] = this_i2c->hw_reg->DATA; hold_bus = this_i2c->options & MSS_I2C_HOLD_BUS; /* Store the information of current I2C bus status in the bus_status*/ this_i2c->bus_status = hold_bus; if (hold_bus == 0u) { this_i2c->hw_reg->CTRL |= STO_MASK; /*xmt stop condition */ /* Bus is released, now we can start listening to bus, if it is slave */ enable_slave_if_required(this_i2c); } else { PLIC_DisableIRQ(this_i2c->irqn); clear_irq = 0u; } /* * Set the transaction back to NO_TRANSACTION to allow user to do * further transaction. */ this_i2c->transaction = NO_TRANSACTION; this_i2c->master_status = MSS_I2C_SUCCESS; break; /*---------------------------------------------------------------------- * Slave receiver */ case ST_GCA_NACK: /* NACK after, GCA addressing */ case ST_SLA_NACK: /* Re-enable AA (assert ack) bit for * future transmissions */ this_i2c->hw_reg->CTRL |= AA_MASK; this_i2c->transaction = NO_TRANSACTION; this_i2c->slave_status = MSS_I2C_SUCCESS; /* Check if transaction was pending. If yes, set the START bit */ if (this_i2c->is_transaction_pending) { this_i2c->hw_reg->CTRL |= STA_MASK ; } break; case ST_GCA_LA: /* Arbitr. lost (GCA rec'd) */ case ST_SLV_LA: /* Arbitr. lost (SLA rec'd) */ /* * We lost arbitration and either the GCE or our address was the * one received so pend the master operation we were starting. */ this_i2c->is_transaction_pending = 1u; /* Fall through to normal ST processing as we are now in slave * mode */ case ST_GCA: /* General call address received, ACK returned */ case ST_SLAVE_SLAW: /* SLA+W received, ACK returned */ this_i2c->transaction = WRITE_SLAVE_TRANSACTION; this_i2c->slave_rx_idx = 0u; this_i2c->random_read_addr = 0u; /* If Start Bit is set, clear it, but store that information since * it is because of pending transaction */ if (this_i2c->hw_reg->CTRL & STA_MASK) { this_i2c->hw_reg->CTRL &= ~STA_MASK ; this_i2c->is_transaction_pending = 1u; } this_i2c->slave_status = MSS_I2C_IN_PROGRESS; #ifdef MSS_I2C_INCLUDE_SLA_IN_RX_PAYLOAD /* Fall through to put address as first byte in payload buffer */ #else /* Only break from this case if the slave address must NOT be * included at the beginning of the received write data. */ break; #endif case ST_GCA_ACK: /* DATA received; ACK sent after GCA */ case ST_RDATA: /* DATA received; must clear DATA register */ if ((this_i2c->slave_rx_buffer != (uint8_t *)0) && (this_i2c->slave_rx_idx < this_i2c->slave_rx_size)) { data = this_i2c->hw_reg->DATA; this_i2c->slave_rx_buffer[this_i2c->slave_rx_idx] = data; this_i2c->slave_rx_idx++; #ifdef MSS_I2C_INCLUDE_SLA_IN_RX_PAYLOAD if ((ST_RDATA == status) || (ST_GCA_ACK == status)) { /* Ignore the slave address byte in the random read address * computation in the case where INCLUDE_SLA_IN_RX_PAYLOAD * is defined. */ #endif this_i2c->random_read_addr = (this_i2c->random_read_addr << 8) + data; #ifdef MSS_I2C_INCLUDE_SLA_IN_RX_PAYLOAD } #endif } if (this_i2c->slave_rx_idx >= this_i2c->slave_rx_size) { /* Send a NACK when done (next reception) */ this_i2c->hw_reg->CTRL &= ~AA_MASK; } break; case ST_RSTOP: /* STOP or repeated START occurred. */ /* We cannot be sure if the transaction has actually completed as * this hardware state reports that either a STOP or repeated START * condition has occurred. We assume that this is a repeated START * if the transaction was a write from the master to this point.*/ if (this_i2c->transaction == WRITE_SLAVE_TRANSACTION) { if (this_i2c->slave_rx_idx == this_i2c->slave_mem_offset_length) { this_i2c->slave_tx_idx = this_i2c->random_read_addr; } { /* Call the slave's write transaction handler if it exists. */ if (this_i2c->slave_write_handler != (mss_i2c_slave_wr_handler_t)0u) { mss_i2c_slave_handler_ret_t h_ret; h_ret = this_i2c->slave_write_handler( this_i2c, this_i2c->slave_rx_buffer, (uint16_t)this_i2c->slave_rx_idx ); if (MSS_I2C_REENABLE_SLAVE_RX == h_ret) { /* There is a small risk that the write handler * could call MSS_I2C_disable_slave() but return * MSS_I2C_REENABLE_SLAVE_RX in error so we only * enable ACKs if still in slave mode. */ enable_slave_if_required(this_i2c); this_i2c->hw_reg->CTRL |= AA_MASK; } else { this_i2c->hw_reg->CTRL &= ~AA_MASK; /* Clear slave mode flag as well otherwise in mixed * master/slave applications, the AA bit will get * set by subsequent master operations. */ this_i2c->is_slave_enabled = 0u; } } else { /* Re-enable address acknowledge in case we were ready * to nack the next received byte. */ this_i2c->hw_reg->CTRL |= AA_MASK; } } } else /* A stop or repeated start outside a write/read operation */ { /* * Reset slave_tx_idx so that a subsequent read will result in * the slave's transmit buffer being sent from the first byte. */ this_i2c->slave_tx_idx = 0u; /* * See if we need to re-enable acknowledgment as some error * conditions, such as a master prematurely ending a transfer, * can see us get here with AA set to 0 which will disable * slave operation if we are not careful. */ enable_slave_if_required(this_i2c); } /* Mark any previous master write transaction as complete. */ this_i2c->slave_status = MSS_I2C_SUCCESS; /* Check if transaction was pending. If yes, set the START bit */ if (this_i2c->is_transaction_pending) { this_i2c->hw_reg->CTRL |= STA_MASK ; } /* * Set the transaction back to NO_TRANSACTION to allow user to do * further transaction. */ this_i2c->transaction = NO_TRANSACTION; break; case ST_SLV_RST: /* SMBUS ONLY: timeout state. must clear interrupt */ /* * Set the transaction back to NO_TRANSACTION to allow user to do * further transaction. */ this_i2c->transaction = NO_TRANSACTION; /* * Reset slave_tx_idx so that a subsequent read will result in the * slave's transmit buffer being sent from the first byte. */ this_i2c->slave_tx_idx = 0u; /* * Clear status to I2C_FAILED only if there was an operation in * progress. */ if (MSS_I2C_IN_PROGRESS == this_i2c->slave_status) { this_i2c->slave_status = MSS_I2C_FAILED; } /* Make sure AA is set correctly. */ enable_slave_if_required(this_i2c); break; /*---------------------------------------------------------------------- * Slave transmitter */ case ST_SLAVE_SLAR_ACK: /* SLA+R received, ACK returned */ case ST_SLARW_LA: /* Arbitration lost, SLA+R received, ACK returned */ case ST_RACK: /* Data tx'ed, ACK received */ if (status == ST_SLAVE_SLAR_ACK) { this_i2c->transaction = READ_SLAVE_TRANSACTION; this_i2c->random_read_addr = 0u; this_i2c->slave_status = MSS_I2C_IN_PROGRESS; /* If Start Bit is set, clear it, but store that information * since it is because of pending transaction. */ if ((this_i2c->hw_reg->CTRL & STA_MASK)) { this_i2c->hw_reg->CTRL &= ~STA_MASK ; this_i2c->is_transaction_pending = 1u; } } if (this_i2c->slave_tx_idx >= this_i2c->slave_tx_size) { /* Ensure 0xFF is returned to the master when the slave s * pecifies an empty transmit buffer. */ this_i2c->hw_reg->DATA = 0xFFu; } else { /* Load the data the data byte to be sent to the master. */ this_i2c->hw_reg->DATA = this_i2c->slave_tx_buffer[this_i2c->slave_tx_idx]; this_i2c->slave_tx_idx++; } /* Determine if this is the last data byte to send to the master. */ if (this_i2c->slave_tx_idx >= this_i2c->slave_tx_size) /* last byte? */ { this_i2c->hw_reg->CTRL &= ~AA_MASK; /* Next read transaction will result in slave's transmit buffer * being sent from the first byte. */ this_i2c->slave_tx_idx = 0u; } break; case ST_SLAVE_RNACK: /* Data byte has been transmitted; not-ACK has * been received. */ case ST_FINAL: /* Last Data byte tx'ed, ACK received */ /* We assume that the transaction will be stopped by the master. * Reset slave_tx_idx so that a subsequent read will result in the slave's * transmit buffer being sent from the first byte. */ this_i2c->slave_tx_idx = 0u; this_i2c->hw_reg->CTRL |= AA_MASK; /* Mark previous state as complete */ this_i2c->slave_status = MSS_I2C_SUCCESS; /* Check if transaction was pending. If yes, set the START bit */ if (this_i2c->is_transaction_pending) { this_i2c->hw_reg->CTRL |= STA_MASK ; } /* * Set the transaction back to NO_TRANSACTION to allow user to do further * transaction */ this_i2c->transaction = NO_TRANSACTION; break; /* Master Reset has been activated Wait 35 ms for interrupt to be set, * clear interrupt and proceed to 0xF8 state. */ case ST_RESET_ACTIVATED: case ST_BUS_ERROR: /* Bus error during MST or selected slave modes */ default: /* Some undefined state has encountered. Clear Start bit to make * sure, next good transaction happen */ this_i2c->hw_reg->CTRL &= ~STA_MASK; /* * Set the transaction back to NO_TRANSACTION to allow user to do further * transaction */ this_i2c->transaction = NO_TRANSACTION; /* * Reset slave_tx_idx so that a subsequent read will result in the slave's * transmit buffer being sent from the first byte. */ this_i2c->slave_tx_idx = 0u; /* * Clear statuses to I2C_FAILED only if there was an operation in progress. */ if (MSS_I2C_IN_PROGRESS == this_i2c->master_status) { this_i2c->master_status = MSS_I2C_FAILED; } if (MSS_I2C_IN_PROGRESS == this_i2c->slave_status) { this_i2c->slave_status = MSS_I2C_FAILED; } break; } if ((this_i2c->master_status != MSS_I2C_IN_PROGRESS) && (this_i2c->slave_status != MSS_I2C_IN_PROGRESS) && (this_i2c->transfer_completion_handler) && (this_i2c->is_slave_enabled == 0)) { if ((this_i2c->master_status != MSS_I2C_IN_PROGRESS)) { this_i2c->transfer_completion_handler(this_i2c, this_i2c->master_status); } else if ((this_i2c->slave_status != MSS_I2C_IN_PROGRESS)) { this_i2c->transfer_completion_handler(this_i2c, this_i2c->slave_status); } else { ; /* To remove LDRA warning. */ } } /* ST_BUS_ERROR */ if (clear_irq) { /* Clear interrupt. */ this_i2c->hw_reg->CTRL &= ~SI_MASK; } /* Read the status register to ensure the last I2C registers write took place * in a system built around a bus making use of posted writes. */ status = this_i2c->hw_reg->STATUS; } /*------------------------------------------------------------------------------ * MSS_I2C_smbus_init() * See "mss_i2c.h" for details of how to use this function. */ /* * SMBSUS_NO = 1 * SMBALERT_NO = 1 * SMBus enable = 1 */ #define MSS_INIT_AND_ENABLE_SMBUS 0x54u void MSS_I2C_smbus_init ( mss_i2c_instance_t * this_i2c, uint8_t frequency ) { /* Set the frequency before enabling time out logic. */ this_i2c->hw_reg->FREQ = frequency; /* Enable SMBUS. */ this_i2c->hw_reg->SMBUS = MSS_INIT_AND_ENABLE_SMBUS; } /*------------------------------------------------------------------------------ * MSS_I2C_enable_smbus_irq() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_enable_smbus_irq ( mss_i2c_instance_t * this_i2c, uint8_t irq_type ) { ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); /* Enable any interrupts selected by the user. */ if ((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi)) { if (irq_type & MSS_I2C_SMBALERT_IRQ) { this_i2c->hw_reg->SMBUS |= SMBALERT_IE_MASK; /* Enable the interrupt at the PLIC. */ PLIC_EnableIRQ(I2C0_ALERT_PLIC); } if (irq_type & MSS_I2C_SMBSUS_IRQ) { this_i2c->hw_reg->SMBUS |= SMBSUS_IE_MASK; /* Enable the interrupt at the PLIC. */ PLIC_EnableIRQ(I2C0_SUS_PLIC); } } else { if (irq_type & MSS_I2C_SMBALERT_IRQ) { this_i2c->hw_reg->SMBUS |= SMBALERT_IE_MASK; /* Enable the interrupt at the PLIC. */ PLIC_EnableIRQ(I2C1_ALERT_PLIC); } if (irq_type & MSS_I2C_SMBSUS_IRQ) { this_i2c->hw_reg->SMBUS |= SMBSUS_IE_MASK; /* Enable the interrupt at the PLIC. */ PLIC_EnableIRQ(I2C1_SUS_PLIC); } } } /*------------------------------------------------------------------------------ * MSS_I2C_disable_smbus_irq() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_disable_smbus_irq ( mss_i2c_instance_t * this_i2c, uint8_t irq_type ) { ASSERT((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi) || (this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)); /* Disable any interrupts selected by the user. */ if ((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi)) { if (irq_type & MSS_I2C_SMBALERT_IRQ) { /* Disable interrupt at the PLIC and the MSS I2C. */ this_i2c->hw_reg->SMBUS &= ~SMBALERT_IE_MASK; PLIC_DisableIRQ(I2C0_ALERT_PLIC); } if (irq_type & MSS_I2C_SMBSUS_IRQ) { /* Disable interrupt at the PLIC and the MSS I2C. */ this_i2c->hw_reg->SMBUS &= ~SMBSUS_IE_MASK; PLIC_DisableIRQ(I2C0_SUS_PLIC); } } else { if (irq_type & MSS_I2C_SMBALERT_IRQ) { /* Disable interrupt at the PLIC and the MSS I2C. */ this_i2c->hw_reg->SMBUS &= ~SMBALERT_IE_MASK; PLIC_DisableIRQ(I2C1_ALERT_PLIC); } if (irq_type & MSS_I2C_SMBSUS_IRQ) { /* Disable interrupt at the PLIC and the MSS I2C. */ this_i2c->hw_reg->SMBUS &= ~SMBSUS_IE_MASK; PLIC_DisableIRQ(I2C1_SUS_PLIC); } } } /*------------------------------------------------------------------------------ * MSS_I2C_suspend_smbus_slave() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_suspend_smbus_slave ( mss_i2c_instance_t * this_i2c ) { /* Active low output so 0 asserts condition. */ this_i2c->hw_reg->SMBUS &= ~SMBSUS_NO_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_resume_smbus_slave() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_resume_smbus_slave ( mss_i2c_instance_t * this_i2c ) { /* Active low output so 1 clears condition. */ this_i2c->hw_reg->SMBUS |= SMBSUS_NO_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_reset_smbus() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_reset_smbus ( mss_i2c_instance_t * this_i2c ) { this_i2c->hw_reg->SMBUS |= SMBUS_RESET_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_set_smbus_alert() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_smbus_alert ( mss_i2c_instance_t * this_i2c ) { /* Active low output so 0 asserts condition. */ this_i2c->hw_reg->SMBUS &= ~SMBALERT_NO_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_clear_smbus_alert() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_clear_smbus_alert ( mss_i2c_instance_t * this_i2c ) { /* Active low output so 1 clears condition. */ this_i2c->hw_reg->SMBUS |= SMBALERT_NO_MASK; } /*------------------------------------------------------------------------------ * MSS_i2C_set_gca() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_gca ( mss_i2c_instance_t * this_i2c ) { /* Accept GC addressing. */ this_i2c->hw_reg->ADDR |= ADDR_GC_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_clear_gca() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_clear_gca ( mss_i2c_instance_t * this_i2c ) { /* Disable GC addressing. */ this_i2c->hw_reg->ADDR &= ~ADDR_GC_MASK; } /*------------------------------------------------------------------------------ * MSS_I2C_set_user_data() * See "mss_i2c.h" for details of how to use this function. */ void MSS_I2C_set_user_data ( mss_i2c_instance_t * this_i2c, void * p_user_data ) { this_i2c->p_user_data = p_user_data ; } /*------------------------------------------------------------------------------ * MSS_I2C_get_user_data() * See "mss_i2c.h" for details of how to use this function. */ void * MSS_I2C_get_user_data ( mss_i2c_instance_t * this_i2c ) { return (this_i2c->p_user_data); } /******************************************************************************* * Global initialization based on instance */ static void global_init ( mss_i2c_instance_t * this_i2c ) { if (&g_mss_i2c0_lo == this_i2c) { this_i2c->hw_reg = MSS_I2C0_LO_BASE; g_i2c_axi_pos |= 0x01; } else if (&g_mss_i2c1_lo == this_i2c) { this_i2c->hw_reg = MSS_I2C1_LO_BASE; g_i2c_axi_pos |= 0x02; } else if (&g_mss_i2c0_hi == this_i2c) { this_i2c->hw_reg = MSS_I2C0_HI_BASE; g_i2c_axi_pos |= 0x04; } else if (&g_mss_i2c1_hi == this_i2c) { this_i2c->hw_reg = MSS_I2C1_HI_BASE; g_i2c_axi_pos |= 0x08; } else { ; } if ((this_i2c == &g_mss_i2c0_lo) || (this_i2c == &g_mss_i2c0_hi)) { this_i2c->irqn = I2C0_MAIN_PLIC; } else if ((this_i2c == &g_mss_i2c1_lo) || (this_i2c == &g_mss_i2c1_hi)) { this_i2c->irqn = I2C1_MAIN_PLIC; } else { ; } } /*------------------------------------------------------------------------------ * External_i2c0_main_plic_IRQHandler interrupt handler */ uint8_t External_i2c0_main_plic_IRQHandler(void) { if (g_i2c_axi_pos & 0x01) { mss_i2c_isr(&g_mss_i2c0_lo); } else { mss_i2c_isr(&g_mss_i2c0_hi); } return (uint8_t)EXT_IRQ_KEEP_ENABLED; } /*------------------------------------------------------------------------------ * i2c1_main_plic_IRQHandler interrupt handler */ uint8_t i2c1_main_plic_IRQHandler(void) { if (g_i2c_axi_pos & 0x02) { mss_i2c_isr(&g_mss_i2c1_lo); } else { mss_i2c_isr(&g_mss_i2c1_hi); } return (uint8_t)EXT_IRQ_KEEP_ENABLED; } #ifdef __cplusplus } #endif