Atmosphere/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.cpp
2019-05-02 19:32:03 -07:00

507 lines
16 KiB
C++

/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_bus_accessor.hpp"
#include "boot_pcv.hpp"
void I2cBusAccessor::Open(I2cBus bus, SpeedMode speed_mode) {
std::scoped_lock<HosMutex> lk(this->open_mutex);
/* Open new session. */
this->open_sessions++;
/* Ensure we're good if this isn't our first session. */
if (this->open_sessions > 1) {
if (this->speed_mode != speed_mode) {
std::abort();
}
return;
}
/* Set all members for chosen bus. */
{
std::scoped_lock<HosMutex> lk(this->register_mutex);
/* Set bus/registers. */
this->SetBus(bus);
/* Set pcv module. */
switch (bus) {
case I2cBus_I2c1:
this->pcv_module = PcvModule_I2C1;
break;
case I2cBus_I2c2:
this->pcv_module = PcvModule_I2C2;
break;
case I2cBus_I2c3:
this->pcv_module = PcvModule_I2C3;
break;
case I2cBus_I2c4:
this->pcv_module = PcvModule_I2C4;
break;
case I2cBus_I2c5:
this->pcv_module = PcvModule_I2C5;
break;
case I2cBus_I2c6:
this->pcv_module = PcvModule_I2C6;
break;
default:
std::abort();
}
/* Set speed mode. */
this->speed_mode = speed_mode;
/* Setup interrupt event. */
this->CreateInterruptEvent(bus);
}
}
void I2cBusAccessor::Close() {
std::scoped_lock<HosMutex> lk(this->open_mutex);
/* Close current session. */
this->open_sessions--;
if (this->open_sessions > 0) {
return;
}
/* Close interrupt event. */
eventClose(&this->interrupt_event);
/* Close PCV. */
Pcv::Finalize();
this->suspended = false;
}
void I2cBusAccessor::Suspend() {
std::scoped_lock<HosMutex> lk(this->open_mutex);
std::scoped_lock<HosMutex> lk_reg(this->register_mutex);
if (!this->suspended) {
this->suspended = true;
if (this->pcv_module != PcvModule_I2C5) {
this->DisableClock();
}
}
}
void I2cBusAccessor::Resume() {
if (this->suspended) {
this->DoInitialConfig();
this->suspended = false;
}
}
void I2cBusAccessor::DoInitialConfig() {
std::scoped_lock<HosMutex> lk(this->register_mutex);
if (this->pcv_module != PcvModule_I2C5) {
Pcv::Initialize();
}
this->ResetController();
this->SetClock(this->speed_mode);
this->SetPacketMode();
this->FlushFifos();
}
size_t I2cBusAccessor::GetOpenSessions() const {
return this->open_sessions;
}
bool I2cBusAccessor::GetBusy() const {
/* Nintendo has a loop here that calls a member function to check if busy, retrying a few times. */
/* This member function does "return false". */
/* We will not bother with the loop. */
return false;
}
void I2cBusAccessor::OnStartTransaction() const {
/* Nothing actually happens here. */
}
void I2cBusAccessor::OnStopTransaction() const {
/* Nothing actually happens here. */
}
Result I2cBusAccessor::StartTransaction(DriverCommand command, AddressingMode addressing_mode, u32 slave_address) {
/* Nothing actually happens here... */
return ResultSuccess;
}
Result I2cBusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
std::scoped_lock<HosMutex> lk(this->register_mutex);
const u8 *cur_src = data;
size_t remaining = num_bytes;
Result rc;
/* Set interrupt enable, clear interrupt status. */
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8E);
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
ON_SCOPE_EXIT { this->ClearInterruptMask(); };
/* Send header. */
this->WriteTransferHeader(TransferMode_Send, option, addressing_mode, slave_address, num_bytes);
/* Send bytes. */
while (true) {
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
const size_t fifo_cnt = (fifo_status >> 4);
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
const size_t cur_bytes = std::min(remaining, sizeof(u32));
u32 val = 0;
for (size_t i = 0; i < cur_bytes; i++) {
val |= cur_src[i] << (8 * i);
}
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, val);
cur_src += cur_bytes;
remaining -= cur_bytes;
}
if (remaining == 0) {
break;
}
eventClear(&this->interrupt_event);
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
this->HandleTransactionResult(ResultI2cBusBusy);
eventClear(&this->interrupt_event);
return ResultI2cTimedOut;
}
if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
return rc;
}
}
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
/* Wait for successful completion. */
while (true) {
if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
return rc;
}
/* Check PACKET_XFER_COMPLETE */
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
if (interrupt_status & 0x80) {
if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
return rc;
}
break;
}
eventClear(&this->interrupt_event);
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
this->HandleTransactionResult(ResultI2cBusBusy);
eventClear(&this->interrupt_event);
return ResultI2cTimedOut;
}
}
return ResultSuccess;
}
Result I2cBusAccessor::Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
std::scoped_lock<HosMutex> lk(this->register_mutex);
u8 *cur_dst = out_data;
size_t remaining = num_bytes;
Result rc;
/* Set interrupt enable, clear interrupt status. */
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8D);
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
/* Send header. */
this->WriteTransferHeader(TransferMode_Receive, option, addressing_mode, slave_address, num_bytes);
/* Receive bytes. */
while (remaining > 0) {
eventClear(&this->interrupt_event);
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
this->HandleTransactionResult(ResultI2cBusBusy);
this->ClearInterruptMask();
eventClear(&this->interrupt_event);
return ResultI2cTimedOut;
}
if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
return rc;
}
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast<size_t>(fifo_status & 0xF));
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
const u32 val = ReadRegister(&this->i2c_registers->I2C_I2C_RX_FIFO_0);
const size_t cur_bytes = std::min(remaining, sizeof(u32));
for (size_t i = 0; i < cur_bytes; i++) {
cur_dst[i] = static_cast<u8>((val >> (8 * i)) & 0xFF);
}
cur_dst += cur_bytes;
remaining -= cur_bytes;
}
}
/* N doesn't do ClearInterruptMask. */
return ResultSuccess;
}
void I2cBusAccessor::SetBus(I2cBus bus) {
this->bus = bus;
this->i2c_registers = GetI2cRegisters(bus);
this->clkrst_registers.SetBus(bus);
}
void I2cBusAccessor::CreateInterruptEvent(I2cBus bus) {
static constexpr u64 s_interrupts[] = {
0x46, 0x74, 0x7C, 0x98, 0x55, 0x5F
};
if (static_cast<size_t>(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
std::abort();
}
Handle evt_h;
if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[static_cast<size_t>(bus)], 1))) {
std::abort();
}
eventLoadRemote(&this->interrupt_event, evt_h, false);
}
void I2cBusAccessor::SetClock(SpeedMode speed_mode) {
u32 t_high, t_low;
u32 clk_div, src_div;
u32 debounce;
switch (speed_mode) {
case SpeedMode_Normal:
t_high = 2;
t_low = 4;
clk_div = 0x19;
src_div = 0x13;
debounce = 2;
break;
case SpeedMode_Fast:
t_high = 2;
t_low = 4;
clk_div = 0x19;
src_div = 0x04;
debounce = 2;
break;
case SpeedMode_FastPlus:
t_high = 2;
t_low = 4;
clk_div = 0x10;
src_div = 0x02;
debounce = 0;
break;
case SpeedMode_HighSpeed:
t_high = 3;
t_low = 8;
clk_div = 0x02;
src_div = 0x02;
debounce = 0;
break;
default:
std::abort();
}
if (speed_mode == SpeedMode_HighSpeed) {
WriteRegister(&this->i2c_registers->I2C_I2C_HS_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, clk_div);
} else {
WriteRegister(&this->i2c_registers->I2C_I2C_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, (clk_div << 16));
}
WriteRegister(&this->i2c_registers->I2C_I2C_CNFG_0, debounce);
ReadRegister(&this->i2c_registers->I2C_I2C_CNFG_0);
if (this->pcv_module != PcvModule_I2C5) {
if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
std::abort();
}
if (R_FAILED(Pcv::SetClockRate(this->pcv_module, (408'000'000) / (src_div + 1)))) {
std::abort();
}
if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
std::abort();
}
}
}
void I2cBusAccessor::ResetController() const {
if (this->pcv_module != PcvModule_I2C5) {
if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
std::abort();
}
if (R_FAILED(Pcv::SetClockRate(this->pcv_module, 81'600'000))) {
std::abort();
}
if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
std::abort();
}
}
}
void I2cBusAccessor::ClearBus() const {
bool success = false;
for (size_t i = 0; i < 3 && !success; i++) {
success = true;
this->ResetController();
WriteRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x90000);
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x4);
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x2);
SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
{
u64 start_tick = armGetSystemTick();
while (ReadRegister(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0) & 1) {
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
success = false;
break;
}
}
}
if (!success) {
continue;
}
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x1);
{
u64 start_tick = armGetSystemTick();
while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0) & 1) {
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
success = false;
break;
}
}
}
if (!success) {
continue;
}
{
u64 start_tick = armGetSystemTick();
while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_STATUS_0) & 1) {
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
success = false;
break;
}
}
}
if (!success) {
continue;
}
}
}
void I2cBusAccessor::DisableClock() {
if (R_FAILED(Pcv::SetClockEnabled(this->pcv_module, false))) {
std::abort();
}
}
void I2cBusAccessor::SetPacketMode() {
/* Set PACKET_MODE_EN, MSTR_CONFIG_LOAD */
SetRegisterBits(&this->i2c_registers->I2C_I2C_CNFG_0, 0x400);
SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
/* Set TX_FIFO_TRIGGER, RX_FIFO_TRIGGER */
WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFC);
}
Result I2cBusAccessor::FlushFifos() {
WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFF);
/* Wait for flush to finish, check every ms for 5 ms. */
for (size_t i = 0; i < 5; i++) {
if (!(ReadRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0) & 3)) {
return ResultSuccess;
}
svcSleepThread(1'000'000ul);
}
return ResultI2cBusBusy;
}
Result I2cBusAccessor::GetTransactionResult() const {
const u32 packet_status = ReadRegister(&this->i2c_registers->I2C_PACKET_TRANSFER_STATUS_0);
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
/* Check for no ack. */
if ((packet_status & 0xC) || (interrupt_status & 0x8)) {
return ResultI2cNoAck;
}
/* Check for arb lost. */
if ((packet_status & 0x2) || (interrupt_status & 0x4)) {
this->ClearBus();
return ResultI2cBusBusy;
}
return ResultSuccess;
}
void I2cBusAccessor::HandleTransactionResult(Result result) {
if (R_FAILED(result)) {
if (result == ResultI2cNoAck || result == ResultI2cBusBusy) {
this->ResetController();
this->SetClock(this->speed_mode);
this->SetPacketMode();
this->FlushFifos();
} else {
std::abort();
}
}
}
Result I2cBusAccessor::GetAndHandleTransactionResult() {
Result rc = this->GetTransactionResult();
this->HandleTransactionResult(rc);
if (R_FAILED(rc)) {
this->ClearInterruptMask();
eventClear(&this->interrupt_event);
return rc;
}
return ResultSuccess;
}
void I2cBusAccessor::WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes) {
this->FlushFifos();
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, 0x10);
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, static_cast<u32>(num_bytes - 1) & 0xFFF);
const u32 slave_addr_val = ((transfer_mode == TransferMode_Receive) & 1) | ((slave_address & 0x7F) << 1);
u32 hdr_val = 0;
hdr_val |= ((this->speed_mode == SpeedMode_HighSpeed) & 1) << 22;
hdr_val |= ((transfer_mode == TransferMode_Receive) & 1) << 19;
hdr_val |= ((addressing_mode != AddressingMode_7Bit) & 1) << 18;
hdr_val |= (1 << 17);
hdr_val |= (((option & I2cTransactionOption_Stop) == 0) & 1) << 16;
hdr_val |= slave_addr_val;
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
}