/* IBM_PROLOG_BEGIN_TAG */ /* This is an automatically generated prolog. */ /* */ /* $Source: src/usr/i2c/i2c.C $ */ /* */ /* OpenPOWER HostBoot Project */ /* */ /* Contributors Listed Below - COPYRIGHT 2011,2018 */ /* [+] Google Inc. */ /* [+] International Business Machines Corp. */ /* */ /* */ /* 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. */ /* */ /* IBM_PROLOG_END_TAG */ /** * @file i2c.C * * @brief Implementation of the i2c device driver * */ // ---------------------------------------------- // Includes // ---------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "i2c.H" #include "errlud_i2c.H" #include "tpmdd.H" #include #include #include #include #include // TODO RTC 94872 Remove the following include in a future commit #include // ---------------------------------------------- // Globals // ---------------------------------------------- // ---------------------------------------------- // Trace definitions // ---------------------------------------------- trace_desc_t* g_trac_i2c = NULL; TRAC_INIT( & g_trac_i2c, I2C_COMP_NAME, KILOBYTE ); trace_desc_t* g_trac_i2cr = NULL; TRAC_INIT( & g_trac_i2cr, "I2CR", KILOBYTE ); // Easy macro replace for unit testing //#define TRACUCOMP(args...) TRACFCOMP(args) #define TRACUCOMP(args...) // ---------------------------------------------- // Defines // ---------------------------------------------- #define I2C_RESET_DELAY_NS (5 * NS_PER_MSEC) // Sleep for 5 ms after reset #define I2C_RESET_POLL_DELAY_NS (500 * 1000) // Sleep for 500usec per poll #define I2C_RESET_POLL_DELAY_TOTAL_NS (500 * NS_PER_MSEC) // Total time to poll #define P9_MASTER_PORTS 13 // Number of Ports used in P9 #define CENTAUR_MASTER_ENGINES 1 // Number of Engines in a Centaur #define MAX_NACK_RETRIES 3 #define PAGE_OPERATION 0xffffffff // Special value use to determine type of op #define P9_ENGINE_SCOM_OFFSET 0x1000 // Derived from ATTR_I2C_BUS_SPEED_ARRAY[engine][port] attribute const TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type g_var = {{NULL}}; #define I2C_BUS_ATTR_MAX_ENGINE I2C_BUS_MAX_ENGINE(g_var) #define I2C_BUS_ATTR_MAX_PORT I2C_BUS_MAX_PORT(g_var) #define FSI_MODE_MAX_PORT 15 // ---------------------------------------------- namespace I2C { // Register the generic I2C perform Op with the routing code for Procs. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::I2C, TARGETING::TYPE_PROC, i2cPerformOp ); // Register the generic I2C perform Op with the routing code for Memory Buffers. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::I2C, TARGETING::TYPE_MEMBUF, i2cPerformOp ); // ------------------------------------------------------------------ // i2cPerformOp // ------------------------------------------------------------------ errlHndl_t i2cPerformOp( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, void * io_buffer, size_t & io_buflen, int64_t i_accessType, va_list i_args ) { errlHndl_t err = NULL; // Get the input args our of the va_list // Address, Port, Engine, Device Addr. // Other args set below misc_args_t args; args.port = va_arg( i_args, uint64_t ); args.engine = va_arg( i_args, uint64_t ); args.devAddr = va_arg( i_args, uint64_t ); // These are additional parms in the case an offset is passed in // via va_list, as well // Set both Host and FSI switches to 0 so that they get set later by // attribute in i2cCommonOp() args.switches.useHostI2C = 0; args.switches.useFsiI2C = 0; // Decide if page select was requested (denoted with special device address) if( args.devAddr == PAGE_OPERATION ) { // since this was a page operation, next arg will be whether we want to // lock the page, or unlock bool l_lockOp = static_cast(va_arg(i_args, int)); if(l_lockOp) { //If page select requested, desired page would be passed in va_list uint8_t l_desiredPage = static_cast(va_arg(i_args, int )); bool l_lockMutex = static_cast(va_arg(i_args, int)); err = i2cPageSwitchOp( i_opType, i_target, i_accessType, l_desiredPage, l_lockMutex, args ); if( err ) { TRACFCOMP(g_trac_i2c, "Locking the page FAILED"); bool l_pageUnlockSuccess = false; l_pageUnlockSuccess = i2cPageUnlockOp( i_target, args ); if( !l_pageUnlockSuccess ) { TRACFCOMP(g_trac_i2c, "An Error occurred when unlocking page after" " failure to lock the page!"); } } } else { bool l_pageUnlockSuccess; l_pageUnlockSuccess = i2cPageUnlockOp( i_target, args ); if( !l_pageUnlockSuccess ) { TRACFCOMP(g_trac_i2c,"i2cPerformOp::i2cPageUnlockOp - Failure unlocking the page"); /*@ * @errortype * @reasoncode I2C_FAILURE_UNLOCKING_EEPROM_PAGE * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PERFORM_OP * @userdata1 Target Huid * @userdata2 * @devdesc I2C master encountered an error while * trying to unlock the eepromPage */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PERFORM_OP, I2C_FAILURE_UNLOCKING_EEPROM_PAGE, TARGETING::get_huid(i_target), 0x0, true /*Add HB SW Callout*/ ); // TODO RTC 148705: Callout downstream DIMMs err->collectTrace(I2C_COMP_NAME, 256 ); } } } else { args.offset_length = va_arg( i_args, uint64_t); if ( args.offset_length != 0 ) { args.offset_buffer = reinterpret_cast (va_arg(i_args, uint64_t)); } // Else, call the normal common function err = i2cCommonOp( i_opType, i_target, io_buffer, io_buflen, i_accessType, args ); } TRACUCOMP( g_trac_i2c, EXIT_MRK"i2cPerformOp() - %s", ((NULL == err) ? "No Error" : "With Error") ); return err; } // end i2cPerformOp // Register the Host-based I2C perform Op with the routing code for Procs. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::HOSTI2C, TARGETING::TYPE_PROC, host_i2cPerformOp ); // Register the Host-based I2C perform Op with the routing code for Mem Buffers. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::HOSTI2C, TARGETING::TYPE_MEMBUF, host_i2cPerformOp ); // ------------------------------------------------------------------ // host_i2cPerformOp // ------------------------------------------------------------------ errlHndl_t host_i2cPerformOp( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, void * io_buffer, size_t & io_buflen, int64_t i_accessType, va_list i_args ) { errlHndl_t err = NULL; // Get the input args our of the va_list // Address, Port, Engine, Device Addr. // Other args set below misc_args_t args; args.port = va_arg( i_args, uint64_t ); args.engine = va_arg( i_args, uint64_t ); args.devAddr = va_arg( i_args, uint64_t ); // These are additional parms in the case an offset is passed in // via va_list, as well args.offset_length = va_arg( i_args, uint64_t); if ( args.offset_length != 0 ) { args.offset_buffer = reinterpret_cast (va_arg(i_args, uint64_t)); } // Set Host switch to 1 and FSI switch to 0 args.switches.useHostI2C = 1; args.switches.useFsiI2C = 0; // Call common function err = i2cCommonOp( i_opType, i_target, io_buffer, io_buflen, i_accessType, args ); TRACDCOMP( g_trac_i2c, EXIT_MRK"host_i2cPerformOp() - %s", ((NULL == err) ? "No Error" : "With Error") ); return err; } // end host_i2cPerformOp // Register the FSI-based I2C perform Op with the routing code for Procs. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::FSI_I2C, TARGETING::TYPE_PROC, fsi_i2cPerformOp ); // Register the FSI-based I2C perform Op with the routing code for Mem Buffers. DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD, DeviceFW::FSI_I2C, TARGETING::TYPE_MEMBUF, fsi_i2cPerformOp ); // ------------------------------------------------------------------ // fsi_i2cPerformOp // ------------------------------------------------------------------ errlHndl_t fsi_i2cPerformOp( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, void * io_buffer, size_t & io_buflen, int64_t i_accessType, va_list i_args ) { errlHndl_t err = NULL; // Get the input args our of the va_list // Address, Port, Engine, Device Addr. // Other args set below misc_args_t args; args.port = va_arg( i_args, uint64_t ); args.engine = va_arg( i_args, uint64_t ); args.devAddr = va_arg( i_args, uint64_t ); // These are additional parms in the case an offset is passed in // via va_list, as well args.offset_length = va_arg( i_args, uint64_t); if ( args.offset_length != 0 ) { args.offset_buffer = reinterpret_cast (va_arg(i_args, uint64_t)); } // Set FSI switch to 1 and Host switch to 0 args.switches.useHostI2C = 0; args.switches.useFsiI2C = 1; // Call common function err = i2cCommonOp( i_opType, i_target, io_buffer, io_buflen, i_accessType, args ); TRACDCOMP( g_trac_i2c, EXIT_MRK"fsi_i2cPerformOp() - %s", ((NULL == err) ? "No Error" : "With Error") ); return err; } // end fsi_i2cPerformOp // ------------------------------------------------------------------ // i2cHandleError // ------------------------------------------------------------------ void i2cHandleError( TARGETING::Target * i_target, errlHndl_t & i_err, misc_args_t & i_args ) { errlHndl_t err_reset = NULL; TRACUCOMP(g_trac_i2c, ENTER_MRK"i2cHandlError()"); if( i_err ) { // if it was a bus arbitration lost error set the // the reset level so a force unlock reset can be performed i2c_reset_level l_reset_level = BASIC_RESET; if ( i_err->reasonCode() == I2C_ARBITRATION_LOST_ONLY_FOUND ) { l_reset_level = FORCE_UNLOCK_RESET; } // Reset the I2C Master err_reset = i2cReset( i_target, i_args, l_reset_level); if( err_reset ) { // 2 error logs, so commit the reset log here TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCommonOp() - " "Previous error (rc=0x%X, eid=0x%X) before " "i2cReset() failed. Committing reset error " "(rc=0x%X, eid=0x%X) and returning original error", i_err->reasonCode(), i_err->eid(), err_reset->reasonCode(), err_reset->eid() ); errlCommit( err_reset, I2C_COMP_ID ); } // Sleep to allow devices to recover from reset nanosleep( 0, I2C_RESET_DELAY_NS ); } TRACUCOMP(g_trac_i2c, EXIT_MRK"i2cHandlError()"); } errlHndl_t i2cChooseEepromPage(TARGETING::Target * i_target, uint8_t & i_currentPage, uint8_t & i_newPage, uint8_t i_desiredPage, misc_args_t & i_args, bool & i_pageSwitchNeeded ) { errlHndl_t l_err = NULL; // Get EEPROM page attribute TRACUCOMP(g_trac_i2c, "i2cChooseEepromPage: current EEPROM page is %d for target(0x%x)", i_currentPage, TARGETING::get_huid(i_target) ); if( i_currentPage != i_desiredPage ) { if( i_desiredPage == PAGE_ONE ) { TRACUCOMP(g_trac_i2c, "i2cChooseEepromPage: Switching to page ONE"); i_args.devAddr = PAGE_ONE_ADDR; i_newPage = PAGE_ONE; i_pageSwitchNeeded = true; } else if( i_desiredPage == PAGE_ZERO ) { TRACUCOMP(g_trac_i2c, "i2cChooseEepromPage: Switching to page ZERO"); i_args.devAddr = PAGE_ZERO_ADDR; i_newPage = PAGE_ZERO; i_pageSwitchNeeded = true; } else { TRACFCOMP(g_trac_i2c, ERR_MRK"i2cChooseEepromPage: Invalid page requested"); /*@ * @errortype * @reasoncode I2C_INVALID_EEPROM_PAGE_REQUEST * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_CHOOSE_EEPROM_PAGE * @userdata1 Target Huid * @userdata2 Requested Page * @devdesc There was a request for an invalid * EEPROM page */ l_err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_CHOOSE_EEPROM_PAGE, I2C_INVALID_EEPROM_PAGE_REQUEST, TARGETING::get_huid(i_target), i_desiredPage, true ); l_err->collectTrace( I2C_COMP_NAME, 256 ); } } return l_err; } // ------------------------------------------------------------------ // i2cPageSwitchOp // ------------------------------------------------------------------ errlHndl_t i2cPageSwitchOp( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, int64_t i_accessType, uint8_t i_desiredPage, bool i_lockMutex, misc_args_t & i_args ) { TRACUCOMP(g_trac_i2c, ENTER_MRK"i2cPageSwitchOp"); errlHndl_t l_err = NULL; errlHndl_t l_err_NACK = NULL; bool l_mutexSuccess = false; bool l_pageSwitchNeeded = false; bool l_mutex_needs_unlock = false; bool l_error = false; mutex_t * l_pageLock = NULL; uint8_t l_currentPage; uint8_t l_newPage; TARGETING::ATTR_EEPROM_PAGE_ARRAY_type page_array; do { // Check for Master Sentinel chip if( TARGETING::MASTER_PROCESSOR_CHIP_TARGET_SENTINEL == i_target ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cPageSwitchOp() - Cannot target Master Sentinel " "Chip for an I2C Operation!" ); /*@ * @errortype * @reasoncode I2C_MASTER_SENTINEL_TARGET * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PAGE_SWITCH_OP * @userdata1 Operation Type requested * @userdata2 * @devdesc Master Sentinel chip was used as a target for an * I2C operation. This is not permitted. */ l_err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PAGE_SWITCH_OP, I2C_MASTER_SENTINEL_TARGET, i_opType, 0x0, true /*Add HB SW Callout*/ ); l_err->collectTrace( I2C_COMP_NAME, 256); break; } //Set Host vs Fsi switches if not done already i2cSetSwitches(i_target, i_args); if(i_lockMutex) { //get page mutex l_mutexSuccess = i2cGetPageMutex(i_target, i_args, l_pageLock ); if(!l_mutexSuccess) { TRACUCOMP(g_trac_i2c, ERR_MRK"Error in i2cPageSwitchOp::i2cGetPageMutex()"); /*@ * @errortype * @reasoncode I2C_INVALID_EEPROM_PAGE_MUTEX * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PAGE_SWITCH_OP * @userdata1 Target Huid * @userdata2 * @devdesc There was an error retrieving the EEPROM page * mutex for this i2c master engine */ l_err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PAGE_SWITCH_OP, I2C_INVALID_EEPROM_PAGE_MUTEX, TARGETING::get_huid(i_target), 0x0, true /*Add HB SW Callout*/ ); l_err->collectTrace( I2C_COMP_NAME, 256 ); break; } (void)mutex_lock( l_pageLock ); } // Calculate variables related to I2C Bus speed in 'args' struct l_err = i2cSetBusVariables( i_target, I2C_BUS_SPEED_FROM_MRW, i_args); if( l_err ) { TRACFCOMP(g_trac_i2c, "Error in i2cPageSwitchOp::i2cSetBusVariables()"); // Error means we need to unlock the page mutex prematurely l_mutex_needs_unlock = true; // Skip performing actual I2C Operation break; } //Get the i2c master page array attribute if( !(i_target->tryGetAttr (page_array ) ) ) { TRACFCOMP(g_trac_i2c, "i2cPageSwitchOp() - Cannot find ATTR_EEPROM_PAGE_ARRAY"); /*@ * @errortype * @reasoncode I2C_ATTRIBUTE_NOT_FOUND * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PAGE_SWITCH_OP * @userdata1 Target HUID for the attribute * @userdata2 * @devdesc ATTR_EEPROM_PAGE_ARRAY not found * @custdesc I2C configuration data missing */ l_err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PAGE_SWITCH_OP, I2C_ATTRIBUTE_NOT_FOUND, TARGETING::get_huid(i_target), 0x0, true ); l_err->collectTrace( I2C_COMP_NAME, 256 ); l_mutex_needs_unlock = true; break; } // Get the current page for this i2c bus l_currentPage = page_array[i_args.engine][i_args.port]; // Choose the correct EEPROM page l_err = i2cChooseEepromPage( i_target, l_currentPage, l_newPage, i_desiredPage, i_args, l_pageSwitchNeeded ); if( l_err ) { TRACFCOMP(g_trac_i2c, ERR_MRK"Error in i2cPageSwitchOp::i2cChooseEepromPage()"); l_mutex_needs_unlock = true; break; } // If we found that a page switch was needed, perform the // necessary write operation to switch to the desired page if( l_pageSwitchNeeded ) { // Perform the actual write operation to switch pages. i_args.read_not_write = false; i_args.with_stop = true; i_args.skip_mode_setup = false; //going to write 2 bytes of zeros to special device address size_t l_zeroBuflen = 2; uint8_t * l_zeroBuffer = static_cast(malloc(l_zeroBuflen)); memset(l_zeroBuffer, 0, l_zeroBuflen); TRACUCOMP(g_trac_i2c,"i2cPageSwitchOp args! \n" "misc_args_t: port:%d / engine: %d: devAddr: %x: skip_mode_step(%d):\n" "with_stop(%d): read_not_write(%d): bus_speed: %d: bit_rate_divisor: %d:\n" "polling_interval_ns: %d: timeout_count: %d: offset_length: %d", i_args.port, i_args.engine, i_args.devAddr, i_args.skip_mode_setup, i_args.with_stop, i_args.read_not_write, i_args.bus_speed, i_args.bit_rate_divisor, i_args.polling_interval_ns, i_args.timeout_count, i_args.offset_length ); // Retry MAX_NACK_RETRIES so we can bypass nacks caused by a busy bus. // Other Nack errors are expected and caused by the empty write // associated with the page switch operation. for(uint8_t retry = 0; retry <= MAX_NACK_RETRIES; retry++) { l_err = i2cWrite(i_target, l_zeroBuffer, l_zeroBuflen, i_args); if(l_err == NULL) { // Operation completed successfully // set attribute and break from retry loop TRACUCOMP(g_trac_i2c,"Set EEPROM_PAGE to %d", i_desiredPage); page_array[i_args.engine][i_args.port] = l_newPage; i_target->setAttr(page_array); break; } else if( l_err->reasonCode() != I2C_NACK_ONLY_FOUND) { // Only retry on NACK failures. Break from retry loop TRACFCOMP(g_trac_i2c, ERR_MRK"i2cPageSwitchOp(): I2C Write " "Non-NACK fail %x", i_args.devAddr ); l_err->collectTrace(I2C_COMP_NAME); l_mutex_needs_unlock = true; l_error = true; break; } else // Handle NACK error { TRACFCOMP(g_trac_i2c, "i2cPageSwitchOp::Expected Nack error. Retrying in case " "this nack was caused by bus being busy " "loop = %d", retry); nanosleep( 0, i_args.polling_interval_ns ); // Retry on NACKs just in case the cause was a busy i2c bus. if( retry < MAX_NACK_RETRIES ) { if(l_err_NACK == NULL) { l_err_NACK = l_err; TRACUCOMP(g_trac_i2c, "Saving first Nack error and retry"); nanosleep(0, i_args.polling_interval_ns); l_err_NACK->collectTrace(I2C_COMP_NAME); } else { // Delete this new NACK error delete l_err; nanosleep(0 ,i_args.polling_interval_ns); l_err = NULL; } // continue to retry continue; } else // no more retries: trace and break; { TRACFCOMP(g_trac_i2c, "Exiting Nack retry loop"); break; } } } // end of retry loop if(l_err_NACK) { if( l_err ) { i2cHandleError( i_target, l_err, i_args ); delete l_err; l_err = NULL; } delete l_err_NACK; l_err_NACK = NULL; } //free zero buffer free(l_zeroBuffer); } else { TRACUCOMP(g_trac_i2c, "On correct page(%d). No page switch needed", l_currentPage); } if( l_error ) { //call i2cHandleError i2cHandleError( i_target, l_err, i_args ); break; } }while( 0 ); if( l_mutex_needs_unlock ) { TRACFCOMP(g_trac_i2c, "Prematurely unlocking page mutex"); (void)mutex_unlock(l_pageLock); } TRACUCOMP(g_trac_i2c, EXIT_MRK"i2cPageSwitchOp()"); return l_err; } // ------------------------------------------------------------------ // i2cPageUnlockOp // ------------------------------------------------------------------ bool i2cPageUnlockOp( TARGETING::Target * i_target, misc_args_t & i_args ) { TRACUCOMP(g_trac_i2c, ENTER_MRK"i2cPageUnlockOp()"); bool l_mutexSuccess = false; mutex_t * l_pageLock = NULL; errlHndl_t l_err = NULL; do { // Get the mutex for this target l_mutexSuccess = i2cGetPageMutex( i_target, i_args, l_pageLock ); if( !l_mutexSuccess ) { TRACUCOMP( g_trac_i2c, ERR_MRK"Error in i2cPageUnlockOp::i2cGetPageMutex()"); /*@ * @errortype * @reasoncode I2C_INVALID_EEPROM_PAGE_MUTEX * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PAGE_UNLOCK_OP * @userdata1 Target Huid * @userdata2 * @devdesc There was an error retrieving the EEPROM page * mutex for this i2c master engine */ l_err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PERFORM_OP, I2C_INVALID_EEPROM_PAGE_MUTEX, TARGETING::get_huid(i_target), 0x0, true /*Add HB SW Callout*/ ); l_err->collectTrace( I2C_COMP_NAME, 256 ); errlCommit(l_err, I2C_COMP_ID); break; } //Unlock the page mutex (void)mutex_unlock(l_pageLock); }while( 0 ); TRACUCOMP(g_trac_i2c, EXIT_MRK"i2cPageUnlockOp()"); return l_mutexSuccess; } // ------------------------------------------------------------------ // i2cSetSwitches // ------------------------------------------------------------------ void i2cSetSwitches( TARGETING::Target * i_target, misc_args_t & io_args ) { // Set Host vs FSI switches if both values are zero; // Otherwise, caller should have already set them if ( ( io_args.switches.useHostI2C == 0 ) && ( io_args.switches.useFsiI2C == 0 ) ) { if ( !( i_target->tryGetAttr (io_args.switches) ) ) { // Default to Host io_args.switches.useHostI2C = 1; io_args.switches.useFsiI2C = 0; } } return; } // ------------------------------------------------------------------ // i2cCommonOp // ------------------------------------------------------------------ errlHndl_t i2cCommonOp( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, void * io_buffer, size_t & io_buflen, int64_t i_accessType, misc_args_t & i_args ) { errlHndl_t err = NULL; bool mutex_success = false; mutex_t * engineLock = NULL; bool mutex_needs_unlock = false; TRACUCOMP( g_trac_i2c, ENTER_MRK"i2cCommonOp(): i_opType=%d, aType=%d, " "p/e/devAddr= %d/%d/0x%x, len=%d, offset=%x/%p", (uint64_t) i_opType, i_accessType, i_args.port, i_args.engine, i_args.devAddr, io_buflen, i_args.offset_length, i_args.offset_buffer); do { // Check for Master Sentinel chip if( TARGETING::MASTER_PROCESSOR_CHIP_TARGET_SENTINEL == i_target ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCommonOp() - Cannot target Master Sentinel " "Chip for an I2C Operation!" ); /*@ * @errortype * @reasoncode I2C_MASTER_SENTINEL_TARGET * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PERFORM_OP * @userdata1 Operation Type requested * @userdata2 * @devdesc Master Sentinel chip was used as a target for an * I2C operation. This is not permitted. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PERFORM_OP, I2C_MASTER_SENTINEL_TARGET, i_opType, 0x0, true /*Add HB SW Callout*/ ); err->collectTrace( I2C_COMP_NAME, 256); break; } //Set Host vs Fsi switches if not done already i2cSetSwitches(i_target, i_args); // Get the mutex for the requested engine mutex_success = i2cGetEngineMutex( i_target, i_args, engineLock ); if( !mutex_success ) { TRACUCOMP( g_trac_i2c, ERR_MRK"Error in i2cCommonOp::i2cGetEngineMutex()"); break; } // Lock on this engine (void)mutex_lock( engineLock ); mutex_needs_unlock = true; // Calculate variables related to I2C Bus Speed in 'args' struct err = i2cSetBusVariables( i_target, I2C_BUS_SPEED_FROM_MRW, i_args); if( err ) { // Skip performing the actual I2C Operation break; } /*******************************************************/ /* Perform the I2C Operation */ /*******************************************************/ /***********************************************/ /* I2C Read with Offset */ /***********************************************/ if( i_opType == DeviceFW::READ && i_args.offset_length != 0 ) { // First WRITE offset to device without a stop i_args.read_not_write = false; i_args.with_stop = false; i_args.skip_mode_setup = false; err = i2cWrite( i_target, i_args.offset_buffer, i_args.offset_length, i_args ); if( err == NULL ) { // Now do the READ with a stop i_args.read_not_write = true; i_args.with_stop = true; // Skip mode setup on this cmd - // already set with previous cmd i_args.skip_mode_setup = true; err = i2cRead( i_target, io_buffer, io_buflen, i_args ); } } /***********************************************/ /* I2C Write with Offset */ /***********************************************/ else if( i_opType == DeviceFW::WRITE && i_args.offset_length != 0 ) { // Add the Offset Information to the start of the data and // then send as a single write operation size_t newBufLen = i_args.offset_length + io_buflen; uint8_t * newBuffer = static_cast(malloc(newBufLen)); // Add the Offset to the buffer memcpy( newBuffer, i_args.offset_buffer, i_args.offset_length); // Now add the data the user wanted to write memcpy( &newBuffer[i_args.offset_length], io_buffer, io_buflen); // Write parms: i_args.read_not_write = false; i_args.with_stop = true; i_args.skip_mode_setup = false; err = i2cWrite( i_target, newBuffer, newBufLen, i_args ); free( newBuffer ); } /***********************************************/ /* I2C Read (no offset) */ /***********************************************/ else if ( i_opType == DeviceFW::READ && i_args.offset_length == 0 ) { // Do a direct READ i_args.read_not_write = true; i_args.with_stop = true; i_args.skip_mode_setup = false; err = i2cRead( i_target, io_buffer, io_buflen, i_args); } /***********************************************/ /* I2C Write (no offset) */ /***********************************************/ else if( i_opType == DeviceFW::WRITE && i_args.offset_length == 0 ) { // Do a direct WRITE with a stop i_args.read_not_write = false; i_args.with_stop = true; i_args.skip_mode_setup = false; err = i2cWrite( i_target, io_buffer, io_buflen, i_args); } /********************************************************/ /* Error - Unsupported I2C Op/Offset Type Combination */ /********************************************************/ else { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCommonOp() - " "Unsupported Op/Offset-Type Combination=%d/%d", i_opType, i_args.offset_length ); uint64_t userdata2 = i_args.offset_length; userdata2 = (userdata2 << 16) | i_args.port; userdata2 = (userdata2 << 16) | i_args.engine; userdata2 = (userdata2 << 16) | i_args.devAddr; /*@ * @errortype * @reasoncode I2C_INVALID_OP_TYPE * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_PERFORM_OP * @userdata1 i_opType * @userdata2[0:15] Offset Length * @userdata2[16:31] Master Port * @userdata2[32:47] Master Engine * @userdata2[48:63] Slave Device Address * @devdesc Invalid operation type. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PERFORM_OP, I2C_INVALID_OP_TYPE, i_opType, userdata2, true /*Add HB SW Callout*/ ); err->collectTrace( I2C_COMP_NAME, 256); // No Operation performed, so can break and skip the section // that handles operation errors break; } // Handle Error from I2C Operation if( err ) { i2cHandleError( i_target, err, i_args ); break; } } while( 0 ); // Check if we need to unlock the mutex if ( mutex_needs_unlock == true ) { // Unlock (void) mutex_unlock( engineLock ); TRACUCOMP( g_trac_i2c, INFO_MRK"Unlocked engine: %d", i_args.engine ); } // If there is an error, add FFDC if ( err != NULL ) { // Add parameter info to log // @todo RTC 114298- update this for new parms/switches I2C::UdI2CParms( i_opType, i_target, io_buflen, i_accessType, i_args ) .addToLog(err); // Add secureboot info to log SECUREBOOT::addSecureUserDetailsToErrlog(err); } TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cCommonOp() - %s", ((NULL == err) ? "No Error" : "With Error") ); return err; } // end i2cCommonOp // ------------------------------------------------------------------ // i2cPresence // ------------------------------------------------------------------ bool i2cPresence( TARGETING::Target * i_target, uint64_t i_port, uint64_t i_engine, uint64_t i_devAddr ) { TRACUCOMP(g_trac_i2c, ENTER_MRK"i2cPresence(): tgt=0x%X: e%d/p%d/" "devAddr=0x%X", TARGETING::get_huid(i_target), i_engine, i_port, i_devAddr ); errlHndl_t err = NULL; bool l_mutex_success = false; bool l_present = false; size_t buflen = 1; uint64_t reset_error = 0; misc_args_t args; args.port = i_port; args.engine = i_engine; args.devAddr = i_devAddr; args.read_not_write = true; args.with_stop = true; args.skip_mode_setup = false; //Registers status_reg_t status; fifo_reg_t fifo; // Synchronization mutex_t * engineLock = NULL; bool mutex_needs_unlock = false; // Use Local Variables (timeoutCount gets decremented); uint64_t l_interval_ns; uint64_t l_timeoutCount; do { // Get the mutex for the requested engine l_mutex_success = i2cGetEngineMutex( i_target, args, engineLock ); if( !l_mutex_success ) { TRACUCOMP( g_trac_i2c, ERR_MRK"Error in i2cPresence::i2cGetEngineMutex()"); break; } // Lock on this engine TRACUCOMP( g_trac_i2c, INFO_MRK"Obtaining lock for engine: %d", args.engine ); (void)mutex_lock( engineLock ); mutex_needs_unlock = true; TRACUCOMP( g_trac_i2c, INFO_MRK"Locked on engine: %d", args.engine ); // Set I2C Mode (Host vs FSI) for the target args.switches.useHostI2C = 0; args.switches.useFsiI2C = 0; i2cSetSwitches( i_target, args ); err = i2cSetBusVariables(i_target, I2C_BUS_SPEED_400KHZ, args); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"error in i2cPresence::i2cSetBusVariables()"); break; } err = i2cSetup( i_target, buflen, args ); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"error in i2cPresence::i2cSetup()"); break; } l_interval_ns = args.polling_interval_ns; l_timeoutCount = args.timeout_count; //Check the Command Complete bit do { nanosleep( 0, l_interval_ns ); status.value = 0x0ull; err = i2cRegisterOp( DeviceFW::READ, i_target, &status.value, I2C_REG_STATUS, args ); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"i2cPresence::error in i2cRegisterOp()"); break; } if( 0 == l_timeoutCount-- ) { TRACUCOMP(g_trac_i2c, ERR_MRK"i2cPresence() - Timed out waiting for " "Command Complete!"); break; } }while( 0 == status.command_complete && 0 == status.fifo_entry_count && 0 == status.nack_received && 0 == status.data_request ); /* Command has completed or fifo has data */ if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"Error when waiting for Command Complete"); break; } if( status.nack_received == 0 ) { //The chip was present. TRACUCOMP(g_trac_i2c, ERR_MRK"chip found! i2cPresence returning true!"); // No nack received so we check the FIFO register if( status.fifo_entry_count != 0 || status.data_request != 0 ) { //Read data from FIFO register to complete operation. fifo.value = 0x0ull; err = i2cRegisterOp( DeviceFW::READ, i_target, &fifo.value, I2C_REG_FIFO, args ); TRACUCOMP(g_trac_i2c, "i2cPresence() - FIFO = 0x%016llx", fifo.value); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"Error in i2cPresence::i2cRegisterOp()" " - FIFO register"); break; } // -check for command complete err = i2cWaitForCmdComp(i_target, args ); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"Error in i2cPresence::i2cWaitForCmdComp()"); break; } } l_present = true; break; } else { //The chip was not present. TRACUCOMP(g_trac_i2c, ERR_MRK"The chip was not present!"); // reset error register err = i2cRegisterOp( DeviceFW::WRITE, i_target, &reset_error, I2C_REG_RESET_ERRORS, args ); if( err ) { TRACUCOMP(g_trac_i2c, ERR_MRK"Error in i2cPresence::i2cRegisterOp()" " - Error register"); } break; } } while ( 0 ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cPresence() Error! " "tgt=0x%X: e%d/p%d/devAddr=0x%X", TARGETING::get_huid(i_target), i_engine, i_port, i_devAddr); errlCommit(err, I2C_COMP_ID); } // Check if we need to unlock the mutex if ( mutex_needs_unlock == true ) { // Unlock (void) mutex_unlock( engineLock ); TRACUCOMP( g_trac_i2c, INFO_MRK"Unlocked engine: %d", args.engine ); } TRACUCOMP(g_trac_i2c, EXIT_MRK"i2cPresence(): tgt=0x%X: e%d/p%d/" "devAddr=0x%X: l_present=%d", TARGETING::get_huid(i_target), i_engine, i_port, i_devAddr, l_present ); return l_present; } // ------------------------------------------------------------------ // i2cRead // ------------------------------------------------------------------ errlHndl_t i2cRead ( TARGETING::Target * i_target, void * o_buffer, size_t & i_buflen, misc_args_t & i_args) { errlHndl_t err = NULL; uint64_t bytesRead = 0x0; // Use Local Variables (timeoutCount gets derecmented) uint64_t interval_ns = i_args.polling_interval_ns; uint64_t timeoutCount = i_args.timeout_count; // Define the regs we'll be using status_reg_t status; fifo_reg_t fifo; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cRead()" ); TRACSCOMP( g_trac_i2cr, "I2C READ START : engine %.2X : port %.2X : devAddr %.2X : len %d", i_args.engine, i_args.port, i_args.devAddr, i_buflen ); do { // Do Command/Mode reg setups. i_args.read_not_write = true; err = i2cSetup( i_target, i_buflen, i_args ); if( err ) { break; } for( bytesRead = 0; bytesRead < i_buflen; bytesRead++ ) { TRACDCOMP( g_trac_i2c, INFO_MRK"Reading byte (%d) out of (%d)", (bytesRead+1), i_buflen ); // Read the status reg to see if there is data in the FIFO status.value = 0x0ull; err = i2cReadStatusReg( i_target, i_args, status ); if( err ) { break; } // Wait for 1 of 2 indictators to read from FIFO: // 1) fifo_entry_count !=0 // 2) Data Request bit is on while( (0 == status.fifo_entry_count) && (0 == status.data_request) ) { nanosleep( 0, interval_ns ); status.value = 0x0ull; err = i2cReadStatusReg( i_target, i_args, status ); if( err ) { break; } TRACUCOMP( g_trac_i2c, "i2cRead() Wait Loop: status=0x%016llx " ".fifo_entry_count=%d, .data_request=%d", status.value, status.fifo_entry_count, status.data_request); if( 0 == timeoutCount-- ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cRead() - " "Timed out waiting for data in FIFO! " "status=0x%016llx " ".fifo_entry_count=%d, .data_request=%d", status.value, status.fifo_entry_count, status.data_request ); /*@ * @errortype * @reasoncode I2C_FIFO_TIMEOUT * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_READ * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc Timed out waiting for data in FIFO to read */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_READ, I2C_FIFO_TIMEOUT, I2C_SET_USER_DATA_1( status, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME, 256); break; } } if( err ) { break; } // Read the data from the fifo fifo.value = 0x0ull; err = i2cRegisterOp( DeviceFW::READ, i_target, &fifo.value, I2C_REG_FIFO, i_args ); TRACUCOMP( g_trac_i2c, INFO_MRK"i2cRead() - FIFO = 0x%016llx", fifo.value); if( err ) { break; } *((uint8_t*)o_buffer + bytesRead) = fifo.byte_0; // Every time FIFO is read, reset timeout count timeoutCount = I2C_TIMEOUT_COUNT( interval_ns ); TRACUCOMP( g_trac_i2cr, "I2C READ DATA : engine %.2X : port %.2x : " "devAddr %.2X : byte %d : %.2X (0x%lx)", i_args.engine, i_args.port, i_args.devAddr, bytesRead, fifo.byte_0, fifo.value ); } if( err ) { break; } // Poll for Command Complete err = i2cWaitForCmdComp( i_target, i_args ); if( err ) { break; } } while( 0 ); TRACSCOMP( g_trac_i2cr, "I2C READ END : engine %.2X : port %.2x : devAddr %.2X : len %d", i_args.engine, i_args.port, i_args.devAddr, i_buflen ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cRead()" ); return err; } // end i2cRead // ------------------------------------------------------------------ // i2cWrite // ------------------------------------------------------------------ errlHndl_t i2cWrite ( TARGETING::Target * i_target, void * i_buffer, size_t & io_buflen, misc_args_t & i_args) { errlHndl_t err = NULL; uint64_t bytesWritten = 0x0; // Define regs we'll be using fifo_reg_t fifo; TRACUCOMP( g_trac_i2c, ENTER_MRK"i2cWrite()" ); TRACSCOMP( g_trac_i2cr, "I2C WRITE START : engine %.2X : port %.2X : devAddr %.2X : len %d", i_args.engine, i_args.port, i_args.devAddr, io_buflen ); do { // Do Command/Mode reg setups i_args.read_not_write = false; err = i2cSetup( i_target, io_buflen, i_args); if( err ) { break; } for( bytesWritten = 0x0; bytesWritten < io_buflen; bytesWritten++ ) { // Wait for FIFO space to be available for the write err = i2cWaitForFifoSpace( i_target, i_args ); if( err ) { break; } // Write data to FIFO fifo.value = 0x0ull; fifo.byte_0 = *((uint8_t*)i_buffer + bytesWritten); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &fifo.value, I2C_REG_FIFO, i_args ); if( err ) { break; } TRACSCOMP( g_trac_i2cr, "I2C WRITE DATA : engine %.2X : port %.2X : " "devAddr %.2X : byte %d : %.2X (0x%lx)", i_args.engine, i_args.port, i_args.devAddr, bytesWritten, fifo.byte_0, fifo.value ); } if( err ) { break; } // Check for Command complete err = i2cWaitForCmdComp( i_target, i_args ); if( err ) { break; } // Make sure we send back how many bytes were written io_buflen = bytesWritten; } while( 0 ); TRACSCOMP( g_trac_i2cr, "I2C WRITE END : engine %.2X: port %.2X : devAddr %.2X : len %d", i_args.engine, i_args.port, i_args.devAddr, io_buflen ); TRACUCOMP( g_trac_i2c, EXIT_MRK"i2cWrite()" ); return err; } // end i2cWrite // ------------------------------------------------------------------ // i2cSetup // ------------------------------------------------------------------ errlHndl_t i2cSetup ( TARGETING::Target * i_target, size_t & i_buflen, misc_args_t & i_args) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cSetup(): buf_len=%d, r_nw=%d, w_stop=%d, sms=%d", i_buflen, i_args.read_not_write, i_args.with_stop, i_args.skip_mode_setup); // Define the registers that we'll use mode_reg_t mode; command_reg_t cmd; do { // Wait for Command complete before we start err = i2cWaitForCmdComp( i_target, i_args ); if( err ) { break; } // Skip mode setup on 2nd of 2 cmd strung together - like when sending // device offset first before read or write if ( i_args.skip_mode_setup == false) { // Write Mode Register: // - bit rate divisor (set in i2cSetClockVariables() ) // - port number mode.value = 0x0ull; mode.bit_rate_div = i_args.bit_rate_divisor; //On P9 the mapping of I2C ports isn't logical //in FSI mode // Engine 0, HB doesn't use, but port 0 == FSI 1 // HB doesn't use, but port 1 == FSI 3 // Port 2,3 are not connected // Engine 1, FSI ports == HB ports // Engine 2, Host only ports // Engine 3, port 0 = FSI 13 // port 1 = FSI 14 // port 2,3 are not connected // @TODO RTC 167460 -- account for centaur and other // and this same mapping other places in I2C DD if ( i_args.switches.useFsiI2C && 0 == i_args.engine) { mode.port_num = (i_args.port * 2) +1; } else if ( i_args.switches.useFsiI2C && 3 == i_args.engine) { mode.port_num = 13 + i_args.port; } else { mode.port_num = i_args.port; } TRACUCOMP( g_trac_i2c,"i2cSetup(): set mode = 0x%lx", mode.value); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &mode.value, I2C_REG_MODE, i_args ); if( err ) { break; } } // Write Command Register: // - with start // - with stop // - RnW // - length cmd.value = 0x0ull; cmd.with_start = 1; cmd.with_stop = (i_args.with_stop ? 1 : 0); cmd.with_addr = 1; // cmd.device_addr is 7 bits // devAddr though is a uint64_t // -- value stored in LSB byte of uint64_t // -- LS-bit is unused, creating the 7 bit cmd.device_addr // So will be masking for LSB, and then shifting to push off LS-bit cmd.device_addr = (0x000000FF & i_args.devAddr) >> 1; cmd.read_not_write = (i_args.read_not_write ? 1 : 0); cmd.length_b = i_buflen; TRACUCOMP( g_trac_i2c,"i2cSetup(): set cmd = 0x%lx", cmd.value); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &cmd.value, I2C_REG_COMMAND, i_args ); if( err ) { break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cSetup()" ); return err; } // end i2cSetup // ------------------------------------------------------------------ // i2cGetEngineMutex // ------------------------------------------------------------------ bool i2cGetEngineMutex( TARGETING::Target * i_target, misc_args_t & i_args, mutex_t *& i_engineLock ) { bool success = true; do { switch( i_args.engine ) { case 0: i_engineLock = i_target-> getHbMutexAttr(); break; case 1: i_engineLock = i_target-> getHbMutexAttr(); break; case 2: i_engineLock = i_target-> getHbMutexAttr(); break; case 3: i_engineLock = i_target-> getHbMutexAttr(); break; default: TRACFCOMP( g_trac_i2c, ERR_MRK"i2cGetEngineMutex: Invalid engine for getting Mutex! " "i_args.engine=%d", i_args.engine ); success = false; assert(false, "i2c.C: Invalid engine for getting Mutex!" "i_args.engine=%d", i_args.engine ); break; }; }while( 0 ); return success; } // ------------------------------------------------------------------ // i2cGetPageMutex // ------------------------------------------------------------------ bool i2cGetPageMutex( TARGETING::Target * i_target, misc_args_t & i_args, mutex_t *& i_pageLock ) { bool success = true; do { switch( i_args.engine ) { case 0: i_pageLock = i_target-> getHbMutexAttr(); break; case 1: i_pageLock = i_target-> getHbMutexAttr(); break; case 2: i_pageLock = i_target-> getHbMutexAttr(); break; case 3: i_pageLock = i_target-> getHbMutexAttr(); break; default: TRACFCOMP( g_trac_i2c, ERR_MRK"i2cGetPageMutex: Invalid engine for getting mutex!"); success = false; assert(false, "i2c.C: Invalid engine for getting Mutex!" "i_args.engine=%d", i_args.engine ); break; }; }while( 0 ); return success; } // ------------------------------------------------------------------ // i2cWaitForCmdComp // ------------------------------------------------------------------ errlHndl_t i2cWaitForCmdComp ( TARGETING::Target * i_target, misc_args_t & i_args) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cWaitForCmdComp()" ); // Define the registers that we'll use status_reg_t status; // Use Local Variables (timeoutCount gets decremented) uint64_t interval_ns = i_args.polling_interval_ns; uint64_t timeoutCount = i_args.timeout_count; TRACUCOMP(g_trac_i2c, "i2cWaitForCmdComp(): timeoutCount=%d, " "interval_ns=%d", timeoutCount, interval_ns); do { // Check the Command Complete bit do { nanosleep( 0, interval_ns ); status.value = 0x0ull; err = i2cReadStatusReg( i_target, i_args, status ); if( err ) { TRACFCOMP(g_trac_i2c, "Errored at i2cWaitForCmdComplete::i2cReadStatusReg"); break; } if( 0 == timeoutCount-- ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cWaitForCmdComp() - " "Timed out waiting for Command Complete! " "status=%016llX", status.value ); /*@ * @errortype * @reasoncode I2C_CMD_COMP_TIMEOUT * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_WAIT_FOR_CMD_COMP * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc Timed out waiting for command complete. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_WAIT_FOR_CMD_COMP, I2C_CMD_COMP_TIMEOUT, I2C_SET_USER_DATA_1( status, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME, 256); break; } } while( 0 == status.command_complete ); /* Command Complete */ if( err ) { break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cWaitForCmdComp()" ); return err; } // end i2cWaitForBus // ------------------------------------------------------------------ // i2cReadStatusReg // ------------------------------------------------------------------ errlHndl_t i2cReadStatusReg ( TARGETING::Target * i_target, misc_args_t & i_args, status_reg_t & o_statusReg ) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cReadStatusReg()" ); do { // Read the status Reg err = i2cRegisterOp( DeviceFW::READ, i_target, &o_statusReg.value, I2C_REG_STATUS, i_args ); if( err ) { break; } TRACUCOMP(g_trac_i2c,"i2cReadStatusReg(): " INFO_MRK"status: 0x%016llx", o_statusReg.value ); // Check for Errors // Per the specification it is a requirement to check for errors each time // that the status register is read. err = i2cCheckForErrors( i_target, i_args, o_statusReg ); if( err ) { break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cReadStatusReg()" ); return err; } // end i2cReadStatusReg // ------------------------------------------------------------------ // i2cCheckForErrors // ------------------------------------------------------------------ errlHndl_t i2cCheckForErrors ( TARGETING::Target * i_target, misc_args_t & i_args, status_reg_t i_statusVal ) { errlHndl_t err = NULL; bool errorFound = false; bool nackFound = false; bool busArbiLostFound = false; uint64_t intRegVal = 0x0; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cCheckForErrors()" ); do { if( 1 == i_statusVal.invalid_cmd ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Invalid Command! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.lbus_parity_error ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Local Bus Parity Error! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.backend_overrun_error ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C BackEnd OverRun Error! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.backend_access_error ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C BackEnd Access Error! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.arbitration_lost_error ) { busArbiLostFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Arbitration Lost! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.nack_received ) { // Rather than using 'errorFound', use specific nackFound nackFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C NACK Received! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.stop_error ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C STOP Error! - status reg: %016llx", i_statusVal.value ); } if( 1 == i_statusVal.any_i2c_interrupt ) { errorFound = true; TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Interrupt Detected! - status reg: %016llx", i_statusVal.value ); // Get the Interrupt Register value to add to the log // - i2cGetInterrupts() adds intRegVal to trace, so it ill be added // to error log below err = i2cGetInterrupts( i_target, i_args, intRegVal ); if( err ) { break; } } if( errorFound ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCheckForErrors() - Error(s) found" ); /*@ * @errortype * @reasoncode I2C_HW_ERROR_FOUND * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_CHECK_FOR_ERRORS * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc Error was found in I2C status register. * Check userdata to determine what the error was. * @custdesc A problem occurred during the IPL of the system: * An error was found in the I2C status register. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_CHECK_FOR_ERRORS, I2C_HW_ERROR_FOUND, I2C_SET_USER_DATA_1( i_statusVal, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME ); break; } else if ( nackFound ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCheckForErrors() - NACK found (only error)" ); /*@ * @errortype * @reasoncode I2C_NACK_ONLY_FOUND * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_CHECK_FOR_ERRORS * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc a NACK Error was found in the I2C status * register. * @custdesc A problem occurred during the IPL of the system: * A NACK error was found in the I2C Status * register. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_CHECK_FOR_ERRORS, I2C_NACK_ONLY_FOUND, I2C_SET_USER_DATA_1( i_statusVal, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME ); break; } else if( busArbiLostFound ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cCheckForErrors() - Bus Arbitration Lost (only error)"); /*@ * @errortype * @reasoncode I2C_ARBITRATION_LOST_ONLY_FOUND * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_CHECK_FOR_ERRORS * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc Bus Arbitration Lost Error was found in * the I2C status register. * @custdesc A problem occurred during the IPL of the system: * A Bus Arbitration Lost error was found * in the I2C status register. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_CHECK_FOR_ERRORS, I2C_ARBITRATION_LOST_ONLY_FOUND, I2C_SET_USER_DATA_1( i_statusVal, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME ); break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cCheckForErrors()" ); return err; } // end i2cCheckForErrors // ------------------------------------------------------------------ // i2cWaitForFifoSpace // ------------------------------------------------------------------ errlHndl_t i2cWaitForFifoSpace ( TARGETING::Target * i_target, misc_args_t & i_args ) { errlHndl_t err = NULL; // Use Local Variables (timeoutCount gets derecmented) uint64_t interval_ns = i_args.polling_interval_ns; uint64_t timeoutCount = i_args.timeout_count; // Define regs we'll be using status_reg_t status; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cWaitForFifoSpace()" ); do { // Read Status reg to get available FIFO bytes status.value = 0x0ull; err = i2cReadStatusReg( i_target, i_args, status ); if( err ) { TRACFCOMP( g_trac_i2c, "Errored out at i2cReadStatusReg: statusreg = %016llx", status.value); break; } TRACUCOMP( g_trac_i2c, "i2cWaitForFifoSpace(): status=0x%016llx " "I2C_MAX_FIFO_CAPACITY=%d, status.fifo_entry_count=%d", status.value, I2C_MAX_FIFO_CAPACITY, status.fifo_entry_count); // 2 Conditions to wait on: // 1) FIFO is full // 2) Data Request bit is not set while( (I2C_MAX_FIFO_CAPACITY <= status.fifo_entry_count) && (0 == status.data_request) ) { // FIFO is full, wait before writing any data nanosleep( 0, interval_ns ); status.value = 0x0ull; err = i2cReadStatusReg( i_target, i_args, status ); if( err ) { break; } if( 0 == timeoutCount-- ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cWrite() - Timed out waiting for space in FIFO!" ); /*@ * @errortype * @reasoncode I2C_FIFO_TIMEOUT * @severity ERRL_SEV_UNRECOVERABLE * @moduleid I2C_WRITE * @userdata1[0:31] Status Register Value * @userdata1[32:63] Master Target * @userdata2[0:7] Master Engine * @userdata2[8:15] Master Port * @userdata2[16:31] Slave Device Address * @userdata2[32:47] Bus Speed * @userdata2[48:63] Bit Rate Devisor * @devdesc Timed out waiting for space to write into FIFO. */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_WRITE, I2C_FIFO_TIMEOUT, I2C_SET_USER_DATA_1( status, i_target), I2C_SET_USER_DATA_2(i_args)); addHwCalloutsI2c(err, i_target, i_args); // Or HB code failed to do the procedure correctly err->addProcedureCallout(HWAS::EPUB_PRC_HB_CODE, HWAS::SRCI_PRIORITY_LOW); err->collectTrace( I2C_COMP_NAME, 256); break; } } if( err ) { break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cWaitForFifoSpace()" ); return err; } // end i2cWaitForFifoSpace // ------------------------------------------------------------------ // i2cSendStopSignal // ------------------------------------------------------------------ errlHndl_t i2cSendStopSignal(TARGETING::Target * i_target, misc_args_t & i_args) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cSendStopSignal" ); do { residual_length_reg_t clkdataline; clkdataline.value = 0x0; //manually send stop signal // set clock low: write 0 to immediate reset scl register TRACUCOMP(g_trac_i2c,"i2cSendStopSignal" "clock line 0x%016llx", clkdataline.value ); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkdataline.value, I2C_REG_RESET_SCL, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C reset SCL register Failed!!" ); break; } //set data low: write 0 to immediate reset sda register err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkdataline.value, I2C_REG_RESET_SDA, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C reset SDA register Failed!!" ); break; } // set clock high: write 0 to immediate set scl register err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkdataline.value, I2C_REG_SET_SCL, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C set SCL register Failed!!" ); break; } //set data high: write 0 to immediate set sda register err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkdataline.value, I2C_REG_SET_SDA, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C set SDA register Failed!!" ); break; } }while(0); return err; }//end i2cSendStopSignal // ------------------------------------------------------------------ // i2cToggleClockLine // ------------------------------------------------------------------ errlHndl_t i2cToggleClockLine(TARGETING::Target * i_target, misc_args_t & i_args) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cToggleClockLine()" ); do { residual_length_reg_t clkline; clkline.value = 0x0; //toggle clock line // set clock low: write 0 to immediate reset scl register TRACUCOMP(g_trac_i2c,"i2cToggleClockLine()" "clock line 0x%016llx", clkline.value ); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkline.value, I2C_REG_RESET_SCL, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C reset SCL register Failed!!" ); break; } // set clock high: write 0 to immediate set scl register err = i2cRegisterOp( DeviceFW::WRITE, i_target, &clkline.value, I2C_REG_SET_SCL, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C set SCL register Failed!!" ); break; } }while(0); return err; }//end i2cToggleClockLine // ------------------------------------------------------------------ // i2cForceResetAndUnlock // ------------------------------------------------------------------ errlHndl_t i2cForceResetAndUnlock( TARGETING::Target * i_target, misc_args_t & i_args) { errlHndl_t err = NULL; mode_reg_t mode; uint64_t l_speed = I2C_BUS_SPEED_FROM_MRW; // I2C Bus Speed Array TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type speed_array; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cForceResetAndUnlock()" ); do { // Get I2C Bus Speed Array attribute. It will be used to determine // which engine/port combinations have devices on them if ( !( i_target->tryGetAttr (speed_array) ) ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cForceResetAndUnlock() - Cannot find " "ATTR_I2C_BUS_SPEED_ARRAY needed for operation"); /*@ * @errortype * @reasoncode I2C_ATTRIBUTE_NOT_FOUND * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_FORCE_RESET_AND_UNLOCK * @userdata1 Target for the attribute * @userdata2 * @devdesc ATTR_I2C_BUS_SPEED_ARRAY not found * @custdesc I2C configuration data missing */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_FORCE_RESET_AND_UNLOCK, I2C_ATTRIBUTE_NOT_FOUND, TARGETING::get_huid(i_target), 0x0, true /*Add HB SW Callout*/ ); err->collectTrace( I2C_COMP_NAME, 256); break; } uint32_t l_numPorts = I2C_BUS_ATTR_MAX_PORT; if (i_args.switches.useFsiI2C == 1) { TRACDCOMP( g_trac_i2c,INFO_MRK "Using FSI I2C, use numports: %d", FSI_MODE_MAX_PORT); l_numPorts = FSI_MODE_MAX_PORT; } // Need to send slave stop to all ports with a device on the engine for( uint32_t port = 0; port < l_numPorts; port++ ) { //Check if diag mode should be skipped TARGETING::TYPE l_type = i_target->getAttr(); if (l_type == TARGETING::TYPE_PROC) { auto skipDiagMode = false; // P9 engine 2 port 0 has a limitation where the diag mode // cannot be used. Since diag mode restrictions are enforced at // the engine level, generalize this restriction to all ports on // that engine, skipping the diagnostic reset based on the // value of the attribute. const auto l_disable_diag_mode = i_target->getAttr< TARGETING::ATTR_DISABLE_I2C_ENGINE2_PORT0_DIAG_MODE>(); if ( (l_disable_diag_mode) && (2 == i_args.engine)) // Host { skipDiagMode = true; } // The FSI accessible I2C master on non-master P9 processors // does not allow diagnostic mode when Secure Boot is enabled. // Note that because I2C is needed before presence detect, we // cannot check the security state of the processor, so we use // the master secure mode as a proxy. However, we are unable // to assume that the processors' secure access bits (SABs) // all match early in the IPL. Therefore we are disabling // diagnostic mode for all FSI-based resets. else if(i_args.switches.useFsiI2C) // FSI engine 0 { skipDiagMode = true; } if(skipDiagMode) { TRACFCOMP(g_trac_i2c, INFO_MRK "Not doing i2cForceResetAndUnlock() for " "target=0x%08X: e/p= %d/%d due to P9 diag mode " "limitations. Disable diag mode on e2 = %d, " "secure mode enabled = %d", TARGETING::get_huid(i_target), i_args.engine, port,l_disable_diag_mode, SECUREBOOT::enabled()); continue; } } size_t logical_engine = i_args.engine; size_t logical_port = port; if (i_args.switches.useFsiI2C == 1) { setLogicalFsiEnginePort(logical_engine, logical_port); } l_speed = speed_array[logical_engine][logical_port]; if ( l_speed == 0 ) { continue; } TRACFCOMP( g_trac_i2c, INFO_MRK"i2cForceResetAndUnlock() - Performing op on " "engine=%d, port=%d", i_args.engine, port); // Clear mode register mode.value = 0x0ull; // set port in mode register mode.port_num = port; // enable diagnostic mode in mode register mode.diag_mode = 0x1; err = i2cRegisterOp( DeviceFW::WRITE, i_target, &mode.value, I2C_REG_MODE, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Enable Diagnostic mode Failed!!" ); // We still need to reset the other ports on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } //toggle clock line err = i2cToggleClockLine( i_target, i_args ); if( err ) { // We still need to reset the other ports on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } //manually send stop signal err = i2cSendStopSignal( i_target, i_args ); if( err ) { // We still need to reset the other ports on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } // disable diagnostic mode in mode register mode.diag_mode = 0x0; err = i2cRegisterOp( DeviceFW::WRITE, i_target, &mode.value, I2C_REG_MODE, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C disable Diagnostic mode Failed!!" ); // We still need to reset the other ports on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } } // end of port for loop }while(0); return err; }// end i2cForceResetAndUnlock // ------------------------------------------------------------------ // i2cReset // ------------------------------------------------------------------ errlHndl_t i2cReset ( TARGETING::Target * i_target, misc_args_t & i_args, i2c_reset_level i_reset_level) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cReset()" ); // Writing to the Status Register does a full I2C reset. status_reg_t reset; do { reset.value = 0x0; err = i2cRegisterOp( DeviceFW::WRITE, i_target, &reset.value, I2C_REG_RESET, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"I2C Reset Failed!!" ); break; } //if the reset is a force unlock then we need to enable //diagnostic mode and toggle the clock and data lines //to manually reset the bus if( i_reset_level == FORCE_UNLOCK_RESET ) { err = i2cForceResetAndUnlock( i_target, i_args ); if( err ) { // We still want to send the slave stop command since the // initial reset completed above. // So just commit the log here and let the function continue. errlCommit( err, I2C_COMP_ID ); } } // Part of doing the I2C Master reset is also sending a stop // command to the slave device. err = i2cSendSlaveStop( i_target, i_args ); if( err ) { break; } } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cReset()" ); return err; } // end i2cReset // ------------------------------------------------------------------ // i2cSendSlaveStop // ------------------------------------------------------------------ errlHndl_t i2cSendSlaveStop ( TARGETING::Target * i_target, misc_args_t & i_args) { errlHndl_t err = NULL; // Master Registers mode_reg_t mode; command_reg_t cmd; status_reg_t status_reg; uint64_t l_speed = I2C_BUS_SPEED_FROM_MRW; // I2C Bus Speed Array TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type speed_array; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cSendSlaveStop()" ); do { // Get I2C Bus Speed Array attribute. It will be used to determine // which engine/port combinations have devices on them if ( !( i_target->tryGetAttr (speed_array) ) ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cSendSlaveStop() - Cannot find " "ATTR_I2C_BUS_SPEED_ARRAY needed for operation"); /*@ * @errortype * @reasoncode I2C_ATTRIBUTE_NOT_FOUND * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_SEND_SLAVE_STOP * @userdata1 Target for the attribute * @userdata2 * @devdesc ATTR_I2C_BUS_SPEED_ARRAY not found * @custdesc I2C configuration data missing */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_SEND_SLAVE_STOP, I2C_ATTRIBUTE_NOT_FOUND, TARGETING::get_huid(i_target), 0x0, true /*Add HB SW Callout*/ ); err->collectTrace( I2C_COMP_NAME, 256); break; } uint32_t l_numPorts = I2C_BUS_ATTR_MAX_PORT; if (i_args.switches.useFsiI2C == 1) { TRACDCOMP( g_trac_i2c,INFO_MRK "Using FSI I2C, use numports: %d", FSI_MODE_MAX_PORT); l_numPorts = FSI_MODE_MAX_PORT; } // Need to send slave stop to all ports with a device on the engine for( uint32_t port = 0; port < l_numPorts; port++ ) { // Only send stop to a port if there are devices on it l_speed = speed_array[i_args.engine][port]; if ( l_speed == 0 ) { continue; } // TODO RTC 178394: Investigate XSCOM errros and improve i2c // presence detect here to remove this workaround if ( (i_args.engine == 1) && (port >= 4) ) { TRACDCOMP( g_trac_i2c, "Saw Errors resetting these devices, temporarily skipping engine: %d, port: %d", i_args.engine, port); continue; } mode.value = 0x0ull; mode.port_num = port; mode.enhanced_mode = 1; // Need this to set bit_rate_divisor err = i2cSetBusVariables ( i_target, l_speed, i_args ); if( err ) { // We still need to send the slave stop to the other ports // on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } mode.bit_rate_div = i_args.bit_rate_divisor; TRACUCOMP(g_trac_i2c,"i2cSendSlaveStop(): " "mode: 0x%016llx", mode.value ); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &mode.value, I2C_REG_MODE, i_args ); if( err ) { // We still need to send the slave stop to the other ports // on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } // Look for clock line (SCL) to be high // such that the 'stop' command will work status_reg.value = 0x0ull; size_t delay_ns = 0; for ( ; delay_ns <= I2C_RESET_POLL_DELAY_TOTAL_NS; delay_ns += I2C_RESET_POLL_DELAY_NS) { err = i2cRegisterOp( DeviceFW::READ, i_target, &status_reg.value, I2C_REG_STATUS, i_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"Reading I2C Status Reg Failed!!" ); break; } if (status_reg.scl_input_level != 0) { break; } // Sleep before polling again nanosleep( 0, I2C_RESET_POLL_DELAY_NS ); } if ( err ) { // We still need to send the slave stop to the other ports // on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } if ( delay_ns > I2C_RESET_POLL_DELAY_TOTAL_NS ) { // We don't see clock line high; in this case // it's not likely for a slave stop command to work. One // possible nasty side-effect of attempting slave stop is the // I2C master can become indefinitely busy and prevent writes // to the mode register from completing. Just continue to the // next port. TRACFCOMP( g_trac_i2c, ERR_MRK"i2cSendSlaveStop(): " "Not seeing SCL (%d) high " "after %d ns of polling (max=%d). " "Full status register = 0x%.16llX. " "Inhibiting sending slave stop to e%/p% for " "HUID 0x%08X.", status_reg.scl_input_level, delay_ns, I2C_RESET_POLL_DELAY_TOTAL_NS, status_reg.value, i_args.engine, port, TARGETING::get_huid(i_target)); continue; } cmd.value = 0x0ull; cmd.with_stop = 1; TRACUCOMP(g_trac_i2c,"i2cSendSlaveStop(): " "cmd: 0x%016llx", cmd.value ); err = i2cRegisterOp( DeviceFW::WRITE, i_target, &cmd.value, I2C_REG_COMMAND, i_args ); if( err ) { // We still need to send the slave stop to the other ports // on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } // Now wait for cmd Complete err = i2cWaitForCmdComp( i_target, i_args ); if( err ) { // We still need to send the slave stop to the other ports // on this I2C engine errlCommit( err, I2C_COMP_ID ); continue; } } // end of port for-loop } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cSendSlaveStop()" ); return err; } // end i2cSendSlaveStop // ------------------------------------------------------------------ // i2cGetInterrupts // ------------------------------------------------------------------ errlHndl_t i2cGetInterrupts ( TARGETING::Target * i_target, misc_args_t & i_args, uint64_t & o_intRegValue ) { errlHndl_t err = NULL; // Master Regs interrupt_reg_t intreg; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cGetInterrupts()" ); do { intreg.value = 0x0; err = i2cRegisterOp( DeviceFW::READ, i_target, &intreg.value, I2C_REG_INTERRUPT, i_args ); if( err ) { break; } TRACUCOMP(g_trac_i2c,"i2cGetInterrupts(): " "interrupt: 0x%016llx", intreg.value ); // Return the data read o_intRegValue = intreg.value; } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cGetInterrupts( int reg val: %016llx)", o_intRegValue ); return err; } // end i2cGetInterrupts #define I2C_CALCULATE_BRD( i_clockSpeed ) ( i_clockSpeed / 37 ) // #define BRD_1024 = ( ( nest_freq / ( 16 * 1024 * KILOBYTE ) ) - 1 ) / 4; // ------------------------------------------------------------------ // i2cSetBusVariables // ------------------------------------------------------------------ errlHndl_t i2cSetBusVariables ( TARGETING::Target * i_target, uint64_t i_speed, misc_args_t & io_args) { errlHndl_t err = NULL; TRACUCOMP( g_trac_i2c, ENTER_MRK"i2cSetBusVariables(): i_speed=%d", i_speed ); do { if ( i_speed == I2C_BUS_SPEED_FROM_MRW ) { // Read data from attributes set by MRW TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type speed_array; if ( ( !( i_target->tryGetAttr (speed_array) ) ) || ( io_args.engine >= I2C_BUS_ATTR_MAX_ENGINE ) || ( io_args.port >= I2C_BUS_ATTR_MAX_PORT ) ) { // Default to 400KHz TRACFCOMP( g_trac_i2c, ERR_MRK"i2cSetBusVariables: " "unable to get TARGETING::ATTR_I2C_BUS_SPEED_ARRAY " "or invalid engine(%d)/port(%d) combo. " "Defaulting to 400KHz.", io_args.engine, io_args.port); io_args.bus_speed = I2C_BUS_SPEED_400KHZ; } else { io_args.bus_speed = speed_array[io_args.engine][io_args.port]; assert(io_args.bus_speed, "i2cSetBusVariables: bus_speed array[%d][%d] for " "tgt 0x%X is 0", io_args.engine, io_args.port, TARGETING::get_huid(i_target)); } } else { io_args.bus_speed = i_speed; } // Set other variables based off of io_args.bus_speed io_args.polling_interval_ns = i2cGetPollingInterval(io_args.bus_speed); io_args.timeout_count = I2C_TIMEOUT_COUNT(io_args.polling_interval_ns); // The Bit-Rate-Divisor set in the I2C Master mode register needs // to know the frequency of the "local bus" serving as a reflock // for the I2C Master uint64_t local_bus_MHZ = 0; if ( io_args.switches.useFsiI2C == 1 ) { // @todo RTC 117560 - verify correct frequency local_bus_MHZ = g_I2C_NEST_FREQ_MHZ; } else { // For Host I2C use Nest Frequency local_bus_MHZ = g_I2C_NEST_FREQ_MHZ; } io_args.bit_rate_divisor = i2cGetBitRateDivisor(io_args.bus_speed, local_bus_MHZ); } while( 0 ); TRACUCOMP(g_trac_i2c,"i2cSetBusVariables(): tgt=0x%X, e/p/dA=%d/%d/0x%X: " "speed=%d: b_sp=%d, b_r_d=0x%x, p_i=%d, to_c = %d", TARGETING::get_huid(i_target), io_args.engine, io_args.port, io_args.devAddr, i_speed, io_args.bus_speed, io_args.bit_rate_divisor, io_args.polling_interval_ns, io_args.timeout_count); TRACUCOMP( g_trac_i2c, EXIT_MRK"i2cSetBusVariables()" ); return err; } /** * @brief This function will handle everything required to process * each I2C master engine based on the input argements. * List of operations in the i2cProcessOperation internal enum. */ enum i2cProcessOperation { I2C_OP_RESET = 1, I2C_OP_SETUP = 2, }; errlHndl_t i2cProcessActiveMasters ( i2cProcessType i_processType, i2cProcessOperation i_processOperation, uint64_t i_busSpeed, bool i_functional, i2cEngineSelect i_engineSelect ) { errlHndl_t err = NULL; bool error_found = false; bool mutex_success = false; mutex_t * engineLock = NULL; bool mutex_needs_unlock = false; misc_args_t io_args; // I2C Bus Speed Array TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type speed_array; TRACFCOMP( g_trac_i2c, ENTER_MRK"i2cProcessActiveMasters(): Type=0x%X " "Operation=%d Bus Speed=%d Functional=%d engineSelect=0x%.2X", i_processType, i_processOperation, i_busSpeed, i_functional, i_engineSelect ); do { // Get list of Procs TARGETING::TargetHandleList procList; if ( i_processType & I2C_RANGE_PROC ) { // Pass input parameter for function (true) or existing (false) TARGETING::getAllChips(procList, TARGETING::TYPE_PROC, i_functional); if( 0 == procList.size() ) { TRACFCOMP(g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: No Processor chips found!"); } TRACFCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: I2C Master Procs: %d", procList.size() ); } // Get list of Membufs TARGETING::TargetHandleList membufList; if ( i_processType & I2C_RANGE_MEMBUF ) { // Pass input parameter for function (true) or existing (false) TARGETING::getAllChips(membufList, TARGETING::TYPE_MEMBUF, i_functional); if( 0 == membufList.size() ) { TRACFCOMP(g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: No Membuf chips found!"); } TRACFCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: I2C Master Membufs: %d", membufList.size() ); } // Combine lists into chipList TARGETING::TargetHandleList chipList; chipList.insert(chipList.end(), procList.begin(), procList.end()); chipList.insert(chipList.end(), membufList.begin(), membufList.end()); // Get the Master Proc Chip Target for comparisons later TARGETING::TargetService& tS = TARGETING::targetService(); TARGETING::Target* masterProcChipTargetHandle = NULL; err = tS.queryMasterProcChipTargetHandle(masterProcChipTargetHandle); assert((err == NULL), "tS.queryMasterProcChipTargetHandle returned " "err for masterProcChipTargetHandle"); // Process each chip/target for( size_t chip = 0; chip < chipList.size(); chip++ ) { TARGETING::Target* tgt = chipList[chip]; TRACUCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: Loop for tgt=0x%X", TARGETING::get_huid(tgt) ); // Look up I2C Mode for the target io_args.switches.useHostI2C = 0; io_args.switches.useFsiI2C = 0; i2cSetSwitches( tgt, io_args ); // Compare mode with input parameter if ( !( i_processType & I2C_RANGE_HOST ) && ( io_args.switches.useHostI2C == 1 ) ) { TRACUCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: skipping tgt=0x%X " "due to i_processType=%d, useHostI2C=%d", TARGETING::get_huid(tgt), i_processType, io_args.switches.useHostI2C ); continue; } if ( !( i_processType & I2C_RANGE_FSI ) && ( io_args.switches.useFsiI2C == 1 ) ) { TRACUCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: skipping tgt=0x%X " "due to i_processType=%d, useFsiI2C=%d", TARGETING::get_huid(tgt), i_processType, io_args.switches.useFsiI2C ); continue; } // Get I2C Bus Speed Array attribute. It will be used to // determine which engines have devices on them if ( !(tgt->tryGetAttr (speed_array) ) ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cProcessActiveMasters: - Cannot find " "ATTR_I2C_BUS_SPEED_ARRAY needed for operation"); /*@ * @errortype * @reasoncode I2C_ATTRIBUTE_NOT_FOUND * @severity ERRORLOG_SEV_UNRECOVERABLE * @moduleid I2C_PROCESS_ACTIVE_MASTERS * @userdata1 Target for the attribute * @userdata2[0:31] Operation * @userdata2[32:64] Type * @devdesc ATTR_I2C_BUS_SPEED_ARRAY not found * @custdesc I2C configuration data missing */ err = new ERRORLOG::ErrlEntry( ERRORLOG::ERRL_SEV_UNRECOVERABLE, I2C_PROCESS_ACTIVE_MASTERS, I2C_ATTRIBUTE_NOT_FOUND, TARGETING::get_huid(tgt), TWO_UINT32_TO_UINT64( i_processOperation, i_processType), true /*Add HB SW Callout*/ ); err->collectTrace( I2C_COMP_NAME, 256); // We still need to reset the other I2C engines errlCommit( err, I2C_COMP_ID ); continue; } // if i_functional == false then all possible returned in chipList, // so need to check if each target is present // -- master target defaulted to present if ( ( i_functional == false ) && ( tgt != masterProcChipTargetHandle ) ) { bool check = FSI::isSlavePresent(tgt); if ( check == false ) { TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: skipping tgt=0x%X " "due to FSI::isSlavePresent returned=%s (%d)", TARGETING::get_huid(tgt), check ? "true" : "false", check ); continue; } else { TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: keeping tgt=0x%X due " "to FSI::isSlavePresent returned=%s (%d)", TARGETING::get_huid(tgt), check ? "true" : "false", check ); } } for( uint8_t engine = 0; engine < I2C_BUS_ATTR_MAX_ENGINE; engine++ ) { io_args.engine = engine; // Only reset engine 0 for FSI if ( (i_processOperation & I2C_OP_RESET ) && ( engine != 0 ) && (io_args.switches.useFsiI2C == 1) ) { TRACDCOMP( g_trac_i2c,INFO_MRK "Only reset engine 0 for FSI"); continue; } // Never touch engine 0 for Host -- the SBE owns it if ( ( engine == 0 ) && (io_args.switches.useHostI2C == 1) ) { TRACDCOMP( g_trac_i2c,INFO_MRK "Never touch engine 0 for Host"); continue; } // Only operate on selected engines if ( ! ( i2cEngineToEngineSelect(engine) & i_engineSelect ) ) { TRACFCOMP( g_trac_i2c,INFO_MRK "Skipping engine %d because i_engineSelect=0x%.2X", engine, i_engineSelect ); continue; } // Look for any device on this engine based on speed_array bool skip = true; size_t l_numPorts = I2C_BUS_ATTR_MAX_PORT; if (io_args.switches.useFsiI2C == 1) { TRACDCOMP( g_trac_i2c,INFO_MRK "Using FSI I2c, use numports: %d", FSI_MODE_MAX_PORT); l_numPorts = FSI_MODE_MAX_PORT; } for ( size_t j = 0; j < l_numPorts; j++ ) { size_t logical_engine = engine; size_t logical_port = j; if (io_args.switches.useFsiI2C == 1) { setLogicalFsiEnginePort(logical_engine, logical_port); } if ( speed_array[logical_engine][logical_port] != 0 ) { skip = false; io_args.port = j; // use this port break; } } // PHYP wants all of the engines set regardless if hostboot // believes there is a device on the bus. if ( i_processOperation == I2C_OP_SETUP ) { skip = false; io_args.port = 0; } if ( skip == true ) { TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: no devices found on " "tgt=0x%X engine=%d", TARGETING::get_huid(tgt), engine ); continue; } else { TRACFCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: Reset/Setup tgt=0x%X " "engine=%d", TARGETING::get_huid(tgt), engine ); } error_found = false; mutex_needs_unlock = false; // Get the mutex for the requested engine mutex_success = i2cGetEngineMutex( tgt, io_args, engineLock ); if( !mutex_success ) { TRACUCOMP( g_trac_i2c, ERR_MRK"Error in i2cProcessActiveMasters: " "i2cGetEngineMutex() failed to get mutex. " "skipping reset on tgt=0x%X engine =%d", TARGETING::get_huid(tgt), engine ); continue; } // Lock on this engine TRACUCOMP( g_trac_i2c, INFO_MRK"i2cProcessActiveMasters: Obtaining lock for " "engine: %d", engine ); (void)mutex_lock( engineLock ); mutex_needs_unlock = true; TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: Locked on engine: %d", engine ); TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: Setup/Reset " "0x%X engine = %d", TARGETING::get_huid(tgt), engine ); // Setup Bus Speed err = i2cSetBusVariables ( tgt, i_busSpeed, io_args ); if( err ) { error_found = true; TRACFCOMP( g_trac_i2c,ERR_MRK "i2cProcessActiveMasters: Error Setting Bus " "Variables: tgt=0x%X engine=%d", TARGETING::get_huid(tgt), engine ); // If we get error skip resetting this target, but still // need to reset other I2C engines errlCommit( err, I2C_COMP_ID ); // Don't continue or break - need mutex unlock } // Now reset or setup the engine/bus if ( error_found == false ) { switch (i_processOperation) { case I2C_OP_RESET: { TRACFCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: reset engine: %d", engine ); err = i2cReset ( tgt, io_args, FORCE_UNLOCK_RESET); if( err ) { TRACFCOMP( g_trac_i2c,ERR_MRK "i2cProcessActiveMasters: Error reseting " "tgt=0x%X, engine=%d", TARGETING::get_huid(tgt), engine); // If we get errors on the reset, we still need // to reset the other I2C engines errlCommit( err, I2C_COMP_ID ); // Don't continue or break - need mutex unlock } break; } case I2C_OP_SETUP: { // Check that engine is in a good state - this // function looks for errors and that all previous // commands are complete err = i2cWaitForCmdComp(tgt, io_args ); if( err ) { TRACFCOMP(g_trac_i2c, ERR_MRK "i2cProcessActiveMasters: Error from " "i2cWaitForCmdComp tgt=0x%X, engine=%d. " "Will reset", TARGETING::get_huid(tgt), engine); // Reset to recover the engine errlHndl_t err_reset = NULL; err_reset = i2cReset ( tgt, io_args, FORCE_UNLOCK_RESET); if( err_reset ) { TRACFCOMP( g_trac_i2c,ERR_MRK "i2cProcessActiveMasters: Error reseting" " tgt=0x%X, engine=%d", TARGETING::get_huid(tgt), engine); // commit reset error and previous error // with the same plid err_reset->plid(err->plid()); TRACFCOMP(g_trac_i2c, "i2cProcessActiveMasters: comitting err" "(eid=0x%X) and err_reset(eid=0x%X) " "with plid 0x%X", err->eid(), err_reset->eid(), err->plid()); errlCommit( err_reset, I2C_COMP_ID ); errlCommit( err, I2C_COMP_ID ); // Don't continue or break-need mutex unlock } else { // The reset recovered the engine, so // just delete the original error log TRACFCOMP(g_trac_i2c, "i2cProcessActiveMasters: Reset worked " "so deleting previous err " "eid=0x%X and plid=0x%X", err->eid(), err->plid()); delete err; err = NULL; } } // Set Mode Register mode_reg_t mode; mode.value = 0x0; TRACFCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: setup engine: %d", engine ); mode.bit_rate_div = io_args.bit_rate_divisor; err = i2cRegisterOp( DeviceFW::WRITE, tgt, &mode.value, I2C_REG_MODE, io_args ); if( err ) { TRACFCOMP( g_trac_i2c, ERR_MRK"i2cProcessActiveMasters:" " Error setting mode for" " Processor, engine: %d", engine ); // If we get errors on these reads, // we still need to continue // to program the I2C Bus Divisor for the rest errlCommit( err, I2C_COMP_ID ); } break; } default: assert (0,"i2cProcessActiveMasters: " "invalid operation"); } } // Check if we need to unlock the mutex if ( mutex_needs_unlock == true ) { // Unlock (void) mutex_unlock( engineLock ); TRACUCOMP( g_trac_i2c,INFO_MRK "i2cProcessActiveMasters: Unlocked engine: %d", engine ); } } // end for-loop engine } // end for-loop chip } while( 0 ); TRACFCOMP( g_trac_i2c, EXIT_MRK"i2cProcessActiveMasters(): err rc=0x%X, plid=0x%X", ERRL_GETRC_SAFE(err), ERRL_GETPLID_SAFE(err)); return err; } /** * @brief This function is a wrapper to the common routine to reset * I2C engines selected based on the input argements. * Set bus speed from the MRW value. */ errlHndl_t i2cResetActiveMasters ( i2cProcessType i_resetType, bool i_functional, i2cEngineSelect i_engineSelect ) { errlHndl_t err = NULL; TRACFCOMP( g_trac_i2c, ENTER_MRK"i2cResetActiveMasters(): i2cProcessType=0x%X, " "i_functional=%d, i_engineSelect=0x%.2X", i_resetType, i_functional, i_engineSelect ); err = i2cProcessActiveMasters (i_resetType, // select engines I2C_OP_RESET, // reset engines I2C_BUS_SPEED_FROM_MRW, i_functional, i_engineSelect); TRACFCOMP( g_trac_i2c, EXIT_MRK"i2cResetActiveMasters(): err rc=0x%X, plid=0x%X", ERRL_GETRC_SAFE(err), ERRL_GETPLID_SAFE(err)); return err; } /** * @brief This function is a wrapper to the common routine to setup * I2C engines selected based on the input argements. * Set bus speed 400KHZ for Phyp (and other payloads). */ errlHndl_t i2cSetupActiveMasters ( i2cProcessType i_setupType, bool i_functional ) { errlHndl_t err = NULL; TRACFCOMP( g_trac_i2c, ENTER_MRK"i2cSetupActiveMasters(): i2cProcessType=0x%X, " "i_functional=%d", i_setupType, i_functional ); err = i2cProcessActiveMasters (i_setupType, // select engines I2C_OP_SETUP, // setup engines I2C_BUS_SPEED_400KHZ, i_functional, I2C_ENGINE_SELECT_ALL); TRACFCOMP( g_trac_i2c, EXIT_MRK"i2cSetupActiveMasters(): err rc=0x%X, plid=0x%X", ERRL_GETRC_SAFE(err), ERRL_GETPLID_SAFE(err)); return err; } // ------------------------------------------------------------------ // i2cSetAccessMode // NOTE: currently just supporting I2C_SET_MODE_PROC_HOST // ------------------------------------------------------------------ void i2cSetAccessMode( i2cSetAccessModeType i_setModeType ) { TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cSetAccessMode(): %d", i_setModeType ); TARGETING::I2cSwitches switches; bool mutex_success = false; mutex_t * engineLocks[I2C_BUS_ATTR_MAX_ENGINE]; misc_args_t args; do { // Get list of targets TARGETING::TargetHandleList tgtList; // Support for I2C_SET_MODE_PROC_HOST TARGETING::getAllChips(tgtList, TARGETING::TYPE_PROC, true); // true: return functional targets if( 0 == tgtList.size() ) { TRACFCOMP(g_trac_i2c, INFO_MRK"i2cSetAccessMode: No Targets found!"); break; } TRACUCOMP( g_trac_i2c, INFO_MRK"i2cSetAccessMode: Targets: %d", tgtList.size() ); // Initalize mutex array for ( size_t index = 0; index < I2C_BUS_ATTR_MAX_ENGINE; index++ ) { engineLocks[index] = NULL; } // Check and set each target for( size_t i = 0; i < tgtList.size(); i++ ) { TARGETING::Target* tgt = tgtList[i]; // Get the mutex for all engines for ( size_t engine = 0; engine < I2C_BUS_ATTR_MAX_ENGINE; engine++ ) { args.engine = engine; engineLocks[engine] = NULL; mutex_success = i2cGetEngineMutex( tgt, args, engineLocks[engine] ); if( !mutex_success ) { TRACFCOMP( g_trac_i2c,ERR_MRK"i2cSetAccessMode: Error from " "i2cGetEngineMutex() getting engine %d lock for " "tgt=0x%X", engine, TARGETING::get_huid(tgt)); break; } // Lock on this engine TRACUCOMP( g_trac_i2c, INFO_MRK"Obtaining lock for engine: %d", args.engine ); (void)mutex_lock( engineLocks[engine] ); TRACUCOMP( g_trac_i2c, INFO_MRK"Locked on engine: %d", args.engine ); } if ( mutex_success ) { // The target is locked so complete the operation switches = tgt->getAttr(); TRACUCOMP( g_trac_i2c,"i2cSetAccessMode: tgt=0x%X switches: " "host=%d, fsi=%d", TARGETING::get_huid(tgt), switches.useHostI2C, switches.useFsiI2C); // Support for I2C_SET_MODE_PROC_HOST if ((switches.useHostI2C != 1) || (switches.useFsiI2C != 0) ) { switches.useHostI2C = 1; switches.useFsiI2C = 0; tgt->setAttr(switches); TRACFCOMP( g_trac_i2c,"i2cSetAccessMode: tgt=0x%X " "I2C_SWITCHES updated: host=%d, fsi=%d", TARGETING::get_huid(tgt), switches.useHostI2C, switches.useFsiI2C); } } // Unlock the engines for ( size_t engine = 0; engine < I2C_BUS_ATTR_MAX_ENGINE; engine++ ) { args.engine = engine; if ( engineLocks[engine] != NULL ) { (void) mutex_unlock( engineLocks[engine] ); TRACUCOMP( g_trac_i2c, INFO_MRK"Unlocked engine: %d", args.engine ); } } } // end of target for loop } while( 0 ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cSetAccessMode"); return; } // ------------------------------------------------------------------ // i2cRegisterOp // ------------------------------------------------------------------ errlHndl_t i2cRegisterOp ( DeviceFW::OperationType i_opType, TARGETING::Target * i_target, uint64_t * io_data_64, i2c_reg_offset_t i_reg, misc_args_t & i_args ) { errlHndl_t err = NULL; TRACDCOMP( g_trac_i2c, ENTER_MRK"i2cRegisterOp()"); uint64_t op_addr = 0x0; uint64_t op_size = 0x0; // in bytes do { // Calculate Register Address and data size based on access type if ( i_args.switches.useHostI2C == 1 ) { op_addr = I2C_HOST_MASTER_BASE_ADDR + i_reg + (i_args.engine * P9_ENGINE_SCOM_OFFSET); op_size=8; err = DeviceFW::deviceOp( i_opType, i_target, io_data_64, op_size, DEVICE_SCOM_ADDRESS(op_addr) ); } else // i_args.switches.useFsiI2C == 1 { // FSI addresses are at 1-byte offsets, so need to multiply the // i_reg offset by 4 since each I2C register is 4 bytes long. op_addr = I2C_FSI_MASTER_BASE_ADDR + ( i_reg * 4 ); if ( i_reg == I2C_REG_FIFO ) { // Only read/write 1 byte at a time for FIFO register op_size = 1; } else { op_size = 4; } // Read or Write, this command should have the data left-justfied err = DeviceFW::deviceOp( i_opType, i_target, io_data_64, op_size, DEVICE_FSI_ADDRESS(op_addr)); } if ( err ) { TRACFCOMP(g_trac_i2c,"i2cRegisterOp %s FAIL!: plid=0X%X, rc=0x%X " "tgt=0x%X, reg=%d, addr=0x%.8X, " "data=0x%.16X", ( i_opType == DeviceFW::READ ) ? "read" : "write", err->plid(), err->reasonCode(), TARGETING::get_huid(i_target), i_reg, op_addr, (*io_data_64) ); } } while( 0 ); TRACUCOMP(g_trac_i2c,"i2cRegisterOp(%s): tgt=0x%X, h/f=%d/%d(%d) " "i_reg=%d, addr=0x%.8X, data=0x%.16X", ( i_opType == DeviceFW::READ ) ? "r" : "w", TARGETING::get_huid(i_target), i_args.switches.useHostI2C, i_args.switches.useFsiI2C, op_size, i_reg, op_addr, (*io_data_64) ); TRACDCOMP( g_trac_i2c, EXIT_MRK"i2cRegisterOp()" ); return err; } /** * @brief Return a set of information related to each I2C master on * the given target chip */ void getMasterInfo( const TARGETING::Target* i_chip, std::list& o_info ) { TRACFCOMP(g_trac_i2c,"getMasterInfo(%.8X)",TARGETING::get_huid(i_chip)); TARGETING::TYPE l_type = i_chip->getAttr(); for( uint32_t engine = 0; engine < I2C_BUS_ATTR_MAX_ENGINE; engine++ ) { // Only support engine 0 for centaur if ( ( engine != 0 ) && ( l_type == TARGETING::TYPE_MEMBUF ) ) { continue; } MasterInfo_t info; //For P9, the base scom address for each i2c engine //can be computed by adding an offset of 0x1000 each time info.scomAddr = 0x000A0000 + engine*P9_ENGINE_SCOM_OFFSET; info.engine = engine; info.freq = i2cGetNestFreq()*FREQ_CONVERSION::HZ_PER_MHZ; // PIB_CLK = NEST_FREQ /4 // Local Bus = PIB_CLK / 4 info.freq = info.freq/16; //convert nest to local bus TRACFCOMP(g_trac_i2c,"getMasterInfo(%.8X): pushing back engine=%d, scomAddr=0x%X",TARGETING::get_huid(i_chip), engine, info.scomAddr); o_info.push_back(info); } } //****************************************************************************** // areI2cDevicesLogicallyEqual (std::unique equality comparison) //****************************************************************************** bool areI2cDevicesLogicallyEqual( const DeviceInfo_t& i_lhs, const DeviceInfo_t& i_rhs) { return (i_lhs.masterChip == i_rhs.masterChip) && (i_lhs.engine == i_rhs.engine) && (i_lhs.masterPort == i_rhs.masterPort) && (i_lhs.addr == i_rhs.addr) && (i_lhs.slavePort == i_rhs.slavePort); } //****************************************************************************** // byI2cDeviceOrder (std::sort comparison function) //****************************************************************************** bool byI2cDeviceOrder( const DeviceInfo_t& i_lhs, const DeviceInfo_t& i_rhs) { bool lhsLogicallyBeforeRhs = (i_lhs.masterChip < i_rhs.masterChip); if(i_lhs.masterChip == i_rhs.masterChip) { lhsLogicallyBeforeRhs = (i_lhs.engine < i_rhs.engine); if(i_lhs.engine == i_rhs.engine) { lhsLogicallyBeforeRhs = (i_lhs.masterPort < i_rhs.masterPort); if(i_lhs.masterPort == i_rhs.masterPort) { lhsLogicallyBeforeRhs = (i_lhs.addr < i_rhs.addr); if(i_lhs.addr == i_rhs.addr) { lhsLogicallyBeforeRhs = (i_lhs.slavePort < i_rhs.slavePort); } } } } return lhsLogicallyBeforeRhs; } //****************************************************************************** // removeI2CDeviceDuplicates //****************************************************************************** void removeI2cDeviceDuplicates(std::vector& io_deviceInfo) { std::vector l_unique_deviceInfo; // Begin by sorting the list // Order I2C devices by chip, engine, port, address, slave port std::sort(io_deviceInfo.begin(), io_deviceInfo.end(), byI2cDeviceOrder); // Build up new unique list (thus removing duplicates) if (io_deviceInfo.size() > 1) { auto currentItr = io_deviceInfo.begin(); auto nextItr = currentItr + 1; do { if (nextItr != io_deviceInfo.end() && (currentItr != NULL)) { if (areI2cDevicesLogicallyEqual(*currentItr, *nextItr)) { // skip if first letter is ?, these are guessed defaults if (currentItr->deviceLabel[0] == '?') { // don't save currentItr as it is a guessed default currentItr = nextItr; } } else { // Save currentItr as nextItr isn't the same logical device if (currentItr != NULL) { l_unique_deviceInfo.push_back(*currentItr); } currentItr = nextItr; } } else { // Save currentItr if pointing at something valid if (currentItr != NULL) { l_unique_deviceInfo.push_back(*currentItr); currentItr = nextItr; } } if (nextItr != io_deviceInfo.end()) { ++nextItr; } } while (currentItr != io_deviceInfo.end()); io_deviceInfo = l_unique_deviceInfo; } } /** * Retrieve some information about I2C devices that the Host * needs to know about */ void getDeviceInfo( TARGETING::Target* i_i2cMaster, std::vector& o_deviceInfo ) { TRACFCOMP(g_trac_i2c,"getDeviceInfo>>"); TARGETING::TargetHandleList chipTargets; if(i_i2cMaster == nullptr) { // If no target specified, use every proc / membuf chip TARGETING::Target* pSys = nullptr; TARGETING::targetService().getTopLevelTarget(pSys); assert(pSys != nullptr,"System target was nullptr"); TARGETING::PredicateCTM procChip( TARGETING::CLASS_CHIP,TARGETING::TYPE_PROC); TARGETING::PredicateCTM membufChip( TARGETING::CLASS_CHIP,TARGETING::TYPE_MEMBUF); TARGETING::PredicatePostfixExpr procOrMembuf; procOrMembuf.push(&procChip).push(&membufChip).Or(); TARGETING::targetService().getAssociated( chipTargets, pSys, TARGETING::TargetService::CHILD, TARGETING::TargetService::ALL, &procOrMembuf); } else { // Otherwise, use the input target chipTargets.push_back(i_i2cMaster); } for(auto pChipTarget : chipTargets) { // If target is a processor, find its upstream node; if a membuf, find // both the upstream processor and node TARGETING::Target* pProc = nullptr; if(pChipTarget->getAttr() == TARGETING::TYPE_PROC) { pProc = pChipTarget; } else { TARGETING::TargetHandleList affinityParentTargets; TARGETING::getParentAffinityTargets ( affinityParentTargets, pChipTarget, TARGETING::CLASS_CHIP, TARGETING::TYPE_PROC, false); // Chip like TPM might not have upstream processor if(affinityParentTargets.empty()) { continue; } assert(affinityParentTargets.size() == 1, "Exactly one affinity parent expected, not %d", affinityParentTargets.size()); pProc = affinityParentTargets[0]; } auto assocProc = pProc->getAttr(); assert(assocProc <= UINT8_MAX,"Proc position exceeded max for uint8_t"); TARGETING::TargetHandleList affinityParentTargets; TARGETING::getParentAffinityTargets ( affinityParentTargets, pProc, TARGETING::CLASS_ENC, TARGETING::TYPE_NODE, false); assert(affinityParentTargets.size() == 1, "Exactly one affinity parent expected, not %d", affinityParentTargets.size()); TARGETING::Target* pNode = affinityParentTargets[0]; auto assocNode = pNode->getAttr(); assert(assocNode <= UINT8_MAX,"Node position exceeded max for uint8_t"); //Get list of all I2C Masters std::list l_i2cInfo; I2C::getMasterInfo( pChipTarget, l_i2cInfo ); //Find all the EEPROMs connected via i2c std::list l_eepromInfo; EEPROM::getEEPROMs( l_eepromInfo ); //Find all TPMs #ifdef CONFIG_TPMDD TPMDD::tpm_info_t tpmInfo; errlHndl_t l_err = NULL; TARGETING::TargetHandleList tpmList; TRUSTEDBOOT::getTPMs(tpmList); #endif //Find all NVDIMMs #ifdef CONFIG_NVDIMM TARGETING::ATTR_MODEL_type l_chipModel = pProc->getAttr(); std::list l_nvdimmInfo; if (l_chipModel != TARGETING::MODEL_CUMULUS) { NVDIMM::getNVDIMMs( l_nvdimmInfo ); } #endif for(auto const& i2cm : l_i2cInfo) { TRACDCOMP(g_trac_i2c,"i2c loop - eng=%.8X", TARGETING::get_huid(pChipTarget)); /* I2C Busses */ std::list::iterator l_eep = l_eepromInfo.begin(); while( l_eep != l_eepromInfo.end() ) { TRACDCOMP(g_trac_i2c,"eeprom loop - eng=%.8X, port=%.8X", TARGETING::get_huid(l_eep->i2cMaster), l_eep->engine ); DeviceInfo_t l_currentDI; //ignore the devices that aren't on the current target if( l_eep->i2cMaster != pChipTarget ) { TRACDCOMP(g_trac_i2c,"skipping unmatched i2cmaster"); l_eep = l_eepromInfo.erase(l_eep); continue; } //skip the devices that are on a different engine else if( l_eep->engine != i2cm.engine) { TRACDCOMP(g_trac_i2c,"skipping umatched engine"); ++l_eep; continue; } l_currentDI.assocNode = assocNode; l_currentDI.assocProc = assocProc; l_currentDI.masterChip = l_eep->i2cMaster; l_currentDI.engine = l_eep->engine; l_currentDI.masterPort = l_eep->port; l_currentDI.addr = l_eep->devAddr; l_currentDI.slavePort = 0xFF; l_currentDI.busFreqKhz = (l_eep->busFreq) / FREQ_CONVERSION::HZ_PER_KHZ; l_currentDI.deviceType = TARGETING::HDAT_I2C_DEVICE_TYPE_SEEPROM; switch(l_eep->device) { case EEPROM::VPD_PRIMARY: case EEPROM::VPD_BACKUP: l_currentDI.devicePurpose = TARGETING::HDAT_I2C_DEVICE_PURPOSE_MODULE_VPD; //TODO RTC:165485 this isn't currently right. we'll need //to add the changes in the enum and possibly the other //struct/attribute. strcpy(l_currentDI.deviceLabel, "?atmel,28c128,vpd,module"); break; case EEPROM::SBE_PRIMARY: case EEPROM::SBE_BACKUP: l_currentDI.devicePurpose = TARGETING::HDAT_I2C_DEVICE_PURPOSE_SBE_SEEPROM; strcpy(l_currentDI.deviceLabel, "?atmel,28c128,unknown,unknown"); break; case EEPROM::LAST_CHIP_TYPE: break; } TRACDCOMP(g_trac_i2c,"Adding addr=%X", l_eep->devAddr); o_deviceInfo.push_back(l_currentDI); l_eep = l_eepromInfo.erase(l_eep); } //end of eeprom iter #ifdef CONFIG_TPMDD for(auto pTpm : tpmList) { DeviceInfo_t l_currentDI; TPMDD::tpm_locality_t locality = TPMDD::TPM_LOCALITY_0; // Lookup i2c info for the TPM l_err = TPMDD::tpmReadAttributes(pTpm, tpmInfo, locality); if( NULL != l_err ) { // Unable to get info, so we skip delete l_err; l_err = nullptr; continue; } // ignore the devices that aren't on the current target if( tpmInfo.i2cTarget != pChipTarget ) { continue; } // skip the devices that are on a different engine else if( tpmInfo.engine != i2cm.engine ) { continue; } l_currentDI.assocNode = assocNode; l_currentDI.assocProc = assocProc; l_currentDI.masterChip = tpmInfo.i2cTarget; l_currentDI.engine = tpmInfo.engine; l_currentDI.masterPort = tpmInfo.port; l_currentDI.addr = tpmInfo.devAddr; l_currentDI.slavePort = 0xFF; l_currentDI.busFreqKhz = (tpmInfo.busFreq) / FREQ_CONVERSION::HZ_PER_KHZ; l_currentDI.deviceType = TARGETING::HDAT_I2C_DEVICE_TYPE_NUVOTON_TPM; l_currentDI.devicePurpose = TARGETING::HDAT_I2C_DEVICE_PURPOSE_TPM; strcpy(l_currentDI.deviceLabel,"?nuvoton,npct601,tpm,host"); o_deviceInfo.push_back(l_currentDI); } //end of tpm iter #endif #ifdef CONFIG_NVDIMM for (auto& l_nv : l_nvdimmInfo) { TRACDCOMP(g_trac_i2c,"nvdimm loop - eng=%.8X, port=%.8X", TARGETING::get_huid(l_nv.i2cMaster), l_nv.engine ); DeviceInfo_t l_currentDI; //ignore the devices that aren't on the current target if( l_nv.i2cMaster != pChipTarget ) { TRACDCOMP(g_trac_i2c,"skipping unmatched i2cmaster"); continue; } //skip the devices that are on a different engine else if( l_nv.engine != i2cm.engine) { TRACDCOMP(g_trac_i2c,"skipping umatched engine"); continue; } l_currentDI.assocNode = assocNode; l_currentDI.assocProc = assocProc; l_currentDI.masterChip = l_nv.i2cMaster; l_currentDI.engine = l_nv.engine; l_currentDI.masterPort = l_nv.port; l_currentDI.addr = l_nv.devAddr; l_currentDI.slavePort = 0xFF; l_currentDI.busFreqKhz = (l_nv.busFreq) / FREQ_CONVERSION::HZ_PER_KHZ; strcpy(l_currentDI.deviceLabel, "?unknown,unknown,nvdimm,nvdimm"); TRACDCOMP(g_trac_i2c,"Adding addr=%X", l_nv.devAddr); o_deviceInfo.push_back(l_currentDI); } //end of nvdimm iter #endif } //end of i2cm #if CONFIG_INCLUDE_XML_OPENPOWER TARGETING::ATTR_HDAT_I2C_ELEMENTS_type l_arrayLength = 0; auto present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_ELEMENTS>(l_arrayLength); if(!present || l_arrayLength == 0) { // The arrays are non-existent or empty continue; } // Assume all required attributes are present from this point TARGETING::ATTR_HDAT_I2C_ENGINE_type l_i2cEngine = {0}; present = pChipTarget->tryGetAttr( l_i2cEngine); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_ENGINE " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_MASTER_PORT_type l_i2cMasterPort = {0}; present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_MASTER_PORT>(l_i2cMasterPort); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_MASTER_PORT " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_DEVICE_TYPE_type l_i2cDevType; memset(&l_i2cDevType,TARGETING::HDAT_I2C_DEVICE_TYPE_UNKNOWN, sizeof(l_i2cDevType)); present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_DEVICE_TYPE>(l_i2cDevType); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_DEVICE_TYPE " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_ADDR_type l_i2cAddr = {0}; present = pChipTarget->tryGetAttr( l_i2cAddr); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_ADDR " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_SLAVE_PORT_type l_i2cSlavePort = {0}; present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_SLAVE_PORT>(l_i2cSlavePort); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_SLAVE_PORT " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_BUS_FREQ_type l_i2cBusFreq = {0}; present = pChipTarget->tryGetAttr( l_i2cBusFreq); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_BUS_FREQ " "attribute",TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_DEVICE_PURPOSE_type l_i2cDevPurpose; memset(&l_i2cDevPurpose,TARGETING::HDAT_I2C_DEVICE_PURPOSE_UNKNOWN, sizeof(l_i2cDevPurpose)); present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_DEVICE_PURPOSE>(l_i2cDevPurpose); assert(present,"Target 0x%08X does not have " "ATTR_HDAT_I2C_DEVICE_PURPOSE attribute", TARGETING::get_huid(pChipTarget)); TARGETING::ATTR_HDAT_I2C_DEVICE_LABEL_type l_i2cDevLabel; present = pChipTarget->tryGetAttr< TARGETING::ATTR_HDAT_I2C_DEVICE_LABEL>(l_i2cDevLabel); assert(present,"Target 0x%08X does not have ATTR_HDAT_I2C_DEVICE_LABEL " "attribute",TARGETING::get_huid(pChipTarget)); for(TARGETING::ATTR_HDAT_I2C_ELEMENTS_type l_idx=0; l_idx < l_arrayLength; ++l_idx) { if( ( pChipTarget->getAttr() == TARGETING::TYPE_PROC) && (l_i2cEngine[l_idx] == 0)) { // We never expose proc engine 0 devices to host, since they are // owned by SBE continue; } if(l_i2cAddr[l_idx] == UINT8_MAX) { continue; } DeviceInfo_t l_currentDevice = {nullptr}; l_currentDevice.assocNode = assocNode; l_currentDevice.assocProc = assocProc; l_currentDevice.masterChip = pChipTarget; l_currentDevice.engine = l_i2cEngine[l_idx]; l_currentDevice.masterPort = l_i2cMasterPort[l_idx]; l_currentDevice.addr = l_i2cAddr[l_idx]; l_currentDevice.slavePort = l_i2cSlavePort[l_idx]; l_currentDevice.busFreqKhz = l_i2cBusFreq[l_idx] / FREQ_CONVERSION::HZ_PER_KHZ; l_currentDevice.deviceType = static_cast( l_i2cDevType[l_idx]); l_currentDevice.devicePurpose = static_cast( l_i2cDevPurpose[l_idx]); memcpy(l_currentDevice.deviceLabel, l_i2cDevLabel[l_idx], sizeof(l_currentDevice.deviceLabel)); l_currentDevice.deviceLabel[sizeof(l_currentDevice.deviceLabel) - 1] = '\0'; o_deviceInfo.push_back(l_currentDevice); } #endif } //end of per chip loop // remove duplicates (also use deviceLabel from MRW, when possible) removeI2cDeviceDuplicates(o_deviceInfo); TRACFCOMP(g_trac_i2c,"<> 32), TARGETING::get_huid(tgt) ); } /** * @brief Utility Function to capture error log user data consisting of * the I2C variables relating to the I2C Master */ uint64_t I2C_SET_USER_DATA_2 ( misc_args_t args) { return FOUR_UINT16_TO_UINT64( TWO_UINT8_TO_UINT16 (args.engine, args.port), args.devAddr & 0x000000000000FFFF, args.bus_speed & 0x000000000000FFFF, args.bit_rate_divisor ); } void setLogicalFsiEnginePort(size_t &io_logical_engine, size_t &io_logical_port) { assert( (io_logical_engine == 0), "setLogicalFsiEnginePort not intended for engines other than 0." " Engine passed in: %d", io_logical_engine); switch (io_logical_port) { case (13): { io_logical_engine = 3; io_logical_port = 0; break; } case (14): { io_logical_engine = 3; io_logical_port = 1; break; } default: { io_logical_engine = 1; break; } } } void addHwCalloutsI2c(errlHndl_t i_err, TARGETING::Target * i_target, const misc_args_t & i_args) { assert(i_err != nullptr, "Bug! The supplied error log is nullptr."); assert(i_target != nullptr, "Bug! The supplied target is nullptr."); if (!INITSERVICE::spBaseServicesEnabled()) { i_err->addI2cDeviceCallout(i_target, i_args.engine, i_args.port, i_args.devAddr, HWAS::SRCI_PRIORITY_HIGH); } // For FSP systems which don't yet have special handling for // i2c device callouts we still need to handle the TPM search // to avoid regression back to the "non TPM aware" behavior. else { // Loop thru TPMs in the system and match physical path, // engine, and port to the i2c master auto l_devFound = false; const auto l_physPath = i_target->getAttr< TARGETING::ATTR_PHYS_PATH>(); TARGETING::TargetHandleList allTpms; TARGETING::getAllChips( allTpms, TARGETING::TYPE_TPM, false ); for(const auto &tpm: allTpms) { const auto l_tpmInfo = tpm->getAttr< TARGETING::ATTR_TPM_INFO>(); if (l_tpmInfo.i2cMasterPath == l_physPath && l_tpmInfo.engine == i_args.engine && l_tpmInfo.port == i_args.port && l_tpmInfo.devAddrLocality0 == i_args.devAddr) { TRACFCOMP(g_trac_i2c, "Unresponsive TPM found: " "Engine=%d, masterPort=%d " "huid for its i2c master is 0x%.8X", l_tpmInfo.engine, l_tpmInfo.port, TARGETING::get_huid(i_target)); i_err->addHwCallout(tpm, HWAS::SRCI_PRIORITY_HIGH, HWAS::NO_DECONFIG, HWAS::GARD_NULL); l_devFound = true; break; } } // Could also be an issue with Processor or its bus // -- both on the same FRU i_err->addHwCallout( i_target, l_devFound? HWAS::SRCI_PRIORITY_MED: HWAS::SRCI_PRIORITY_HIGH, HWAS::NO_DECONFIG, HWAS::GARD_NULL ); } } }; // end namespace I2C