blob: 593a45400049e3dd0a7bff8dc025d6586ec9359a [file] [log] [blame] [edit]
/* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "ti_msp_dl_config.h"
#include "ti/smbus/smbus.h"
#include "log.h"
#include "error.h"
#include "stats.h"
#include "dolos_smbus.h"
#include "smart_battery.h"
#include "perf.h"
#include "dolos_timers.h"
#define SMBUS_CONTROLLER_CLK 32000000
#define SMBUS_CONTROLLER_TIMEOUT 10000
static SMBus smbus_controller_inst;
static SMBus smbus_target_inst;
static volatile SMBus_State smbus_controller_bus_state;
static uint8_t smbus_target_rx_buffer[SMB_MAX_PACKET_SIZE];
static uint8_t smbus_target_tx_buffer[SMB_MAX_PACKET_SIZE];
/* Communication state for DOLOS_SMBUS_TARGET, should be cleared if there's no communication for 10 seconds by
* DOLOS_SMBUS_COMMUNICATION_TIMER and set immediately when we receive an interrupt on the DOLOS_SMBUS_TARGET interface
*/
static bool target_communication_state = false;
void dsb_init(void)
{
DEBUG("Initializing SMBus controller and SMBus target");
/* Initiate SMBUS controller peripheral */
SMBus_controllerInit(&smbus_controller_inst, DOLOS_SMBUS_CONTROLLER_INST, SMBUS_CONTROLLER_CLK);
/* Initiate SMBUS target peripheral */
SMBus_targetInit(&smbus_target_inst, DOLOS_SMBUS_TARGET_INST);
SMBus_targetSetAddress(&smbus_target_inst, DOLOS_SMBUS_TARGET_TARGET_OWN_ADDR);
SMBus_targetSetRxBuffer(&smbus_target_inst, smbus_target_rx_buffer, sizeof(smbus_target_rx_buffer));
SMBus_targetSetTxBuffer(&smbus_target_inst, smbus_target_tx_buffer, sizeof(smbus_target_tx_buffer));
SMBus_targetEnableInt(&smbus_target_inst);
/* Enable NVIC interrupts for controller SMBus */
NVIC_ClearPendingIRQ(DOLOS_SMBUS_CONTROLLER_INST_INT_IRQN);
NVIC_EnableIRQ(DOLOS_SMBUS_CONTROLLER_INST_INT_IRQN);
/* Enable NVIC interrupts for target SMBus */
NVIC_ClearPendingIRQ(DOLOS_SMBUS_TARGET_INST_INT_IRQN);
NVIC_EnableIRQ(DOLOS_SMBUS_TARGET_INST_INT_IRQN);
}
static inline int dsb_controller_read_internal(uint8_t target_address, bool is_block, uint8_t command_byte,
uint8_t *rx_data, int rx_size, size_t *rx_len)
{
int8_t ret;
/* Send command to the target */
if (is_block) {
DEBUG("Read block target(%#x), command: %#x", rx_size, target_address, command_byte);
ret = SMBus_controllerReadBlock(&smbus_controller_inst, target_address, command_byte, rx_data);
} else {
DEBUG("Read %d target(%#x), command: %#x", rx_size, target_address, command_byte);
ret = SMBus_controllerReadByteWord(&smbus_controller_inst, target_address, command_byte, rx_data,
rx_size);
}
if (ret != SMBUS_RET_OK) {
ERROR("Failed to send command byte to target(%#x): %d", target_address, ret);
SMBus_controllerReset(&smbus_controller_inst);
return DOLOS_ERROR_SMBUS;
}
/* Wait until transfer is done, if it times out reset the controller */
ret = SMBus_controllerWaitUntilDone(&smbus_controller_inst, SMBUS_CONTROLLER_TIMEOUT);
if (ret != SMBUS_RET_OK) {
ERROR("Timeout while waiting after command byte from target(%#x): %d", target_address, ret);
SMBus_controllerReset(&smbus_controller_inst);
return DOLOS_ERROR_TIMEOUT;
}
/* If transfer is over, read available data on the SMBus line and store it in controller RX Buff */
if (smbus_controller_bus_state != SMBus_State_OK) {
ERROR("Target(%#x) not in correct state: %d", target_address, smbus_controller_bus_state);
SMBus_controllerReset(&smbus_controller_inst);
return DOLOS_ERROR_SMBUS;
}
*rx_len = SMBus_getRxPayloadAvailable(&smbus_controller_inst);
DEBUG("Successfully read %d bytes from target(%#x)", *rx_len, target_address);
return DOLOS_SUCCESS;
}
int dsb_controller_read_block(uint8_t target_address, uint8_t command_byte, uint8_t *rx_data, size_t *rx_len)
{
return dsb_controller_read_internal(target_address, true, command_byte, rx_data, 0, rx_len);
}
int dsb_controller_read_byte(uint8_t target_address, uint8_t command_byte, uint8_t *rx_data)
{
return dsb_controller_read_internal(target_address, false, command_byte, rx_data, 1, NULL);
}
int dsb_controller_read_word(uint8_t target_address, uint8_t command_byte, uint8_t *rx_data)
{
return dsb_controller_read_internal(target_address, false, command_byte, rx_data, 2, NULL);
}
int dsb_controller_write(uint8_t target_address, uint8_t command_byte, uint8_t *tx_data, int tx_size)
{
int ret;
if (tx_data) {
DEBUG("WriteByteWord:: sending command byte to target(%#x), command: %#x", tx_size, target_address,
command_byte);
ret = SMBus_controllerWriteByteWord(&smbus_controller_inst, target_address, command_byte, tx_data,
tx_size);
} else {
DEBUG("SendByte:: sending command byte to target(%#x), command: %#x", tx_size, target_address,
command_byte);
ret = SMBus_controllerSendByte(&smbus_controller_inst, target_address, command_byte);
}
if (ret != SMBUS_RET_OK) {
ERROR("Failed to send command byte to target(%#x): %d", target_address, ret);
SMBus_controllerReset(&smbus_controller_inst);
return DOLOS_ERROR_SMBUS;
}
/* Wait until transfer is done, if it times out reset the controller */
ret = SMBus_controllerWaitUntilDone(&smbus_controller_inst, SMBUS_CONTROLLER_TIMEOUT);
if (ret != SMBUS_RET_OK) {
ERROR("Timeout while waiting after command byte from target(%#x): %d", target_address, ret);
SMBus_controllerReset(&smbus_controller_inst);
return DOLOS_ERROR_TIMEOUT;
}
DEBUG("Wrote to target(%#x) to register %d", target_address, command_byte);
return DOLOS_SUCCESS;
}
/* smbus_controller_inst interrupt handler */
void DOLOS_SMBUS_CONTROLLER_INST_IRQHandler(void)
{
smbus_controller_bus_state = SMBus_controllerProcessInt(&smbus_controller_inst);
}
/** Handles SMBus read requests for smart battery registers.
*
* Find the value in the register array and write it in the smbus.
*/
static void dsb_handle_sb_smbus_read(SMBus *smbus, uint8_t cmd)
{
int written_bytes = 0;
written_bytes = sb_register_smbus_read_handler(cmd);
if (written_bytes == 0) {
stats.smbus_target_reg_read_failures++;
return;
}
stats.smbus_target_reg_read_success++;
stats.smbus_target_reg_read_bytes += written_bytes;
}
static void dsb_handle_sb_smbus_write(SMBus *smbus, uint8_t cmd)
{
int read_bytes = 0;
read_bytes = sb_register_smbus_write_handler(cmd);
if (read_bytes == 0) {
stats.smbus_target_reg_write_failures++;
return;
}
stats.smbus_target_reg_write_success++;
stats.smbus_target_reg_write_bytes += read_bytes;
}
void dsb_target_write_word(const uint8_t *data)
{
smbus_target_inst.nwk.txLen = 2;
smbus_target_inst.nwk.txBuffPtr[0] = data[0];
smbus_target_inst.nwk.txBuffPtr[1] = data[1];
}
void dsb_target_write_block(const uint8_t *data, const uint8_t length)
{
smbus_target_inst.nwk.txLen = length + 1;
smbus_target_inst.nwk.txBuffPtr[0] = length;
for (size_t i = 0; i < length; i++) {
smbus_target_inst.nwk.txBuffPtr[i + 1] = data[i];
}
}
void dsb_target_read_word(uint8_t *data)
{
data[0] = smbus_target_inst.nwk.rxBuffPtr[1];
data[1] = smbus_target_inst.nwk.rxBuffPtr[2];
}
void dsb_target_read_block(uint8_t *data, uint8_t *length)
{
*length = smbus_target_inst.nwk.rxBuffPtr[1];
for (size_t i = 0; i < *length; i++) {
data[i] = smbus_target_inst.nwk.rxBuffPtr[i + 2];
}
}
void dsb_target_reset(void)
{
DL_I2C_reset(DOLOS_SMBUS_TARGET_INST);
DL_I2C_disablePower(DOLOS_SMBUS_TARGET_INST);
DL_I2C_enablePower(DOLOS_SMBUS_TARGET_INST);
delay_cycles(POWER_STARTUP_DELAY);
SYSCFG_DL_DOLOS_SMBUS_TARGET_init();
/* A workaround for I2C CLK wedging issue */
DL_I2C_disableTargetWakeup(DOLOS_SMBUS_TARGET_INST);
}
void dsb_target_set_communication_state(bool state)
{
target_communication_state = state;
}
bool dsb_target_get_communication_state(void)
{
return target_communication_state;
}
static uint8_t last_rx_cmd;
/* smbus_target_inst interrupt handler */
void DOLOS_SMBUS_TARGET_INST_IRQHandler(void)
{
PERF_RECORD_START(irq_target_smbus);
/* Mark that communication was received on the target interface and reset the DOLOS_SMBUS_COMMUNICATON_TIMER */
target_communication_state = true;
dtimers_reset_smbus_communication_timer();
/* Handle target interrupts */
SMBus_State bus_state;
stats.smbus_target_int_count++;
bus_state = SMBus_targetProcessInt(&smbus_target_inst);
if (bus_state < SMBus_State_Unknown) {
stats.smbus_target_state_count[bus_state]++;
} else {
stats.smbus_target_state_count[SMBus_State_Unknown]++;
}
switch (bus_state) {
case SMBus_State_Target_FirstByte:
last_rx_cmd = SMBus_targetGetCommand(&smbus_target_inst);
break;
case SMBus_State_Target_CmdComplete:
if (smbus_target_inst.nwk.eState == SMBus_NwkState_TX_Resp) {
dsb_handle_sb_smbus_read(&smbus_target_inst, last_rx_cmd);
} else {
if (sb_is_register_read_only(last_rx_cmd)) {
break;
}
dsb_handle_sb_smbus_write(&smbus_target_inst, last_rx_cmd);
}
break;
default:
break;
}
SMBus_processDone(&smbus_target_inst);
PERF_RECORD_END(irq_target_smbus);
}