/************************************************************************** * Project : shakti devt board * Name of the file : i2c_driver.c * Brief Description of file : Demonstartes the working of i2c protocol. * Name of Author : Kotteeswaran * Email id : kottee.1@gmail.com Copyright (C) 2019 IIT Madras. All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***************************************************************************/ /** @file i2c_driver.c @brief Contains the driver routines for i2c driver using dedicated I2C pins. @detail This file has the driver support for i2c using dedicated i2c pins. This file contains init, configure, read and write routines*/ /* Enable these bits only when corresponding interrupt is needed.*/ #include "i2c.h"//Includes the definitions of i2c communication protocol// #include "log.h" #include "utils.h" /* Enable these bits only when corresponding interrupt is needed.*/ //#define USE_SA_WRITE_I2C_INTERRUPT 1 //#define USE_WRITE_I2C_INTERRUPT 1 //#define USE_READ_I2C_INTERRUPT 1 i2c_struct *i2c_instance[MAX_I2C_COUNT]; /** * @fn void i2c_init() * @brief Initialises the i2c modules. * @details Initialises the i2c array modules to the max. no of i2c modules present */ void i2c_init() { for(int i=0; i< MAX_I2C_COUNT; i++) { i2c_instance[i] = (i2c_struct*) (I2C0_BASE + (i * I2C_OFFSET)); } } /** @fn int config_i2c(i2c_struct * instance, unsigned char prescale_div, unsigned char scl_div) * @brief This routine configures the serial clock frequency count and * prescaler count. * @details There are 4 registers which are configurable. This function * writes into the registr based on the passed address. he serial clock count * and prescalar count decides the frequency (sck) that needs to be used for * the I2C serial communication. Then resets status register. * @param i2c_struct* * @param unsigned char prescale_div * @param unsigned char scl_div * @return REturns 0 if success; else returns -ENXIO. */ int config_i2c(i2c_struct * instance, unsigned char prescale_div, unsigned char scl_div) { unsigned char temp = 0; log_debug("\tI2C: Initializing the Controller\n"); if(prescale_div != instance->prescale ) { instance->prescale = prescale_div; #ifdef DEBUG temp = instance->prescale; if((temp | 0x00) != prescale_div) { log_error("\t Failed to write Prescale division Written Value: 0x%x; read Value: 0x%x\n", prescale_div, instance->prescale); return -ENXIO; } else { log_debug("\tPrescaler successfully initalized\n"); } #endif } if(scl_div != instance->scl ) { instance->scl = scl_div; //Setting the I2C clock value to be 1, which will set the clock for module and prescaler clock #ifdef DEBUG temp = instance->scl; /* Just reading the written value to see if all is well -- Compiler should not optimize this load!!! Compiler can just optimize the store to pointer address followed by load pointer to a register to just an immediate load to the register since clock register is not used anywhere -- but the purpose is lost. Don't give compiler optimizations */ if((temp | 0x00) != scl_div) { log_error("\tClock initialization failed Write Value: 0x%x; read Value: 0x%x\n", scl_div, temp); return -ENXIO; } else { log_debug("\tClock successfully initalized\n"); } #endif } /* S1=0x80 S0 selected, serial interface off */ log_debug("\tClearing the status register. \n"); instance->control = I2C_PIN; // Reading set control Register Value to ensure sanctity log_debug("\tReading Status Register \n"); temp = instance->control; //Check whether the status register is cleared or not. if((temp & 0x7f) != 0){ log_error("\tDevice Not Recognized\n"); return -ENXIO; } log_debug("\tWaiting for a specified time\n "); waitfor(900); //1 Second software wait -- Should be 900000 but setting to 900 now since simulation is already slow log_debug("\tDone Waiting \n "); log_info("\nControl: %x; Status: %x", instance->control, instance->status); /* Enable Serial Interface */ instance->control = I2C_IDLE; waitfor(900); //1 Second software wait -- Should be 900000 but setting to 900 now since simulation is already slow temp = instance->status; /* Check to see if I2C is really in Idle and see if we can access the status register -- If not something wrong in initialization. This also verifies if Control is properly written since zero bit will be initialized to zero*/ if(temp != (I2C_PIN | I2C_BB)){ log_error("\n\tInitialization failed; Status Reg: %x\n", temp); return -ENXIO; } log_info("\tI2C Initialization success\n"); return 0; } /** * @fn int wait_till_I2c_bus_free(i2c_struct * instance) * @brief wait for the i2c bus to be freee. * @details once a I2C Transaction is started, the bus needs to be freed for other devices * to use the bus. This function checks the busy bit in status register to become free * for a particular time. If it becomes free, returns 0, else negative value. * @param i2c_struct* * @return Returns 0 if the bus becomes free; else returns ETIMEDOUT. */ int wait_till_I2c_bus_free(i2c_struct * instance) { log_debug("\tCheck for I2C Bus Busy to be free.\n"); int timeout = DEF_TIMEOUT; int status; status = instance->status; while (!(status & I2C_BB) && --timeout) { waitfor(20000); /* wait for 100 us */ status = instance->status; } if (timeout == 0) { log_error("\t Bus busy wait - timed out. Resetting\n"); return ETIMEDOUT; } return 0; } /** * @fn int wait_till_txrx_operation_Completes(i2c_struct * instance, int *status) * @brief Waits in the loop till the i2c tx/rx operation completes * @details The PIN bit in the status register becomes high when tx/rx operation * starts and becomes low once done. This function checks whether tx/rx operaiton is complete or not. * @param i2c_struct* * @param int *status --> contents of the status register * @return zero if success; else -ETIMEOUT */ int wait_till_txrx_operation_Completes(i2c_struct * instance, int *status) { int timeout = DEF_TIMEOUT; *status = instance->status; while ((*status & I2C_PIN) && --timeout) { waitfor(10000); /* wait for 100 us */ *status = instance->status; } if (timeout == 0){ log_info("\tWait for pin timed out\n"); return ETIMEDOUT; } waitfor(10000); /* wait for 100 us */ log_debug("\n I2C tx_rx operation is completed"); return 0; } /** * @fn int sendbytes(i2c_struct * instance, const char *buf, int count, int last, int eni) * @brief writes "n" number of bits over i2c bus. * @details Called when the user wants to write n number of bits over i2c bus. * @param i2c_struct* --- i2c structure pointer * @param const char *buf --- Pointer to buffer which contains the data to be written. * @param int count --- No of bytes to be written. * @param int last --- If 1, I2C stop command can be sent * @param int eni --- External Interrupt Control * @return Returns number of bytes written else EREMOTEIO. */ int sendbytes(i2c_struct * instance, const char *buf, int count, int last, int eni) { int wrcount, status, timeout; printf("\tStarting Write Transaction -- Did you create tri1 nets for SDA and SCL in verilog?\n"); for (wrcount=0; wrcountdata = buf[wrcount]; timeout = wait_till_txrx_operation_Completes(instance, &status); if (timeout) { printf("\tTimeout happened - Write did not go through the BFM -- Diagnose\n"); instance->control = I2C_STOP; return EREMOTEIO; } if (status & I2C_LRB) { // What error is this? instance->control = I2C_STOP;//~ printf("\tSome status check failing\n"); return EREMOTEIO; } } if (last){ printf("\tLast byte sent : Issue a stop\n"); instance->control = I2C_STOP; } else{ printf("\tSending Rep Start and doing some other R/W transaction\n"); if(!eni) instance->control = I2C_REPSTART; else instance->control = I2C_REPSTART_ENI; } return wrcount; } /** * @fn int readbytes(i2c_struct * instance, char *buf, int count, int last) * @brief Reads "n" number of bytes from I2C Bus * @details Reads n number of bytes over I2C Bus and store teh same in * "buf" pointer. * @param i2c_struct* --- i2c structure pointer * @param const char *buf --- Pointer to buffer which contains the read data. * @param int count --- No of bytes to be read. * @param int last --- If 1, I2C stop command can be sent * @return Returns number of bytest read over i2c bus. else -1. */ int readbytes(i2c_struct * instance, char *buf, int count, int last) { int i, status; int wfp; /* increment number of bytes to read by one -- read dummy byte */ for (i = 0; i <= count; i++) { wfp = wait_till_txrx_operation_Completes(instance, &status); if (wfp) { instance->control = I2C_STOP; return -1; } if ((status & I2C_LRB) && (i != count)) { instance->control = I2C_STOP; printf("\tNo ack\n"); return -1; } if (i) { buf[i - 1] = instance->data; printf("\n Read Value: %x", buf[i - 1]); } else instance->data = !instance->data; /* dummy read */ if (i == count - 1) { instance->control = I2C_ESO; } else if (i == count) { if (last) instance->control = I2C_STOP; else instance->control = I2C_REPSTART_ENI; } } return i-1; //excluding the dummy read } /** * @fn int i2c_send_slave_address(i2c_struct * instance, unsigned char slaveAddress, unsigned char rdWrCntrl, unsigned long delay) * @brief Performs the intilization of i2c slave. * @details Writes slave addresss into the i2b to start write or read operation. * @param i2c_struct* * @param unsigned char slaveAddress * @param unsigned char rdWrCntrl * @param unsigned long delay * @return Zero if success; else non zero */ int i2c_send_slave_address(i2c_struct * instance, unsigned char slaveAddress, unsigned char rdWrCntrl, unsigned long delay) { int timeout; unsigned char temp = 0; int status = 0; delay = delay; if(rdWrCntrl == 0) slaveAddress |= I2C_WRITE; else slaveAddress |= I2C_READ; //Writing the slave address that needs to be written into data register. instance->data = slaveAddress; log_debug("\tSlave Address 0x%x written into data register\n", slaveAddress); //Reads back the data register to confirm temp = instance->data; //Reads the slave address from I2C controller if(slaveAddress != (int)temp) { log_error("\tSlave address is not matching; Written Add. Value: 0x%x; Read Add. Value: 0x%x\n", slaveAddress, temp); log_error("\n There is some issue in AXI interface. Please check."); return EAXI_ERROR; } //Waits till the bus becomes free. while(wait_till_I2c_bus_free(instance)) { log_error("\tError in Waiting for BB\n"); return EI2C_BUS_ERROR; } //Send the start condition and slave address to slave #ifndef USE_SA_WRITE_I2C_INTERRUPT instance->control = I2C_START; //Sending the slave address to the I2C slave waitfor(90000); //Wait for PIN to become low. timeout = wait_till_txrx_operation_Completes(instance, &status); if (timeout) {//Asking the controller to send a start signal to initiate the transaction printf("\tTimeout happened - Write did not go through the BFM -- Diagnose\n"); instance->control = I2C_STOP; //~ return EI2C_PIN_ERROR; } if (status & I2C_LRB) { instance->control = I2C_STOP; //~ printf("\tSome status check failing\n"); return EI2C_LRB_ERROR; } #else i2c_complete_flag = 0; instance->control = I2C_START_ENI; //Sending the slave address to the I2C slave while(!i2c_complete_flag); log_info("\n Slave Address Write Operation is complete."); i2c_complete_flag = 0; #endif return I2C_SUCCESS; } /** * @fn int i2c_write_data(i2c_struct * instance, unsigned char writeData, unsigned char delay) * @brief I does the reading or writing from the address specified . * @details Writes one byte to the slave I2C DEVICE. * @param i2c_struct*, * @param unsigned char writedata * @param unsigned char delay * @return Returns Zeron on success; else -EREMOTEIO */ int i2c_write_data(i2c_struct * instance, unsigned char writeData, unsigned char delay) { int timeout; int status = 0; delay = delay; instance->data= writeData; #ifndef USE_WRITE_I2C_INTERRUPT timeout = wait_till_txrx_operation_Completes(instance, &status); if (timeout) { printf("\tTimeout happened - Write did not go through the BFM -- Diagnose\n"); instance->control = I2C_STOP; //~ return EREMOTEIO; } if (status & I2C_LRB) { // What error is this? instance->control = I2C_STOP; //~ printf("\tSome status check failing\n"); return EI2C_LRB_ERROR; } #else i2c_complete_flag = 0; instance->control = I2C_STOP_ENI; //Sending the sslave address to the I2C slave while(!i2c_complete_flag); log_info("\n Write Operation is complete."); i2c_complete_flag = 0; #endif return I2C_SUCCESS; } /** * @fn int i2c_read_data(i2c_struct * instance, unsigned char *read_data, unsigned char delay) * @brief It does the reading or writing from the address specified . * @details Reads a byte of data over I2C bus from the passed I2C location. * @param i2c_struct* * @param unsigned char *read_data * @param unsigned char delay * @return Zero on success; else -ETIMEOUT */ //#define READ_INTERRUPT 1 int i2c_read_data(i2c_struct * instance, unsigned char *read_data, unsigned char delay) { int status = 0; /* Make a dummy read as per spec of the I2C controller */ *read_data = instance->data; //~ #ifdef USE_WRITE_I2C_INTERRUPT i2c_complete_flag = 0; instance->control = I2C_REPSTART_ENI; //~ while(!i2c_complete_flag); *read_data = instance->data; printf("\n I2C Read Data = %x", i2c_read_data); #else while(wait_till_txrx_operation_Completes(instance, &status)) { printf("\twaiting for pin\n"); waitfor(delay); } #endif return I2C_SUCCESS; } /** * @fn int i2c_send_interrupt_slave_address(i2c_struct * instance, unsigned char slaveAddress, unsigned char rdWrCntrl, unsigned long delay) * @brief Sends the slave address over I2C Bus. * @details Interrupt based routine to send slave address to the I2C slave device * @param i2c_struct* * @param unsigned char slaveAddress * @param unsigned char rdWrCntrl * @param unsigned long delay * @return Zero on success. Else corresponding error value. */ int i2c_send_interrupt_slave_address(i2c_struct * instance, unsigned char slaveAddress, unsigned char rdWrCntrl, unsigned long delay) { int timeout; unsigned char temp = 0; int status = 0; delay = delay; if(rdWrCntrl == 0) slaveAddress |= I2C_WRITE; else slaveAddress |= I2C_READ; log_debug("\n\tSetting Slave Address : 0x%x\n", slaveAddress);/* Writes the slave address to I2C controller */ //Writing the slave address that needs to be written into data register. instance->data = slaveAddress; log_debug("\tSlave Address is written into data register\n"); //Reads back the data register to confirm temp = instance->data; //Reads the slave address from I2C controller log_debug("\tSet slave address read again, which is 0x%x\n",temp); if(slaveAddress != (int)temp) { log_error("\tSlave address is not matching; Written Add. Value: 0x%x; Read Add. Value: 0x%x\n", slaveAddress, temp); log_error("\n There is some issue in AXI interface. Please check."); return EAXI_ERROR; } //Waits till the bus becomes free. while(wait_till_I2c_bus_free(instance)) { log_error("\tError in Waiting for BB\n"); return EI2C_BUS_ERROR; } //Send the start condition and slave address to slave #ifndef USE_SA_WRITE_I2C_INTERRUPT instance->control = I2C_START;; //Sending the slave address to the I2C slave //Wait for PIN to become low. timeout = wait_till_txrx_operation_Completes(instance, &status); if (timeout) {//Asking the controller to send a start signal to initiate the transaction printf("\tTimeout happened - Write did not go through the BFM -- Diagnose\n"); instance->control = I2C_STOP; //~ return EI2C_PIN_ERROR; } if (status & I2C_LRB) { instance->control = I2C_STOP; //~ printf("\tSome status check failing\n"); return EI2C_LRB_ERROR; } #else i2c_complete_flag = 0; instance->control = I2C_REPSTART_ENI; //Sending the slave address to the I2C slave while(!i2c_complete_flag); log_info("\n Slave Address Write Operation is complete."); i2c_complete_flag = 0; #endif log_info("\n Slave address is written successfully"); return I2C_SUCCESS; } /** * @fn int i2c_read_interrupt_data(i2c_struct * instance, unsigned char *read_data, unsigned char delay, unsigned char last) * @brief Interrupt based I2C read * @details Interrupt based i2c read function to read from the I2C slave. * @param i2c_struct * @param unsigned char *read_data * @param unsigned char delay * @param unsigned char last * @return Zero on success. */ int i2c_read_interrupt_data(i2c_struct * instance, unsigned char *read_data, unsigned char delay, unsigned char last) { int status = 0; /* Make a dummy read as per spec of the I2C controller */ *read_data = instance->data; #ifdef USE_READ_I2C_INTERRUPT i2c_complete_flag = 0; if(last) { instance->control = I2C_STOP_ENI; //~ while(!i2c_complete_flag); } else { /* Needs to be tested */ // instance->control = I2C_REPSTART_ENI; // printf("\n Call I2C rep. start eni"); // while(!i2c_complete_flag); } printf("\n I2C Read Data = %x", *read_data); #else while(wait_till_txrx_operation_Completes(instance, &status)) { printf("\twaiting for pin\n"); waitfor(delay); } if(!last) { printf("\n Rep Start"); // instance->control = I2C_REPSTART; } else { printf("\nCall I2C Stop"); instance->control = I2C_STOP; } #endif return I2C_SUCCESS; } /** * @fn int i2c_write_interrupt_data(i2c_struct * instance, unsigned char writeData, unsigned char delay, unsigned char last) * @brief Interrupt based I2C write function. * @details Writes a byte of data into slave I2C bus using interrupt. * @param i2c_struct* * @param unsigned char writeData * @param unsigned char delay * @param unsigned char last * @return Zero on success. Else based on the error. */ int i2c_write_interrupt_data(i2c_struct * instance, unsigned char writeData, unsigned char delay, unsigned char last) { int timeout; int status = 0; delay = delay; instance->data = writeData; #ifndef USE_WRITE_I2C_INTERRUPT timeout = wait_till_txrx_operation_Completes(instance, &status); if (timeout) { printf("\tTimeout happened - Write did not go through the BFM -- Diagnose\n"); instance->control = I2C_STOP; //~ return EREMOTEIO; } if (status & I2C_LRB) { // What error is this? instance->control = I2C_STOP;//~ printf("\tSome status check failing\n"); return EI2C_LRB_ERROR; } if(1 == last) { instance->control = I2C_STOP;; printf("\tI2C Write Success and completes\n"); } #else i2c_complete_flag = 0; if(last) { instance->control = I2C_STOP_ENI; //Sending the sslave address to the I2C slave printf("\n Calling stop eni write"); while(!i2c_complete_flag); } else { // instance->control = I2C_REPSTART_ENI; // printf("\n Calling repstart eni write"); // while(!i2c_complete_flag); } log_info("\n Write Operation is complete."); i2c_complete_flag = 0; #endif return I2C_SUCCESS; }