306 lines
7.8 KiB
C
306 lines
7.8 KiB
C
|
/* Copyright 2014 The Chromium OS Authors. All rights reserved.
|
|||
|
* Use of this source code is governed by a BSD-style license that can be
|
|||
|
* found in the LICENSE file.
|
|||
|
*/
|
|||
|
|
|||
|
#include "clock.h"
|
|||
|
#include "common.h"
|
|||
|
#include "console.h"
|
|||
|
#include "gpio.h"
|
|||
|
#include "hooks.h"
|
|||
|
#include "i2c.h"
|
|||
|
#include "ppi.h"
|
|||
|
#include "registers.h"
|
|||
|
#include "task.h"
|
|||
|
#include "timer.h"
|
|||
|
#include "util.h"
|
|||
|
|
|||
|
/* Console output macros */
|
|||
|
#define CPUTS(outstr) cputs(CC_I2C, outstr)
|
|||
|
#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args)
|
|||
|
#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
|
|||
|
|
|||
|
#define I2C_TIMEOUT 20000
|
|||
|
|
|||
|
/* Keep track of the PPI channel used for each port */
|
|||
|
static int i2c_ppi_chan[] = {-1, -1};
|
|||
|
|
|||
|
static void i2c_init_port(unsigned int port);
|
|||
|
|
|||
|
/* board-specific setup for post-I2C module init */
|
|||
|
void __board_i2c_post_init(int port)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
void board_i2c_post_init(int port)
|
|||
|
__attribute__((weak, alias("__board_i2c_post_init")));
|
|||
|
|
|||
|
static void i2c_init_port(unsigned int port)
|
|||
|
{
|
|||
|
NRF51_TWI_RXDRDY(port) = 0;
|
|||
|
NRF51_TWI_TXDSENT(port) = 0;
|
|||
|
|
|||
|
NRF51_TWI_PSELSCL(port) = NRF51_TWI_SCL_PIN(port);
|
|||
|
NRF51_TWI_PSELSDA(port) = NRF51_TWI_SDA_PIN(port);
|
|||
|
NRF51_TWI_FREQUENCY(port) = NRF51_TWI_FREQ(port);
|
|||
|
|
|||
|
NRF51_PPI_CHENCLR = 1 << i2c_ppi_chan[port];
|
|||
|
|
|||
|
NRF51_PPI_EEP(i2c_ppi_chan[port]) = (uint32_t)&NRF51_TWI_BB(port);
|
|||
|
NRF51_PPI_TEP(i2c_ppi_chan[port]) =
|
|||
|
(uint32_t)&NRF51_TWI_SUSPEND(port);
|
|||
|
|
|||
|
/* Master enable */
|
|||
|
NRF51_TWI_ENABLE(port) = NRF51_TWI_ENABLE_VAL;
|
|||
|
|
|||
|
if (!(i2c_raw_get_scl(port) && (i2c_raw_get_sda(port))))
|
|||
|
CPRINTF("port %d could be wedged\n", port);
|
|||
|
}
|
|||
|
|
|||
|
static void i2c_init(void)
|
|||
|
{
|
|||
|
int i, rv;
|
|||
|
|
|||
|
gpio_config_module(MODULE_I2C, 1);
|
|||
|
|
|||
|
for (i = 0; i < i2c_ports_used; i++) {
|
|||
|
if (i2c_ppi_chan[i] == -1) {
|
|||
|
rv = ppi_request_channel(&i2c_ppi_chan[i]);
|
|||
|
ASSERT(rv == EC_SUCCESS);
|
|||
|
|
|||
|
i2c_init_port(i);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
|
|||
|
|
|||
|
static void dump_i2c_reg(int port)
|
|||
|
{
|
|||
|
#ifdef CONFIG_I2C_DEBUG
|
|||
|
CPRINTF("port : %01d\n", port);
|
|||
|
CPRINTF("Regs :\n");
|
|||
|
CPRINTF(" 1: INTEN : %08x\n", NRF51_TWI_INTEN(port));
|
|||
|
CPRINTF(" 2: ERRORSRC : %08x\n", NRF51_TWI_ERRORSRC(port));
|
|||
|
CPRINTF(" 3: ENABLE : %08x\n", NRF51_TWI_ENABLE(port));
|
|||
|
CPRINTF(" 4: PSELSCL : %08x\n", NRF51_TWI_PSELSCL(port));
|
|||
|
CPRINTF(" 5: PSELSDA : %08x\n", NRF51_TWI_PSELSDA(port));
|
|||
|
CPRINTF(" 6: RXD : %08x\n", NRF51_TWI_RXD(port));
|
|||
|
CPRINTF(" 7: TXD : %08x\n", NRF51_TWI_TXD(port));
|
|||
|
CPRINTF(" 8: FREQUENCY : %08x\n", NRF51_TWI_FREQUENCY(port));
|
|||
|
CPRINTF(" 9: ADDRESS : %08x\n", NRF51_TWI_ADDRESS(port));
|
|||
|
CPRINTF("Events :\n");
|
|||
|
CPRINTF(" STOPPED : %08x\n", NRF51_TWI_STOPPED(port));
|
|||
|
CPRINTF(" RXDRDY : %08x\n", NRF51_TWI_RXDRDY(port));
|
|||
|
CPRINTF(" TXDSENT : %08x\n", NRF51_TWI_TXDSENT(port));
|
|||
|
CPRINTF(" ERROR : %08x\n", NRF51_TWI_ERROR(port));
|
|||
|
CPRINTF(" BB : %08x\n", NRF51_TWI_BB(port));
|
|||
|
#endif /* CONFIG_I2C_DEBUG */
|
|||
|
}
|
|||
|
|
|||
|
static void i2c_recover(int port)
|
|||
|
{
|
|||
|
/*
|
|||
|
* Recovery of the TWI peripheral:
|
|||
|
* To recover a TWI peripheral that has been locked up you must use
|
|||
|
* the following code.
|
|||
|
* After the recover function it is important to reconfigure all
|
|||
|
* relevant TWI registers explicitly to ensure that it operates
|
|||
|
* correctly.
|
|||
|
* TWI0:
|
|||
|
* NRF_TWI0->ENABLE =
|
|||
|
* TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
|
|||
|
* *(uint32_t *)(NRF_TWI0_BASE + 0xFFC) = 0;
|
|||
|
* nrf_delay_us(5);
|
|||
|
* *(uint32_t *)(NRF_TWI0_BASE + 0xFFC) = 1;
|
|||
|
* NRF_TWI0->ENABLE =
|
|||
|
* TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos;
|
|||
|
*/
|
|||
|
NRF51_TWI_ENABLE(port) = NRF51_TWI_DISABLE_VAL;
|
|||
|
NRF51_TWI_POWER(port) = 0;
|
|||
|
udelay(5);
|
|||
|
NRF51_TWI_POWER(port) = 1;
|
|||
|
|
|||
|
i2c_init_port(port);
|
|||
|
}
|
|||
|
|
|||
|
static void handle_i2c_error(int port, int rv)
|
|||
|
{
|
|||
|
if (rv == EC_SUCCESS)
|
|||
|
return;
|
|||
|
|
|||
|
#ifdef CONFIG_I2C_DEBUG
|
|||
|
if (rv != EC_ERROR_TIMEOUT)
|
|||
|
CPRINTF("handle_i2c_error %d\n", rv);
|
|||
|
else
|
|||
|
CPRINTF("handle_i2c_error: Timeout\n");
|
|||
|
|
|||
|
dump_i2c_reg(port);
|
|||
|
#endif
|
|||
|
|
|||
|
/* This may be a little too heavy handed. */
|
|||
|
i2c_recover(port);
|
|||
|
}
|
|||
|
|
|||
|
static int i2c_master_write(const int port, const uint16_t slave_addr_flags,
|
|||
|
const uint8_t *data, int size, int stop)
|
|||
|
{
|
|||
|
int bytes_sent;
|
|||
|
int timeout = I2C_TIMEOUT;
|
|||
|
|
|||
|
NRF51_TWI_ADDRESS(port) = I2C_GET_ADDR(slave_addr_flags);
|
|||
|
|
|||
|
/* Clear the sent bit */
|
|||
|
NRF51_TWI_TXDSENT(port) = 0;
|
|||
|
|
|||
|
for (bytes_sent = 0; bytes_sent < size; bytes_sent++) {
|
|||
|
/*Send a byte */
|
|||
|
NRF51_TWI_TXD(port) = data[bytes_sent];
|
|||
|
|
|||
|
/* Only send a start for the first byte */
|
|||
|
if (bytes_sent == 0)
|
|||
|
NRF51_TWI_STARTTX(port) = 1;
|
|||
|
|
|||
|
/* Wait for ACK/NACK */
|
|||
|
timeout = I2C_TIMEOUT;
|
|||
|
while (timeout > 0 && NRF51_TWI_TXDSENT(port) == 0 &&
|
|||
|
NRF51_TWI_ERROR(port) == 0)
|
|||
|
timeout--;
|
|||
|
|
|||
|
if (timeout == 0)
|
|||
|
return EC_ERROR_TIMEOUT;
|
|||
|
|
|||
|
if (NRF51_TWI_ERROR(port))
|
|||
|
return EC_ERROR_UNKNOWN;
|
|||
|
|
|||
|
/* Clear the sent bit */
|
|||
|
NRF51_TWI_TXDSENT(port) = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (stop) {
|
|||
|
NRF51_TWI_STOPPED(port) = 0;
|
|||
|
NRF51_TWI_STOP(port) = 1;
|
|||
|
timeout = 10;
|
|||
|
while (NRF51_TWI_STOPPED(port) == 0 && timeout > 0)
|
|||
|
timeout--;
|
|||
|
}
|
|||
|
|
|||
|
return EC_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
static int i2c_master_read(const int port, const uint16_t slave_addr_flags,
|
|||
|
uint8_t *data, int size)
|
|||
|
{
|
|||
|
int curr_byte;
|
|||
|
int timeout = I2C_TIMEOUT;
|
|||
|
|
|||
|
NRF51_TWI_ADDRESS(port) = I2C_GET_ADDR(slave_addr_flags);
|
|||
|
|
|||
|
if (size == 1) /* Last byte: stop after this one. */
|
|||
|
NRF51_PPI_TEP(i2c_ppi_chan[port]) =
|
|||
|
(uint32_t)&NRF51_TWI_STOP(port);
|
|||
|
else
|
|||
|
NRF51_PPI_TEP(i2c_ppi_chan[port]) =
|
|||
|
(uint32_t)&NRF51_TWI_SUSPEND(port);
|
|||
|
NRF51_PPI_CHENSET = 1 << i2c_ppi_chan[port];
|
|||
|
|
|||
|
NRF51_TWI_RXDRDY(port) = 0;
|
|||
|
NRF51_TWI_STARTRX(port) = 1;
|
|||
|
|
|||
|
for (curr_byte = 0; curr_byte < size; curr_byte++) {
|
|||
|
|
|||
|
/* Wait for data */
|
|||
|
while (timeout > 0 && NRF51_TWI_RXDRDY(port) == 0 &&
|
|||
|
NRF51_TWI_ERROR(port) == 0)
|
|||
|
timeout--;
|
|||
|
|
|||
|
if (timeout == 0)
|
|||
|
return EC_ERROR_TIMEOUT;
|
|||
|
|
|||
|
if (NRF51_TWI_ERROR(port))
|
|||
|
return EC_ERROR_UNKNOWN;
|
|||
|
|
|||
|
data[curr_byte] = NRF51_TWI_RXD(port);
|
|||
|
NRF51_TWI_RXDRDY(port) = 0;
|
|||
|
|
|||
|
/* Second to the last byte: stop next time. */
|
|||
|
if (curr_byte == size-2)
|
|||
|
NRF51_PPI_TEP(i2c_ppi_chan[port]) =
|
|||
|
(uint32_t)&NRF51_TWI_STOP(port);
|
|||
|
|
|||
|
/*
|
|||
|
* According to nRF51822-PAN v2.4 (Product Anomaly Notice),
|
|||
|
* the I2C locks up when RESUME is triggered too soon.
|
|||
|
* "the firmware should ensure that the time between receiving
|
|||
|
* the RXDRDY event and trigging the RESUME task is at least
|
|||
|
* two times the TWI clock period (i.e. 20 μs at 100 kbps).
|
|||
|
* Provided the TWI slave doesn’t do clock stretching during
|
|||
|
* the ACK bit, this will be enough to avoid the RESUME task
|
|||
|
* hit the end of the ACK bit. If this fails, a recovery of
|
|||
|
* the peripheral will be necessary, see i2c_recover.
|
|||
|
*/
|
|||
|
udelay(20);
|
|||
|
NRF51_TWI_RESUME(port) = 1;
|
|||
|
}
|
|||
|
|
|||
|
timeout = I2C_TIMEOUT;
|
|||
|
while (NRF51_TWI_STOPPED(port) == 0 && timeout > 0)
|
|||
|
timeout--;
|
|||
|
|
|||
|
NRF51_TWI_STOP(port) = 0;
|
|||
|
|
|||
|
NRF51_PPI_CHENCLR = 1 << i2c_ppi_chan[port];
|
|||
|
|
|||
|
return EC_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
int chip_i2c_xfer(const int port, const uint16_t slave_addr_flags,
|
|||
|
const uint8_t *out, int out_bytes,
|
|||
|
uint8_t *in, int in_bytes, int flags)
|
|||
|
{
|
|||
|
int rv = EC_SUCCESS;
|
|||
|
|
|||
|
ASSERT(out || !out_bytes);
|
|||
|
ASSERT(in || !in_bytes);
|
|||
|
|
|||
|
if (out_bytes)
|
|||
|
rv = i2c_master_write(port, slave_addr_flags,
|
|||
|
out, out_bytes,
|
|||
|
in_bytes ? 0 : 1);
|
|||
|
if (rv == EC_SUCCESS && in_bytes)
|
|||
|
rv = i2c_master_read(port, slave_addr_flags,
|
|||
|
in, in_bytes);
|
|||
|
|
|||
|
handle_i2c_error(port, rv);
|
|||
|
|
|||
|
return rv;
|
|||
|
}
|
|||
|
|
|||
|
int i2c_raw_get_scl(int port)
|
|||
|
{
|
|||
|
enum gpio_signal g;
|
|||
|
|
|||
|
if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS)
|
|||
|
return gpio_get_level(g);
|
|||
|
|
|||
|
/* If no SCL pin defined for this port, then return 1 to appear idle. */
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
int i2c_raw_get_sda(int port)
|
|||
|
{
|
|||
|
enum gpio_signal g;
|
|||
|
|
|||
|
if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS)
|
|||
|
return gpio_get_level(g);
|
|||
|
|
|||
|
/* If no SDA pin defined for this port, then return 1 to appear idle. */
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
int i2c_get_line_levels(int port)
|
|||
|
{
|
|||
|
return (i2c_raw_get_sda(port) ? I2C_LINE_SDA_HIGH : 0) |
|
|||
|
(i2c_raw_get_scl(port) ? I2C_LINE_SCL_HIGH : 0);
|
|||
|
}
|
|||
|
|