| /* --COPYRIGHT--,BSD |
| * Copyright (c) 2023, Texas Instruments Incorporated |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * * Neither the name of Texas Instruments Incorporated nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
| * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * --/COPYRIGHT--*/ |
| |
| #include <stdint.h> |
| #include <ti/devices/msp/msp.h> |
| #include <ti/driverlib/driverlib.h> |
| #include <ti/driverlib/m0p/dl_core.h> |
| |
| |
| #include "smbus.h" |
| #include "smbus_nwk.h" |
| #include "smbus_phy.h" |
| |
| void SMBus_PHY_disable(SMBus *smbus) |
| { |
| DL_I2C_disableController(smbus->phy.SMBus_Phy_i2cBase); |
| smbus->ctrl.bits.phyEn = 0; // Update control flag |
| } |
| |
| void SMBus_PHY_enable(SMBus *smbus) |
| { |
| if(smbus->ctrl.bits.controller) |
| { |
| SMBus_PHY_controllerEnable(smbus); |
| } |
| else |
| { |
| SMBus_PHY_targetEnable(smbus); |
| } |
| } |
| |
| |
| void SMBus_PHY_targetEnable(SMBus *smbus) |
| { |
| // Make sure the PHY is disabled |
| SMBus_PHY_disable(smbus); |
| |
| // Write the Target Address |
| DL_I2C_setTargetOwnAddress(smbus->phy.SMBus_Phy_i2cBase, smbus->ownTargetAddr); |
| |
| // Clear all flags |
| DL_I2C_clearInterruptStatus(smbus->phy.SMBus_Phy_i2cBase, 0xFFFF); |
| |
| |
| if(smbus->ctrl.bits.intEn == 1) |
| { |
| SMBus_targetEnableInt(smbus); |
| } |
| |
| // Enable I2C module |
| DL_I2C_enableTarget(smbus->phy.SMBus_Phy_i2cBase); |
| smbus->ctrl.bits.phyEn = 1; // Set global flag |
| } |
| |
| void SMBus_PHY_targetEnableInt(SMBus *smbus) |
| { |
| DL_I2C_clearInterruptStatus(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_TARGET_START | |
| DL_I2C_INTERRUPT_TARGET_STOP | |
| DL_I2C_INTERRUPT_TARGET_TXFIFO_EMPTY); |
| DL_I2C_enableInterrupt(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_TARGET_START | |
| DL_I2C_INTERRUPT_TARGET_STOP | |
| DL_I2C_INTERRUPT_TARGET_TXFIFO_EMPTY); |
| |
| // Set global flag |
| smbus->ctrl.bits.intEn = 1; |
| } |
| |
| void SMBus_PHY_targetInit(SMBus *smbus, |
| I2C_Regs *i2cInst) |
| { |
| SMBus_Phy *SMBusPHY = &smbus->phy; |
| |
| // Set the I2C Base Address |
| smbus->phy.SMBus_Phy_i2cBase = i2cInst; |
| |
| // Enabling the I2C module is handled by SysConfig |
| } |
| |
| SMBus_State SMBus_PHY_targetProcessInt(SMBus *smbus) |
| { |
| SMBus_State ret_state = SMBus_State_Target_NTR; |
| SMBus_Phy *SMBusPHY = &smbus->phy; |
| uint8_t data, addr; |
| uint32_t i2cIntControllerFlags, i2cIntTargetFlags; |
| |
| /* |
| * Check Interrupt controller flags first. |
| * A SMBus target can act as a host in some scenarios (i.e. Host notify). |
| * In such cases, the target device needs to handle controller flags. |
| */ |
| i2cIntControllerFlags = DL_I2C_getEnabledInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_TX_DONE | |
| DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_NACK | |
| DL_I2C_INTERRUPT_CONTROLLER_START | |
| DL_I2C_INTERRUPT_CONTROLLER_STOP | |
| DL_I2C_INTERRUPT_CONTROLLER_ARBITRATION_LOST); |
| if (i2cIntControllerFlags) |
| { |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_TX_DONE) |
| { |
| SMBus_NWK_controllerTxDone(smbus); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER) |
| { |
| // TXIFG0 is set when the TX buffer is empty and we need to send data to |
| // target |
| ret_state = SMBus_NWK_controllerProcessTx(smbus, &data); |
| // Send the data |
| DL_I2C_fillControllerTXFIFO(SMBusPHY->SMBus_Phy_i2cBase, &data, 1); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_NACK) |
| { |
| ret_state = SMBus_NWK_controllerProcessNACK(smbus); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_STOP) |
| { |
| ret_state = SMBus_NWK_controllerProcessStop(smbus); |
| } |
| |
| DL_I2C_clearInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, i2cIntControllerFlags); |
| } |
| |
| |
| /* Attend Target Interrupt flags */ |
| i2cIntTargetFlags = DL_I2C_getEnabledInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_TARGET_START | |
| DL_I2C_INTERRUPT_TARGET_STOP | |
| DL_I2C_INTERRUPT_TARGET_TXFIFO_EMPTY | |
| DL_I2C_INTERRUPT_TARGET_PEC_RX_ERROR); |
| |
| if (i2cIntTargetFlags) |
| { |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER) |
| { |
| data = DL_I2C_receiveTargetData(SMBusPHY->SMBus_Phy_i2cBase); |
| ret_state = SMBus_NWK_targetProcessRx(smbus, data); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_START) |
| { |
| data = (uint8_t) DL_I2C_getTargetAddressMatch(SMBusPHY->SMBus_Phy_i2cBase); |
| ret_state = SMBus_NWK_targetProcessStart(smbus, data); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_STOP) |
| { |
| ret_state = SMBus_NWK_targetProcessStop(smbus); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_TXFIFO_EMPTY) |
| { |
| ret_state = SMBus_NWK_targetProcessTx(smbus, &data); |
| DL_I2C_fillTargetTXFIFO(SMBusPHY->SMBus_Phy_i2cBase, &data, 1); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_PEC_RX_ERROR) |
| { |
| smbus->status.bits.pecErr = 1; |
| ret_state = SMBus_State_PECError; |
| } |
| DL_I2C_clearInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, i2cIntTargetFlags); |
| } |
| return(ret_state); |
| } |
| |
| void SMBus_PHY_controllerEnable(SMBus *smbus) |
| { |
| // Make sure the PHY is disabled |
| SMBus_PHY_disable(smbus); |
| |
| DL_I2C_enableController(smbus->phy.SMBus_Phy_i2cBase); |
| |
| // Re-enable Interrupts since the Reset will clear them |
| if(smbus->ctrl.bits.intEn == 1) |
| { |
| SMBus_controllerEnableInt(smbus); |
| } |
| |
| smbus->ctrl.bits.phyEn = 1; // Set global flag |
| } |
| |
| void SMBus_PHY_controllerEnableInt(SMBus *smbus) |
| { |
| DL_I2C_clearInterruptStatus(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_NACK | |
| DL_I2C_INTERRUPT_CONTROLLER_ARBITRATION_LOST | |
| DL_I2C_INTERRUPT_CONTROLLER_START | |
| DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_STOP | |
| DL_I2C_INTERRUPT_TIMEOUT_A); |
| DL_I2C_enableInterrupt(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_NACK | |
| DL_I2C_INTERRUPT_CONTROLLER_ARBITRATION_LOST | |
| DL_I2C_INTERRUPT_CONTROLLER_START | |
| DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_STOP | |
| DL_I2C_INTERRUPT_TIMEOUT_A); |
| |
| // Set global flag |
| smbus->ctrl.bits.intEn = 1; |
| } |
| |
| void SMBus_PHY_controllerInit(SMBus *smbus, |
| I2C_Regs *i2cRegs, |
| uint32_t busClk) |
| { |
| SMBus_Phy *SMBusPHY = &smbus->phy; |
| smbus->phy.SMBus_Phy_i2cBase = i2cRegs; |
| |
| // Target Address not initialized by default |
| smbus->ownTargetAddr = 0; |
| |
| // Enabling the I2C module is handled by SysConfig |
| } |
| |
| void SMBus_PHY_controllerSendStop(SMBus *smbus) |
| { |
| DL_I2C_startControllerTransferAdvanced(smbus->phy.SMBus_Phy_i2cBase, |
| 0x0, // This is a don't care |
| DL_I2C_CONTROLLER_DIRECTION_TX, |
| 0, |
| DL_I2C_CONTROLLER_START_DISABLE, |
| DL_I2C_CONTROLLER_STOP_ENABLE, |
| DL_I2C_CONTROLLER_ACK_DISABLE); |
| } |
| |
| void SMBus_PHY_controllerStartTx(SMBus *smbus, |
| uint8_t targetaddr, |
| uint16_t length, |
| SMBus_Stop stopFlag, |
| uint8_t setPEC) |
| |
| { |
| uint8_t data; |
| DL_I2C_CONTROLLER_STOP phy_stop; |
| |
| if (stopFlag == SMBus_Stop_After_Transfer) |
| { |
| phy_stop = DL_I2C_CONTROLLER_STOP_ENABLE; |
| DL_I2C_disableInterrupt(smbus->phy.SMBus_Phy_i2cBase, DL_I2C_INTERRUPT_CONTROLLER_TX_DONE); |
| } |
| else |
| { |
| phy_stop = DL_I2C_CONTROLLER_STOP_DISABLE; |
| DL_I2C_clearInterruptStatus(smbus->phy.SMBus_Phy_i2cBase, DL_I2C_INTERRUPT_CONTROLLER_TX_DONE); |
| DL_I2C_enableInterrupt(smbus->phy.SMBus_Phy_i2cBase, DL_I2C_INTERRUPT_CONTROLLER_TX_DONE); |
| } |
| |
| |
| if (length > 0) { |
| DL_I2C_disableInterrupt(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER); |
| DL_I2C_flushControllerTXFIFO(smbus->phy.SMBus_Phy_i2cBase); |
| DL_I2C_clearInterruptStatus(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER); |
| DL_I2C_enableInterrupt(smbus->phy.SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER); |
| |
| SMBus_NWK_controllerProcessTx(smbus, &data); |
| DL_I2C_fillControllerTXFIFO(smbus->phy.SMBus_Phy_i2cBase, &data, 1); |
| } |
| |
| if (setPEC == 1 && smbus->ctrl.bits.pecEn == 1) { |
| DL_I2C_setControllerPECCountValue(smbus->phy.SMBus_Phy_i2cBase, length); |
| } |
| else { |
| DL_I2C_setControllerPECCountValue(smbus->phy.SMBus_Phy_i2cBase, 0); |
| } |
| |
| DL_I2C_startControllerTransferAdvanced(smbus->phy.SMBus_Phy_i2cBase, |
| targetaddr, |
| DL_I2C_CONTROLLER_DIRECTION_TX, |
| length, |
| DL_I2C_CONTROLLER_START_ENABLE, |
| phy_stop, |
| DL_I2C_CONTROLLER_ACK_ENABLE); |
| } |
| |
| void SMBus_PHY_controllerStartRx(SMBus *smbus, |
| uint8_t targetaddr, |
| uint16_t length, |
| SMBus_Start startFlag, |
| SMBus_Stop stopFlag, |
| SMBus_Auto_Ack ackFlag, |
| uint8_t setPEC) |
| { |
| DL_I2C_CONTROLLER_START phy_start; |
| DL_I2C_CONTROLLER_STOP phy_stop; |
| DL_I2C_CONTROLLER_ACK phy_ack; |
| uint8_t data; |
| |
| if (startFlag == SMBus_Start_Before_Transfer) |
| { |
| phy_start = DL_I2C_CONTROLLER_START_ENABLE; |
| } |
| else |
| { |
| phy_start = DL_I2C_CONTROLLER_START_DISABLE; |
| } |
| |
| if (stopFlag == SMBus_Stop_After_Transfer) |
| { |
| phy_stop = DL_I2C_CONTROLLER_STOP_ENABLE; |
| } |
| else |
| { |
| phy_stop = DL_I2C_CONTROLLER_STOP_DISABLE; |
| } |
| |
| if (ackFlag == SMBus_Auto_Ack_Last_Byte) |
| { |
| phy_ack = DL_I2C_CONTROLLER_ACK_ENABLE; |
| } |
| else |
| { |
| phy_ack = DL_I2C_CONTROLLER_ACK_DISABLE; |
| } |
| |
| if (setPEC == 1 && smbus->ctrl.bits.pecEn == 1) { |
| DL_I2C_setControllerPECCountValue(smbus->phy.SMBus_Phy_i2cBase, length); |
| } |
| else { |
| DL_I2C_setControllerPECCountValue(smbus->phy.SMBus_Phy_i2cBase, 0); |
| } |
| |
| // HW will ignore new transfer requests while a transfer is active, so wait until the HW is ready + 1 delay_cycle |
| // In normal operation this should just be a small region of time between when the HW has received a byte |
| // and while ack is being generated. |
| while ((DL_I2C_getControllerStatus(smbus->phy.SMBus_Phy_i2cBase) | DL_I2C_CONTROLLER_STATUS_BUSY) == DL_I2C_CONTROLLER_STATUS_BUSY ) |
| ; |
| |
| __NOP(); |
| |
| DL_I2C_startControllerTransferAdvanced(smbus->phy.SMBus_Phy_i2cBase, |
| targetaddr, |
| DL_I2C_CONTROLLER_DIRECTION_RX, |
| length, |
| phy_start, |
| phy_stop, |
| phy_ack); |
| |
| } |
| |
| void SMBus_PHY_controllerEnableHostNotify(SMBus *smbus) |
| { |
| DL_I2C_enableDefaultHostAddress(smbus->phy.SMBus_Phy_i2cBase); |
| } |
| |
| void SMBus_PHY_controllerDisableHostNotify(SMBus *smbus) |
| { |
| DL_I2C_disableDefaultHostAddress(smbus->phy.SMBus_Phy_i2cBase); |
| } |
| |
| |
| SMBus_State SMBus_PHY_controllerProcessInt(SMBus *smbus) |
| { |
| uint8_t data; |
| SMBus_State ret_state; |
| SMBus_Phy *SMBusPHY = &smbus->phy; |
| uint32_t i2cIntControllerFlags, i2cIntTimeoutFlags, i2cIntTargetFlags; |
| |
| /* Check Interrupt controller flags first */ |
| i2cIntControllerFlags = DL_I2C_getEnabledInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_CONTROLLER_TX_DONE | |
| DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_CONTROLLER_NACK | |
| DL_I2C_INTERRUPT_CONTROLLER_START | |
| DL_I2C_INTERRUPT_CONTROLLER_STOP | |
| DL_I2C_INTERRUPT_CONTROLLER_ARBITRATION_LOST); |
| |
| if (i2cIntControllerFlags) |
| { |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_TX_DONE) |
| { |
| SMBus_NWK_controllerTxDone(smbus); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER) |
| { |
| data = DL_I2C_receiveControllerData(SMBusPHY->SMBus_Phy_i2cBase); |
| // Pass data to NWK layer |
| ret_state = SMBus_NWK_controllerProcessRx(smbus, data); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER) |
| { |
| // TXIFG0 is set when the TX buffer is empty and we need to send data to |
| // target |
| ret_state = SMBus_NWK_controllerProcessTx(smbus, &data); |
| // Send the data |
| DL_I2C_fillControllerTXFIFO(SMBusPHY->SMBus_Phy_i2cBase, &data, 1); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_NACK) |
| { |
| ret_state = SMBus_NWK_controllerProcessNACK(smbus); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_START) |
| { |
| //__BKPT(0); /* Placeholder for debugging purposes */ |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_STOP) |
| { |
| ret_state = SMBus_NWK_controllerProcessStop(smbus); |
| } |
| if (i2cIntControllerFlags & DL_I2C_INTERRUPT_CONTROLLER_ARBITRATION_LOST) |
| { |
| //__BKPT(0); /* Placeholder for debugging purposes */ |
| } |
| |
| DL_I2C_clearInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, i2cIntControllerFlags); |
| } |
| |
| /* Timeout interrupt flags will be set with TimeoutA or TimeOutB */ |
| i2cIntTimeoutFlags = DL_I2C_getEnabledInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_TIMEOUT_A | |
| DL_I2C_INTERRUPT_TIMEOUT_B); |
| if (i2cIntTimeoutFlags) |
| { |
| ret_state = SMBus_NWK_controllerProcessTimeout(smbus); |
| DL_I2C_clearInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, i2cIntTimeoutFlags); |
| } |
| |
| /* Target Interrupt flags in some scenarios like Alert Notify */ |
| /* |
| * Check Interrupt target flags. |
| * A SMBus controller can act as a target in some scenarios (i.e. Host |
| * notify). In such cases, the controller device needs to handle target |
| * flags. |
| */ |
| i2cIntTargetFlags = DL_I2C_getEnabledInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, |
| DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER | |
| DL_I2C_INTERRUPT_TARGET_START | |
| DL_I2C_INTERRUPT_TARGET_STOP | |
| DL_I2C_INTERRUPT_TARGET_TXFIFO_EMPTY); |
| |
| if (i2cIntTargetFlags) |
| { |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER) |
| { |
| data = DL_I2C_receiveTargetData(SMBusPHY->SMBus_Phy_i2cBase); |
| ret_state = SMBus_NWK_targetProcessRx(smbus, data); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_START) |
| { |
| data = (uint8_t) DL_I2C_getTargetAddressMatch(SMBusPHY->SMBus_Phy_i2cBase); |
| ret_state = SMBus_NWK_targetProcessStart(smbus, data); |
| } |
| if (i2cIntTargetFlags & DL_I2C_INTERRUPT_TARGET_STOP) |
| { |
| ret_state = SMBus_NWK_targetProcessStop(smbus); |
| } |
| DL_I2C_clearInterruptStatus(SMBusPHY->SMBus_Phy_i2cBase, i2cIntTargetFlags); |
| } |
| |
| return(ret_state); |
| } |
| |
| |
| |