mirror of
https://github.com/CTCaer/hekate
synced 2024-09-07 00:29:00 +00:00
425 lines
9.9 KiB
C
425 lines
9.9 KiB
C
/*
|
|
* Copyright (c) 2018 naehrwert
|
|
* Copyright (c) 2018-2020 CTCaer
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#include <soc/i2c.h>
|
|
#include <utils/util.h>
|
|
|
|
#define I2C_PACKET_PROT_I2C BIT(4)
|
|
#define I2C_HEADER_CONT_XFER BIT(15)
|
|
#define I2C_HEADER_REP_START BIT(16)
|
|
#define I2C_HEADER_IE_ENABLE BIT(17)
|
|
#define I2C_HEADER_READ BIT(19)
|
|
|
|
#define I2C_CNFG (0x00 / 4)
|
|
#define CMD1_WRITE (0 << 6)
|
|
#define CMD1_READ BIT(6)
|
|
#define NORMAL_MODE_GO BIT(9)
|
|
#define PACKET_MODE_GO BIT(10)
|
|
#define NEW_MASTER_FSM BIT(11)
|
|
#define DEBOUNCE_CNT_4T (2 << 12)
|
|
|
|
#define I2C_CMD_ADDR0 (0x04 / 4)
|
|
#define ADDR0_WRITE 0
|
|
#define ADDR0_READ 1
|
|
|
|
#define I2C_CMD_DATA1 (0x0C / 4)
|
|
#define I2C_CMD_DATA2 (0x10 / 4)
|
|
|
|
#define I2C_STATUS (0x1C / 4)
|
|
#define I2C_STATUS_NOACK (0xF << 0)
|
|
#define I2C_STATUS_BUSY BIT(8)
|
|
|
|
#define I2C_TX_FIFO (0x50 / 4)
|
|
#define I2C_RX_FIFO (0x54 / 4)
|
|
|
|
#define I2C_FIFO_CONTROL (0x5C / 4)
|
|
#define RX_FIFO_FLUSH BIT(0)
|
|
#define TX_FIFO_FLUSH BIT(1)
|
|
|
|
#define I2C_FIFO_STATUS (0x60 / 4)
|
|
#define RX_FIFO_FULL_CNT (0xF << 0)
|
|
#define TX_FIFO_EMPTY_CNT (0xF << 4)
|
|
|
|
#define I2C_INT_EN (0x64 / 4)
|
|
#define I2C_INT_STATUS (0x68 / 4)
|
|
#define I2C_INT_SOURCE (0x70 / 4)
|
|
#define RX_FIFO_DATA_REQ BIT(0)
|
|
#define TX_FIFO_DATA_REQ BIT(1)
|
|
#define ARB_LOST BIT(2)
|
|
#define NO_ACK BIT(3)
|
|
#define RX_FIFO_UNDER BIT(4)
|
|
#define TX_FIFO_OVER BIT(5)
|
|
#define ALL_PACKETS_COMPLETE BIT(6)
|
|
#define PACKET_COMPLETE BIT(7)
|
|
#define BUS_CLEAR_DONE BIT(11)
|
|
|
|
#define I2C_CLK_DIVISOR (0x6C / 4)
|
|
|
|
#define I2C_BUS_CLEAR_CONFIG (0x84 / 4)
|
|
#define BC_ENABLE BIT(0)
|
|
#define BC_TERMINATE BIT(1)
|
|
|
|
#define I2C_BUS_CLEAR_STATUS (0x88 / 4)
|
|
|
|
#define I2C_CONFIG_LOAD (0x8C / 4)
|
|
#define MSTR_CONFIG_LOAD BIT(0)
|
|
#define TIMEOUT_CONFIG_LOAD BIT(2)
|
|
|
|
static const u32 i2c_addrs[] = {
|
|
0x7000C000, // I2C_1.
|
|
0x7000C400, // I2C_2.
|
|
0x7000C500, // I2C_3.
|
|
0x7000C700, // I2C_4.
|
|
0x7000D000, // I2C_5.
|
|
0x7000D100 // I2C_6.
|
|
};
|
|
|
|
static void _i2c_load_cfg_wait(vu32 *base)
|
|
{
|
|
base[I2C_CONFIG_LOAD] = BIT(5) | TIMEOUT_CONFIG_LOAD | MSTR_CONFIG_LOAD;
|
|
for (u32 i = 0; i < 20; i++)
|
|
{
|
|
usleep(1);
|
|
if (!(base[I2C_CONFIG_LOAD] & MSTR_CONFIG_LOAD))
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int _i2c_send_single(u32 i2c_idx, u32 dev_addr, u8 *buf, u32 size)
|
|
{
|
|
if (size > 8)
|
|
return 0;
|
|
|
|
u32 tmp = 0;
|
|
|
|
vu32 *base = (vu32 *)i2c_addrs[i2c_idx];
|
|
|
|
// Set device address and send mode.
|
|
base[I2C_CMD_ADDR0] = dev_addr << 1 | ADDR0_WRITE;
|
|
|
|
if (size > 4)
|
|
{
|
|
memcpy(&tmp, buf, 4);
|
|
base[I2C_CMD_DATA1] = tmp; //Set value.
|
|
tmp = 0;
|
|
memcpy(&tmp, buf + 4, size - 4);
|
|
base[I2C_CMD_DATA2] = tmp;
|
|
}
|
|
else
|
|
{
|
|
memcpy(&tmp, buf, size);
|
|
base[I2C_CMD_DATA1] = tmp; //Set value.
|
|
}
|
|
|
|
// Set size and send mode.
|
|
base[I2C_CNFG] = ((size - 1) << 1) | DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_WRITE;
|
|
|
|
// Load configuration.
|
|
_i2c_load_cfg_wait(base);
|
|
|
|
// Initiate transaction on normal mode.
|
|
base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | NORMAL_MODE_GO;
|
|
|
|
u32 timeout = get_tmr_us() + 200000; // Actual for max 8 bytes at 100KHz is 0.74ms.
|
|
while (base[I2C_STATUS] & I2C_STATUS_BUSY)
|
|
{
|
|
if (get_tmr_us() > timeout)
|
|
return 0;
|
|
}
|
|
|
|
if (base[I2C_STATUS] & I2C_STATUS_NOACK)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _i2c_recv_single(u32 i2c_idx, u8 *buf, u32 size, u32 dev_addr)
|
|
{
|
|
if (size > 8)
|
|
return 0;
|
|
|
|
vu32 *base = (vu32 *)i2c_addrs[i2c_idx];
|
|
|
|
// Set device address and recv mode.
|
|
base[I2C_CMD_ADDR0] = (dev_addr << 1) | ADDR0_READ;
|
|
|
|
// Set size and recv mode.
|
|
base[I2C_CNFG] = ((size - 1) << 1) | DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_READ;
|
|
|
|
// Load configuration.
|
|
_i2c_load_cfg_wait(base);
|
|
|
|
// Initiate transaction on normal mode.
|
|
base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | NORMAL_MODE_GO;
|
|
|
|
u32 timeout = get_tmr_us() + 200000; // Actual for max 8 bytes at 100KHz is 0.74ms.
|
|
while (base[I2C_STATUS] & I2C_STATUS_BUSY)
|
|
{
|
|
if (get_tmr_us() > timeout)
|
|
return 0;
|
|
}
|
|
|
|
if (base[I2C_STATUS] & I2C_STATUS_NOACK)
|
|
return 0;
|
|
|
|
u32 tmp = base[I2C_CMD_DATA1]; // Get LS value.
|
|
if (size > 4)
|
|
{
|
|
memcpy(buf, &tmp, 4);
|
|
tmp = base[I2C_CMD_DATA2]; // Get MS value.
|
|
memcpy(buf + 4, &tmp, size - 4);
|
|
}
|
|
else
|
|
memcpy(buf, &tmp, size);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _i2c_send_pkt(u32 i2c_idx, u8 *buf, u32 size, u32 dev_addr)
|
|
{
|
|
if (size > 32)
|
|
return 0;
|
|
|
|
int res = 0;
|
|
|
|
vu32 *base = (vu32 *)i2c_addrs[i2c_idx];
|
|
|
|
// Enable interrupts.
|
|
base[I2C_INT_EN] = ALL_PACKETS_COMPLETE | PACKET_COMPLETE | NO_ACK |
|
|
ARB_LOST | TX_FIFO_OVER | RX_FIFO_UNDER | TX_FIFO_DATA_REQ;
|
|
base[I2C_INT_STATUS] = base[I2C_INT_STATUS];
|
|
|
|
// Set device address and recv mode.
|
|
base[I2C_CMD_ADDR0] = (dev_addr << 1) | ADDR0_READ;
|
|
|
|
// Set recv mode.
|
|
base[I2C_CNFG] = DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_WRITE;
|
|
|
|
// Set and flush FIFO.
|
|
base[I2C_FIFO_CONTROL] = RX_FIFO_FLUSH | TX_FIFO_FLUSH;
|
|
|
|
// Load configuration.
|
|
_i2c_load_cfg_wait(base);
|
|
|
|
// Initiate transaction on packet mode.
|
|
base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | PACKET_MODE_GO;
|
|
|
|
u32 hdr[3];
|
|
hdr[0] = I2C_PACKET_PROT_I2C;
|
|
hdr[1] = size - 1;
|
|
hdr[2] = I2C_HEADER_IE_ENABLE | I2C_HEADER_CONT_XFER | (dev_addr << 1);
|
|
|
|
// Send header with request.
|
|
base[I2C_TX_FIFO] = hdr[0];
|
|
base[I2C_TX_FIFO] = hdr[1];
|
|
base[I2C_TX_FIFO] = hdr[2];
|
|
|
|
u32 timeout = get_tmr_ms() + 400;
|
|
while (size)
|
|
{
|
|
if (base[I2C_FIFO_STATUS] & TX_FIFO_EMPTY_CNT)
|
|
{
|
|
u32 tmp = 0;
|
|
u32 snd_size = MIN(size, 4);
|
|
memcpy(&tmp, buf, snd_size);
|
|
base[I2C_TX_FIFO] = tmp;
|
|
buf += snd_size;
|
|
size -= snd_size;
|
|
}
|
|
|
|
if (get_tmr_ms() > timeout)
|
|
{
|
|
res = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (base[I2C_STATUS] & I2C_STATUS_NOACK || base[I2C_INT_STATUS] & NO_ACK)
|
|
res = 1;
|
|
|
|
// Disable packet mode.
|
|
usleep(20);
|
|
base[I2C_CNFG] &= 0xFFFFF9FF;
|
|
|
|
// Disable interrupts.
|
|
base[I2C_INT_EN] = 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
static int _i2c_recv_pkt(u32 i2c_idx, u8 *buf, u32 size, u32 dev_addr, u32 reg)
|
|
{
|
|
if (size > 32)
|
|
return 0;
|
|
|
|
int res = 0;
|
|
|
|
vu32 *base = (vu32 *)i2c_addrs[i2c_idx];
|
|
|
|
// Enable interrupts.
|
|
base[I2C_INT_EN] = ALL_PACKETS_COMPLETE | PACKET_COMPLETE | NO_ACK |
|
|
ARB_LOST | TX_FIFO_OVER | RX_FIFO_UNDER | RX_FIFO_DATA_REQ;
|
|
base[I2C_INT_STATUS] = base[I2C_INT_STATUS];
|
|
|
|
// Set device address and recv mode.
|
|
base[I2C_CMD_ADDR0] = (dev_addr << 1) | ADDR0_READ;
|
|
|
|
// Set recv mode.
|
|
base[I2C_CNFG] = DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_READ;
|
|
|
|
// Set and flush FIFO.
|
|
base[I2C_FIFO_CONTROL] = RX_FIFO_FLUSH | TX_FIFO_FLUSH;
|
|
|
|
// Load configuration.
|
|
_i2c_load_cfg_wait(base);
|
|
|
|
// Initiate transaction on packet mode.
|
|
base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | PACKET_MODE_GO;
|
|
|
|
// Send reg request.
|
|
u32 hdr[3];
|
|
hdr[0] = I2C_PACKET_PROT_I2C;
|
|
hdr[1] = 1 - 1;
|
|
hdr[2] = I2C_HEADER_REP_START | (dev_addr << 1);
|
|
|
|
// Send header with reg request.
|
|
base[I2C_TX_FIFO] = hdr[0];
|
|
base[I2C_TX_FIFO] = hdr[1];
|
|
base[I2C_TX_FIFO] = hdr[2];
|
|
base[I2C_TX_FIFO] = reg;
|
|
|
|
u32 timeout = get_tmr_ms() + 400;
|
|
while (!(base[I2C_FIFO_STATUS] & TX_FIFO_EMPTY_CNT))
|
|
if (get_tmr_ms() > timeout)
|
|
break;
|
|
|
|
// Send read request.
|
|
hdr[1] = size - 1;
|
|
hdr[2] = I2C_HEADER_READ | (dev_addr << 1);
|
|
|
|
// Send header with read request.
|
|
base[I2C_TX_FIFO] = hdr[0];
|
|
base[I2C_TX_FIFO] = hdr[1];
|
|
base[I2C_TX_FIFO] = hdr[2];
|
|
|
|
timeout = get_tmr_ms() + 400;
|
|
while (size)
|
|
{
|
|
if (base[I2C_FIFO_STATUS] & RX_FIFO_FULL_CNT)
|
|
{
|
|
u32 rcv_size = MIN(size, 4);
|
|
u32 tmp = base[I2C_RX_FIFO];
|
|
memcpy(buf, &tmp, rcv_size);
|
|
buf += rcv_size;
|
|
size -= rcv_size;
|
|
}
|
|
|
|
if (get_tmr_ms() > timeout)
|
|
{
|
|
res = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (base[I2C_STATUS] & I2C_STATUS_NOACK || base[I2C_INT_STATUS] & NO_ACK)
|
|
res = 1;
|
|
|
|
// Disable packet mode.
|
|
usleep(20);
|
|
base[I2C_CNFG] &= 0xFFFFF9FF;
|
|
|
|
// Disable interrupts.
|
|
base[I2C_INT_EN] = 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
void i2c_init(u32 i2c_idx)
|
|
{
|
|
vu32 *base = (vu32 *)i2c_addrs[i2c_idx];
|
|
|
|
base[I2C_CLK_DIVISOR] = (5 << 16) | 1; // SF mode Div: 6, HS mode div: 2.
|
|
base[I2C_BUS_CLEAR_CONFIG] = (9 << 16) | BC_TERMINATE | BC_ENABLE;
|
|
|
|
// Load configuration.
|
|
_i2c_load_cfg_wait(base);
|
|
|
|
for (u32 i = 0; i < 10; i++)
|
|
{
|
|
usleep(20000);
|
|
if (base[I2C_INT_STATUS] & BUS_CLEAR_DONE)
|
|
break;
|
|
}
|
|
|
|
(vu32)base[I2C_BUS_CLEAR_STATUS];
|
|
base[I2C_INT_STATUS] = base[I2C_INT_STATUS];
|
|
}
|
|
|
|
int i2c_recv_buf(u8 *buf, u32 size, u32 i2c_idx, u32 dev_addr)
|
|
{
|
|
return _i2c_recv_single(i2c_idx, buf, size, dev_addr);
|
|
}
|
|
|
|
int i2c_send_buf_big(u32 i2c_idx, u32 dev_addr, u8 *buf, u32 size)
|
|
{
|
|
if (size > 32)
|
|
return 0;
|
|
|
|
return _i2c_send_pkt(i2c_idx, buf, size, dev_addr);
|
|
}
|
|
|
|
int i2c_recv_buf_big(u8 *buf, u32 size, u32 i2c_idx, u32 dev_addr, u32 reg)
|
|
{
|
|
return _i2c_recv_pkt(i2c_idx, buf, size, dev_addr, reg);
|
|
}
|
|
|
|
int i2c_send_buf_small(u32 i2c_idx, u32 dev_addr, u32 reg, u8 *buf, u32 size)
|
|
{
|
|
u8 tmp[4];
|
|
|
|
if (size > 7)
|
|
return 0;
|
|
|
|
tmp[0] = reg;
|
|
memcpy(tmp + 1, buf, size);
|
|
|
|
return _i2c_send_single(i2c_idx, dev_addr, tmp, size + 1);
|
|
}
|
|
|
|
int i2c_recv_buf_small(u8 *buf, u32 size, u32 i2c_idx, u32 dev_addr, u32 reg)
|
|
{
|
|
int res = _i2c_send_single(i2c_idx, dev_addr, (u8 *)®, 1);
|
|
if (res)
|
|
res = _i2c_recv_single(i2c_idx, buf, size, dev_addr);
|
|
return res;
|
|
}
|
|
|
|
int i2c_send_byte(u32 i2c_idx, u32 dev_addr, u32 reg, u8 val)
|
|
{
|
|
return i2c_send_buf_small(i2c_idx, dev_addr, reg, &val, 1);
|
|
}
|
|
|
|
u8 i2c_recv_byte(u32 i2c_idx, u32 dev_addr, u32 reg)
|
|
{
|
|
u8 tmp = 0;
|
|
i2c_recv_buf_small(&tmp, 1, i2c_idx, dev_addr, reg);
|
|
return tmp;
|
|
}
|
|
|