mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-08 14:54:11 +08:00
296 lines
7.1 KiB
C
296 lines
7.1 KiB
C
/*
|
|
* This file is part of libn3ds
|
|
* Copyright (C) 2024 derrek, profi200
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "types.h"
|
|
#include "arm11/drivers/i2c.h"
|
|
#include "kevent.h"
|
|
#include "kmutex.h"
|
|
#include "arm11/drivers/interrupt.h"
|
|
|
|
|
|
static const struct
|
|
{
|
|
u8 busId;
|
|
u8 devAddr;
|
|
} g_i2cDevTable[18] =
|
|
{
|
|
{I2C_BUS1, 0x25u<<1}, // 0x4A
|
|
{I2C_BUS1, 0x3Du<<1}, // 0x7A
|
|
{I2C_BUS1, 0x3Cu<<1}, // 0x78
|
|
{I2C_BUS2, 0x25u<<1}, // 0x4A
|
|
{I2C_BUS2, 0x3Cu<<1}, // 0x78
|
|
{I2C_BUS2, 0x16u<<1}, // 0x2C
|
|
{I2C_BUS2, 0x17u<<1}, // 0x2E
|
|
{I2C_BUS2, 0x20u<<1}, // 0x40
|
|
{I2C_BUS2, 0x22u<<1}, // 0x44
|
|
{I2C_BUS3, 0x6Bu<<1}, // 0xD6
|
|
{I2C_BUS3, 0x68u<<1}, // 0xD0
|
|
{I2C_BUS3, 0x69u<<1}, // 0xD2
|
|
{I2C_BUS3, 0x52u<<1}, // 0xA4
|
|
{I2C_BUS3, 0x4Du<<1}, // 0x9A
|
|
{I2C_BUS3, 0x50u<<1}, // 0xA0
|
|
{I2C_BUS2, 0x77u<<1}, // 0xEE
|
|
{I2C_BUS1, 0x20u<<1}, // 0x40
|
|
{I2C_BUS3, 0x2Au<<1} // 0x54
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
KHandle mutex;
|
|
I2cBus *const i2cBus;
|
|
KHandle event;
|
|
} I2cState;
|
|
static I2cState g_i2cState[3] = {{0, (I2cBus*)I2C1_REGS_BASE, 0},
|
|
{0, (I2cBus*)I2C2_REGS_BASE, 0},
|
|
{0, (I2cBus*)I2C3_REGS_BASE, 0}};
|
|
|
|
|
|
|
|
static bool checkAck(I2cBus *const i2cBus)
|
|
{
|
|
// If we received a NACK stop the transfer.
|
|
if((i2cBus->cnt & I2C_ACK) == 0)
|
|
{
|
|
i2cBus->cnt = I2C_EN | I2C_IRQ_EN | I2C_ERROR | I2C_STOP;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sendByte(I2cBus *const i2cBus, const u8 data, const u8 params, KHandle const event)
|
|
{
|
|
i2cBus->data = data;
|
|
i2cBus->cnt = I2C_EN | I2C_IRQ_EN | I2C_DIR_S | params;
|
|
waitForEvent(event);
|
|
}
|
|
|
|
static u8 recvByte(I2cBus *const i2cBus, u8 params, KHandle const event)
|
|
{
|
|
i2cBus->cnt = I2C_EN | I2C_IRQ_EN | I2C_DIR_R | params;
|
|
waitForEvent(event);
|
|
return i2cBus->data;
|
|
}
|
|
|
|
void I2C_init(void)
|
|
{
|
|
static bool inited = false;
|
|
if(inited) return;
|
|
inited = true;
|
|
|
|
for(unsigned i = 0; i < 3; i++)
|
|
{
|
|
static const Interrupt i2cIrqs[3] = {IRQ_I2C1, IRQ_I2C2, IRQ_I2C3};
|
|
const KHandle event = createEvent(true);
|
|
bindInterruptToEvent(event, i2cIrqs[i], 14);
|
|
g_i2cState[i].event = event;
|
|
|
|
g_i2cState[i].mutex = createMutex();
|
|
|
|
I2cBus *const i2cBus = g_i2cState[i].i2cBus;
|
|
while(i2cBus->cnt & I2C_EN);
|
|
i2cBus->cntex = I2C_CLK_STRETCH_EN;
|
|
i2cBus->scl = I2C_DELAYS(5u, 0u);
|
|
}
|
|
}
|
|
|
|
static bool startTransfer(const u8 devAddr, const u32 regAddr, const bool read, const I2cState *const state)
|
|
{
|
|
u32 tries = 8;
|
|
I2cBus *const i2cBus = state->i2cBus;
|
|
const KHandle event = state->event;
|
|
do
|
|
{
|
|
// Edge case on previous transfer error (NACK).
|
|
// This is a special case where we can't predict when or if
|
|
// the IRQ has fired. If it fires after checking but
|
|
// before a wfi this would hang.
|
|
if(i2cBus->cnt & I2C_EN) waitForEvent(event);
|
|
clearEvent(event);
|
|
|
|
// If regAddr is valid we select a register. Otherwise direct transfer.
|
|
if(regAddr < 0x100)
|
|
{
|
|
// Select device for write and start.
|
|
sendByte(i2cBus, devAddr, I2C_START, event);
|
|
if(!checkAck(i2cBus)) continue;
|
|
|
|
// Select register.
|
|
sendByte(i2cBus, regAddr, 0, event);
|
|
if(!checkAck(i2cBus)) continue;
|
|
|
|
// Select device in read mode for read transfer.
|
|
if(read)
|
|
{
|
|
sendByte(i2cBus, devAddr | BIT(0), I2C_START, event);
|
|
if(!checkAck(i2cBus)) continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Select device and start.
|
|
sendByte(i2cBus, (read ? devAddr | BIT(0) : devAddr), I2C_START, event);
|
|
if(!checkAck(i2cBus)) continue;
|
|
}
|
|
|
|
break;
|
|
} while(--tries > 0);
|
|
|
|
return tries > 0;
|
|
}
|
|
|
|
bool I2C_readArray(const I2cDevice devId, const u32 regAddr, void *out, u32 size)
|
|
{
|
|
const u8 devAddr = g_i2cDevTable[devId].devAddr;
|
|
const I2cState *const state = &g_i2cState[g_i2cDevTable[devId].busId];
|
|
I2cBus *const i2cBus = state->i2cBus;
|
|
const KHandle event = state->event;
|
|
const KHandle mutex = state->mutex;
|
|
u8 *ptr8 = out;
|
|
|
|
|
|
bool res = true;
|
|
lockMutex(mutex);
|
|
if(startTransfer(devAddr, regAddr, true, state))
|
|
{
|
|
while(--size) *ptr8++ = recvByte(i2cBus, I2C_ACK, event);
|
|
|
|
// Last byte transfer.
|
|
*ptr8 = recvByte(i2cBus, I2C_STOP, event);
|
|
}
|
|
else res = false;
|
|
unlockMutex(mutex);
|
|
|
|
return res;
|
|
}
|
|
|
|
bool I2C_writeArray(const I2cDevice devId, const u32 regAddr, const void *in, u32 size)
|
|
{
|
|
const u8 devAddr = g_i2cDevTable[devId].devAddr;
|
|
const I2cState *const state = &g_i2cState[g_i2cDevTable[devId].busId];
|
|
I2cBus *const i2cBus = state->i2cBus;
|
|
const KHandle event = state->event;
|
|
const KHandle mutex = state->mutex;
|
|
const u8 *ptr8 = in;
|
|
|
|
|
|
lockMutex(mutex);
|
|
if(!startTransfer(devAddr, regAddr, false, state))
|
|
{
|
|
unlockMutex(mutex);
|
|
return false;
|
|
}
|
|
|
|
while(--size)
|
|
{
|
|
sendByte(i2cBus, *ptr8++, 0, event);
|
|
if(!checkAck(i2cBus))
|
|
{
|
|
unlockMutex(mutex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Last byte transfer.
|
|
sendByte(i2cBus, *ptr8, I2C_STOP, event);
|
|
if(!checkAck(i2cBus))
|
|
{
|
|
unlockMutex(mutex);
|
|
return false;
|
|
}
|
|
unlockMutex(mutex);
|
|
|
|
return true;
|
|
}
|
|
|
|
u8 I2C_read(const I2cDevice devId, const u32 regAddr)
|
|
{
|
|
u8 data;
|
|
if(!I2C_readArray(devId, regAddr, &data, 1)) return 0xFF;
|
|
return data;
|
|
}
|
|
|
|
bool I2C_write(const I2cDevice devId, const u32 regAddr, const u8 data)
|
|
{
|
|
return I2C_writeArray(devId, regAddr, &data, 1);
|
|
}
|
|
// ---------------------------------------------------------------- //
|
|
|
|
static bool checkAckNoIrq(I2cBus *const i2cBus)
|
|
{
|
|
// If we received a NACK stop the transfer.
|
|
if((i2cBus->cnt & I2C_ACK) == 0)
|
|
{
|
|
i2cBus->cnt = I2C_EN | I2C_ERROR | I2C_STOP;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool I2C_writeRegArrayIntSafe(const I2cDevice devId, const u8 regAddr, const void *in, u32 size)
|
|
{
|
|
const u8 devAddr = g_i2cDevTable[devId].devAddr;
|
|
I2cBus *const i2cBus = getI2cBusRegs(g_i2cDevTable[devId].busId);
|
|
const u8 *ptr8 = in;
|
|
|
|
u32 tries = 8;
|
|
do
|
|
{
|
|
while(i2cBus->cnt & I2C_EN);
|
|
|
|
// Select device and start.
|
|
i2cBus->data = devAddr;
|
|
i2cBus->cnt = I2C_EN | I2C_DIR_S | I2C_START;
|
|
while(i2cBus->cnt & I2C_EN);
|
|
if(!checkAckNoIrq(i2cBus)) continue;
|
|
|
|
// Select register.
|
|
i2cBus->data = regAddr;
|
|
i2cBus->cnt = I2C_EN | I2C_DIR_S;
|
|
while(i2cBus->cnt & I2C_EN);
|
|
if(!checkAckNoIrq(i2cBus)) continue;
|
|
|
|
break;
|
|
} while(--tries > 0);
|
|
|
|
if(tries == 0) return false;
|
|
|
|
while(--size)
|
|
{
|
|
i2cBus->data = *ptr8++;
|
|
i2cBus->cnt = I2C_EN | I2C_DIR_S;
|
|
while(i2cBus->cnt & I2C_EN);
|
|
if(!checkAckNoIrq(i2cBus))
|
|
return false;
|
|
}
|
|
|
|
// Last byte transfer.
|
|
i2cBus->data = *ptr8;
|
|
i2cBus->cnt = I2C_EN | I2C_DIR_S | I2C_STOP;
|
|
while(i2cBus->cnt & I2C_EN);
|
|
if(!checkAckNoIrq(i2cBus))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool I2C_writeRegIntSafe(const I2cDevice devId, const u8 regAddr, const u8 data)
|
|
{
|
|
return I2C_writeRegArrayIntSafe(devId, regAddr, &data, 1);
|
|
} |