blob: d6a012780382705554cabf1a746a9482f0b20373 [file] [log] [blame]
/*
* Setup helpers for Two Wire Interface (TWI) (I²C) Allwinner CPUs
*
* Only functionality for I²C master is provided.
* Largely based on the uboot-sunxi code.
*
* Copyright (C) 2012 Henrik Nordstrom <henrik@henriknordstrom.net>
* Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com>
* Subject to the GNU GPL v2, or (at your option) any later version.
*/
#include "memmap.h"
#include "twi.h"
#include <arch/io.h>
#include <delay.h>
#include <device/i2c.h>
#define TWI_BASE(n) (A1X_TWI0_BASE + 0x400 * (n))
#define TWI_TIMEOUT (50 * 1000)
static u8 is_busy(struct a1x_twi *twi)
{
return (read32(&twi->stat) != TWI_STAT_IDLE);
}
static enum cb_err wait_until_idle(struct a1x_twi *twi)
{
u32 i = TWI_TIMEOUT;
while (i-- && is_busy((twi)))
udelay(1);
return i ? CB_SUCCESS : CB_ERR;
}
/* FIXME: This function is basic, and unintelligent */
static void configure_clock(struct a1x_twi *twi, u32 speed_hz)
{
/* FIXME: We assume clock is 24MHz, which may not be the case */
u32 apb_clk = 24000000, m, n;
/* Pre-divide the clock by 8 */
n = 3;
m = (apb_clk >> n) / speed_hz;
write32(TWI_CLK_M(m) | TWI_CLK_N(n), &twi->clk);
}
void a1x_twi_init(u8 bus, u32 speed_hz)
{
u32 i = TWI_TIMEOUT;
struct a1x_twi *twi = (void *)TWI_BASE(bus);
configure_clock(twi, speed_hz);
/* Enable the I²C bus */
write32(TWI_CTL_BUS_EN, &twi->ctl);
/* Issue soft reset */
write32(1, &twi->reset);
while (i-- && read32(&twi->reset))
udelay(1);
}
static void clear_interrupt_flag(struct a1x_twi *twi)
{
write32(read32(&twi->ctl) & ~TWI_CTL_INT_FLAG, &twi->ctl);
}
static void i2c_send_data(struct a1x_twi *twi, u8 data)
{
write32(data, &twi->data);
clear_interrupt_flag(twi);
}
static enum twi_status wait_for_status(struct a1x_twi *twi)
{
u32 i = TWI_TIMEOUT;
/* Wait until interrupt is asserted again */
while (i-- && !(read32(&twi->ctl) & TWI_CTL_INT_FLAG))
udelay(1);
/* A timeout here most likely indicates a bus error */
return i ? read32(&twi->stat) : TWI_STAT_BUS_ERROR;
}
static void i2c_send_start(struct a1x_twi *twi)
{
u32 reg32, i;
/* Send START condition */
reg32 = read32(&twi->ctl);
reg32 &= ~TWI_CTL_INT_FLAG;
reg32 |= TWI_CTL_M_START;
write32(reg32, &twi->ctl);
/* M_START is automatically cleared after condition is transmitted */
i = TWI_TIMEOUT;
while (i-- && (read32(&twi->ctl) & TWI_CTL_M_START))
udelay(1);
}
static void i2c_send_stop(struct a1x_twi *twi)
{
u32 reg32;
/* Send STOP condition */
reg32 = read32(&twi->ctl);
reg32 &= ~TWI_CTL_INT_FLAG;
reg32 |= TWI_CTL_M_STOP;
write32(reg32, &twi->ctl);
}
static int i2c_read(unsigned bus, unsigned chip, unsigned addr,
uint8_t *buf, unsigned len)
{
unsigned count = len;
enum twi_status expected_status;
struct a1x_twi *twi = (void *)TWI_BASE(bus);
if (wait_until_idle(twi) != CB_SUCCESS)
return CB_ERR;
i2c_send_start(twi);
if (wait_for_status(twi) != TWI_STAT_TX_START)
return CB_ERR;
/* Send chip address */
i2c_send_data(twi, chip << 1);
if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK)
return CB_ERR;
/* Send data address */
i2c_send_data(twi, addr);
if (wait_for_status(twi) != TWI_STAT_TXD_ACK)
return CB_ERR;
/* Send restart for read */
i2c_send_start(twi);
if (wait_for_status(twi) != TWI_STAT_TX_RSTART)
return CB_ERR;
/* Send chip address */
i2c_send_data(twi, chip << 1 | 1);
if (wait_for_status(twi) != TWI_STAT_TX_AR_ACK)
return CB_ERR;
/* Start ACK-ing received data */
setbits_le32(&twi->ctl, TWI_CTL_A_ACK);
expected_status = TWI_STAT_RXD_ACK;
/* Read data */
while (count > 0) {
if (count == 1) {
/* Do not ACK the last byte */
clrbits_le32(&twi->ctl, TWI_CTL_A_ACK);
expected_status = TWI_STAT_RXD_NAK;
}
clear_interrupt_flag(twi);
if (wait_for_status(twi) != expected_status)
return CB_ERR;
*buf++ = read32(&twi->data);
count--;
}
i2c_send_stop(twi);
return len;
}
static int i2c_write(unsigned bus, unsigned chip, unsigned addr,
const uint8_t *buf, unsigned len)
{
unsigned count = len;
struct a1x_twi *twi = (void *)TWI_BASE(bus);
if (wait_until_idle(twi) != CB_SUCCESS)
return CB_ERR;
i2c_send_start(twi);
if (wait_for_status(twi) != TWI_STAT_TX_START)
return CB_ERR;
/* Send chip address */
i2c_send_data(twi, chip << 1);
if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK)
return CB_ERR;
/* Send data address */
i2c_send_data(twi, addr);
if (wait_for_status(twi) != TWI_STAT_TXD_ACK)
return CB_ERR;
/* Send data */
while (count > 0) {
i2c_send_data(twi, *buf++);
if (wait_for_status(twi) != TWI_STAT_TXD_ACK)
return CB_ERR;
count--;
}
i2c_send_stop(twi);
return len;
}
/*
* This transfer function is not complete or correct, but it provides
* the basic support that the above read and write functions previously
* provided directly. It is extremely limited and not useful for
* advanced drivers like TPM.
*
* TODO: Rewite the i2c_transfer and supporting functions
*
*/
int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int count)
{
struct i2c_seg *seg = segments;
if (seg->read) {
/* Read has one buf for the addr and one for the data */
if (count != 2)
return -1;
if(i2c_read(bus, seg->chip, *seg->buf, seg[1].buf, seg[1].len) < 0)
return -1;
} else {
/* Write buf has adder and data. */
if (count != 1)
return -1;
if(i2c_write(bus, seg->chip, *seg->buf, seg->buf+1, seg->len-1) < 0)
return -1;
}
return 0;
}