mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-25 21:24:21 +08:00
670514f524
Due to breaking boots from NOR flashes, commit d6b7757
("i2c: mvtwsi:
Eliminate twsi_control_flags") removed the static global
twsi_control_flags variable, which kept a set of default flags that were
always or'd to the control register when writing. It was replaced with a
flags parameter, which was passed around between the functions that
needed it.
Since the twsi_control_flags variable was used just for the purposes of
a) setting the MVTWSI_CONTROL_TWSIEN on every control register write,
and
b) setting the MVTWSI_CONTROL_ACK from twsi_i2c_read if needed,
anyway, the added overhead of another variable being passed around is no
longer justified, and we are better off implementing this flag setting
logic locally in the functions that actually write to the control
register.
Therefore, this patch sets MVTWSI_CONTROL_TWSIEN on every control
register write, replaces the twsi_i2c_read's flags parameter with a
ack_flag parameter, which tells the function whether to acknowledge the
read or not, and removes every other instance of the flags variable.
This has the added benefit that now every notion of "global default
flags" is gone, and it's much easier to see which control flags are
actually set at which point in time.
Signed-off-by: Mario Six <mario.six@gdsys.cc>
Reviewed-by: Stefan Roese <sr@denx.de>
528 lines
14 KiB
C
528 lines
14 KiB
C
/*
|
|
* Driver for the TWSI (i2c) controller found on the Marvell
|
|
* orion5x and kirkwood SoC families.
|
|
*
|
|
* Author: Albert Aribaud <albert.u.boot@aribaud.net>
|
|
* Copyright (c) 2010 Albert Aribaud.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <i2c.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/io.h>
|
|
|
|
/*
|
|
* Include a file that will provide CONFIG_I2C_MVTWSI_BASE*, and possibly other
|
|
* settings
|
|
*/
|
|
|
|
#if defined(CONFIG_ORION5X)
|
|
#include <asm/arch/orion5x.h>
|
|
#elif (defined(CONFIG_KIRKWOOD) || defined(CONFIG_ARCH_MVEBU))
|
|
#include <asm/arch/soc.h>
|
|
#elif defined(CONFIG_SUNXI)
|
|
#include <asm/arch/i2c.h>
|
|
#else
|
|
#error Driver mvtwsi not supported by SoC or board
|
|
#endif
|
|
|
|
/*
|
|
* TWSI register structure
|
|
*/
|
|
|
|
#ifdef CONFIG_SUNXI
|
|
|
|
struct mvtwsi_registers {
|
|
u32 slave_address;
|
|
u32 xtnd_slave_addr;
|
|
u32 data;
|
|
u32 control;
|
|
u32 status;
|
|
u32 baudrate;
|
|
u32 soft_reset;
|
|
};
|
|
|
|
#else
|
|
|
|
struct mvtwsi_registers {
|
|
u32 slave_address;
|
|
u32 data;
|
|
u32 control;
|
|
union {
|
|
u32 status; /* When reading */
|
|
u32 baudrate; /* When writing */
|
|
};
|
|
u32 xtnd_slave_addr;
|
|
u32 reserved[2];
|
|
u32 soft_reset;
|
|
};
|
|
|
|
#endif
|
|
|
|
/*
|
|
* enum mvtwsi_ctrl_register_fields - Bit masks for flags in the control
|
|
* register
|
|
*/
|
|
enum mvtwsi_ctrl_register_fields {
|
|
/* Acknowledge bit */
|
|
MVTWSI_CONTROL_ACK = 0x00000004,
|
|
/* Interrupt flag */
|
|
MVTWSI_CONTROL_IFLG = 0x00000008,
|
|
/* Stop bit */
|
|
MVTWSI_CONTROL_STOP = 0x00000010,
|
|
/* Start bit */
|
|
MVTWSI_CONTROL_START = 0x00000020,
|
|
/* I2C enable */
|
|
MVTWSI_CONTROL_TWSIEN = 0x00000040,
|
|
/* Interrupt enable */
|
|
MVTWSI_CONTROL_INTEN = 0x00000080,
|
|
};
|
|
|
|
/*
|
|
* On sun6i and newer, IFLG is a write-clear bit, which is cleared by writing 1;
|
|
* on other platforms, it is a normal r/w bit, which is cleared by writing 0.
|
|
*/
|
|
|
|
#ifdef CONFIG_SUNXI_GEN_SUN6I
|
|
#define MVTWSI_CONTROL_CLEAR_IFLG 0x00000008
|
|
#else
|
|
#define MVTWSI_CONTROL_CLEAR_IFLG 0x00000000
|
|
#endif
|
|
|
|
/*
|
|
* enum mvstwsi_status_values - Possible values of I2C controller's status
|
|
* register
|
|
*
|
|
* Only those statuses expected in normal master operation on
|
|
* non-10-bit-address devices are specified.
|
|
*
|
|
* Every status that's unexpected during normal operation (bus errors,
|
|
* arbitration losses, missing ACKs...) is passed back to the caller as an error
|
|
* code.
|
|
*/
|
|
enum mvstwsi_status_values {
|
|
/* START condition transmitted */
|
|
MVTWSI_STATUS_START = 0x08,
|
|
/* Repeated START condition transmitted */
|
|
MVTWSI_STATUS_REPEATED_START = 0x10,
|
|
/* Address + write bit transmitted, ACK received */
|
|
MVTWSI_STATUS_ADDR_W_ACK = 0x18,
|
|
/* Data transmitted, ACK received */
|
|
MVTWSI_STATUS_DATA_W_ACK = 0x28,
|
|
/* Address + read bit transmitted, ACK received */
|
|
MVTWSI_STATUS_ADDR_R_ACK = 0x40,
|
|
/* Address + read bit transmitted, ACK not received */
|
|
MVTWSI_STATUS_ADDR_R_NAK = 0x48,
|
|
/* Data received, ACK transmitted */
|
|
MVTWSI_STATUS_DATA_R_ACK = 0x50,
|
|
/* Data received, ACK not transmitted */
|
|
MVTWSI_STATUS_DATA_R_NAK = 0x58,
|
|
/* No relevant status */
|
|
MVTWSI_STATUS_IDLE = 0xF8,
|
|
};
|
|
|
|
/*
|
|
* enum mvstwsi_ack_flags - Determine whether a read byte should be
|
|
* acknowledged or not.
|
|
*/
|
|
enum mvtwsi_ack_flags {
|
|
/* Send NAK after received byte */
|
|
MVTWSI_READ_NAK = 0,
|
|
/* Send ACK after received byte */
|
|
MVTWSI_READ_ACK = 1,
|
|
};
|
|
|
|
/*
|
|
* MVTWSI controller base
|
|
*/
|
|
|
|
static struct mvtwsi_registers *twsi_get_base(struct i2c_adapter *adap)
|
|
{
|
|
switch (adap->hwadapnr) {
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE0
|
|
case 0:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE0;
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE1
|
|
case 1:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE1;
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE2
|
|
case 2:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE2;
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE3
|
|
case 3:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE3;
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE4
|
|
case 4:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE4;
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE5
|
|
case 5:
|
|
return (struct mvtwsi_registers *)CONFIG_I2C_MVTWSI_BASE5;
|
|
#endif
|
|
default:
|
|
printf("Missing mvtwsi controller %d base\n", adap->hwadapnr);
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* enum mvtwsi_error_class - types of I2C errors
|
|
*/
|
|
enum mvtwsi_error_class {
|
|
/* The controller returned a different status than expected */
|
|
MVTWSI_ERROR_WRONG_STATUS = 0x01,
|
|
/* The controller timed out */
|
|
MVTWSI_ERROR_TIMEOUT = 0x02,
|
|
};
|
|
|
|
/*
|
|
* mvtwsi_error() - Build I2C return code from error information
|
|
*
|
|
* For debugging purposes, this function packs some information of an occurred
|
|
* error into a return code. These error codes are returned from I2C API
|
|
* functions (i2c_{read,write}, dm_i2c_{read,write}, etc.).
|
|
*
|
|
* @ec: The error class of the error (enum mvtwsi_error_class).
|
|
* @lc: The last value of the control register.
|
|
* @ls: The last value of the status register.
|
|
* @es: The expected value of the status register.
|
|
* @return The generated error code.
|
|
*/
|
|
inline uint mvtwsi_error(uint ec, uint lc, uint ls, uint es)
|
|
{
|
|
return ((ec << 24) & 0xFF000000)
|
|
| ((lc << 16) & 0x00FF0000)
|
|
| ((ls << 8) & 0x0000FF00)
|
|
| (es & 0xFF);
|
|
}
|
|
|
|
/*
|
|
* Wait for IFLG to raise, or return 'timeout.' Then, if the status is as
|
|
* expected, return 0 (ok) or 'wrong status' otherwise.
|
|
*/
|
|
static int twsi_wait(struct i2c_adapter *adap, int expected_status)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
int control, status;
|
|
int timeout = 1000;
|
|
|
|
do {
|
|
control = readl(&twsi->control);
|
|
if (control & MVTWSI_CONTROL_IFLG) {
|
|
status = readl(&twsi->status);
|
|
if (status == expected_status)
|
|
return 0;
|
|
else
|
|
return mvtwsi_error(
|
|
MVTWSI_ERROR_WRONG_STATUS,
|
|
control, status, expected_status);
|
|
}
|
|
udelay(10); /* One clock cycle at 100 kHz */
|
|
} while (timeout--);
|
|
status = readl(&twsi->status);
|
|
return mvtwsi_error(MVTWSI_ERROR_TIMEOUT, control, status,
|
|
expected_status);
|
|
}
|
|
|
|
/*
|
|
* Assert the START condition, either in a single I2C transaction
|
|
* or inside back-to-back ones (repeated starts).
|
|
*/
|
|
static int twsi_start(struct i2c_adapter *adap, int expected_status)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
|
|
/* Assert START */
|
|
writel(MVTWSI_CONTROL_TWSIEN | MVTWSI_CONTROL_START |
|
|
MVTWSI_CONTROL_CLEAR_IFLG, &twsi->control);
|
|
/* Wait for controller to process START */
|
|
return twsi_wait(adap, expected_status);
|
|
}
|
|
|
|
/*
|
|
* Send a byte (i2c address or data).
|
|
*/
|
|
static int twsi_send(struct i2c_adapter *adap, u8 byte, int expected_status)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
|
|
/* Write byte to data register for sending */
|
|
writel(byte, &twsi->data);
|
|
/* Clear any pending interrupt -- that will cause sending */
|
|
writel(MVTWSI_CONTROL_TWSIEN | MVTWSI_CONTROL_CLEAR_IFLG,
|
|
&twsi->control);
|
|
/* Wait for controller to receive byte, and check ACK */
|
|
return twsi_wait(adap, expected_status);
|
|
}
|
|
|
|
/*
|
|
* Receive a byte.
|
|
*/
|
|
static int twsi_recv(struct i2c_adapter *adap, u8 *byte, int ack_flag)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
int expected_status, status, control;
|
|
|
|
/* Compute expected status based on passed ACK flag */
|
|
expected_status = ack_flag ? MVTWSI_STATUS_DATA_R_ACK :
|
|
MVTWSI_STATUS_DATA_R_NAK;
|
|
/* Acknowledge *previous state*, and launch receive */
|
|
control = MVTWSI_CONTROL_TWSIEN;
|
|
control |= ack_flag == MVTWSI_READ_ACK ? MVTWSI_CONTROL_ACK : 0;
|
|
writel(control | MVTWSI_CONTROL_CLEAR_IFLG, &twsi->control);
|
|
/* Wait for controller to receive byte, and assert ACK or NAK */
|
|
status = twsi_wait(adap, expected_status);
|
|
/* If we did receive the expected byte, store it */
|
|
if (status == 0)
|
|
*byte = readl(&twsi->data);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Assert the STOP condition.
|
|
* This is also used to force the bus back to idle (SDA = SCL = 1).
|
|
*/
|
|
static int twsi_stop(struct i2c_adapter *adap, int status)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
int control, stop_status;
|
|
int timeout = 1000;
|
|
|
|
/* Assert STOP */
|
|
control = MVTWSI_CONTROL_TWSIEN | MVTWSI_CONTROL_STOP;
|
|
writel(control | MVTWSI_CONTROL_CLEAR_IFLG, &twsi->control);
|
|
/* Wait for IDLE; IFLG won't rise, so we can't use twsi_wait() */
|
|
do {
|
|
stop_status = readl(&twsi->status);
|
|
if (stop_status == MVTWSI_STATUS_IDLE)
|
|
break;
|
|
udelay(10); /* One clock cycle at 100 kHz */
|
|
} while (timeout--);
|
|
control = readl(&twsi->control);
|
|
if (stop_status != MVTWSI_STATUS_IDLE)
|
|
if (status == 0)
|
|
status = mvtwsi_error(
|
|
MVTWSI_ERROR_TIMEOUT,
|
|
control, status, MVTWSI_STATUS_IDLE);
|
|
return status;
|
|
}
|
|
|
|
static unsigned int twsi_calc_freq(const int n, const int m)
|
|
{
|
|
#ifdef CONFIG_SUNXI
|
|
return CONFIG_SYS_TCLK / (10 * (m + 1) * (1 << n));
|
|
#else
|
|
return CONFIG_SYS_TCLK / (10 * (m + 1) * (2 << n));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Reset controller.
|
|
* Controller reset also resets the baud rate and slave address, so
|
|
* they must be re-established afterwards.
|
|
*/
|
|
static void twsi_reset(struct i2c_adapter *adap)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
|
|
/* Reset controller */
|
|
writel(0, &twsi->soft_reset);
|
|
/* Wait 2 ms -- this is what the Marvell LSP does */
|
|
udelay(20000);
|
|
}
|
|
|
|
/*
|
|
* Sets baud to the highest possible value not exceeding the requested one.
|
|
*/
|
|
static unsigned int twsi_i2c_set_bus_speed(struct i2c_adapter *adap,
|
|
unsigned int requested_speed)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
unsigned int tmp_speed, highest_speed, n, m;
|
|
unsigned int baud = 0x44; /* Baud rate after controller reset */
|
|
|
|
highest_speed = 0;
|
|
/* Successively try m, n combinations, and use the combination
|
|
* resulting in the largest speed that's not above the requested
|
|
* speed */
|
|
for (n = 0; n < 8; n++) {
|
|
for (m = 0; m < 16; m++) {
|
|
tmp_speed = twsi_calc_freq(n, m);
|
|
if ((tmp_speed <= requested_speed) &&
|
|
(tmp_speed > highest_speed)) {
|
|
highest_speed = tmp_speed;
|
|
baud = (m << 3) | n;
|
|
}
|
|
}
|
|
}
|
|
writel(baud, &twsi->baudrate);
|
|
return 0;
|
|
}
|
|
|
|
static void twsi_i2c_init(struct i2c_adapter *adap, int speed, int slaveadd)
|
|
{
|
|
struct mvtwsi_registers *twsi = twsi_get_base(adap);
|
|
|
|
/* Reset controller */
|
|
twsi_reset(adap);
|
|
/* Set speed */
|
|
twsi_i2c_set_bus_speed(adap, speed);
|
|
/* Set slave address; even though we don't use it */
|
|
writel(slaveadd, &twsi->slave_address);
|
|
writel(0, &twsi->xtnd_slave_addr);
|
|
/* Assert STOP, but don't care for the result */
|
|
(void) twsi_stop(adap, 0);
|
|
}
|
|
|
|
/*
|
|
* Begin I2C transaction with expected start status, at given address.
|
|
* Expected address status will derive from direction bit (bit 0) in addr.
|
|
*/
|
|
static int i2c_begin(struct i2c_adapter *adap, int expected_start_status,
|
|
u8 addr)
|
|
{
|
|
int status, expected_addr_status;
|
|
|
|
/* Compute the expected address status from the direction bit in
|
|
* the address byte */
|
|
if (addr & 1) /* Reading */
|
|
expected_addr_status = MVTWSI_STATUS_ADDR_R_ACK;
|
|
else /* Writing */
|
|
expected_addr_status = MVTWSI_STATUS_ADDR_W_ACK;
|
|
/* Assert START */
|
|
status = twsi_start(adap, expected_start_status);
|
|
/* Send out the address if the start went well */
|
|
if (status == 0)
|
|
status = twsi_send(adap, addr, expected_addr_status);
|
|
/* Return 0, or the status of the first failure */
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Begin read, nak data byte, end.
|
|
*/
|
|
static int twsi_i2c_probe(struct i2c_adapter *adap, uchar chip)
|
|
{
|
|
u8 dummy_byte;
|
|
int status;
|
|
|
|
/* Begin i2c read */
|
|
status = i2c_begin(adap, MVTWSI_STATUS_START, (chip << 1) | 1);
|
|
/* Dummy read was accepted: receive byte, but NAK it. */
|
|
if (status == 0)
|
|
status = twsi_recv(adap, &dummy_byte, MVTWSI_READ_NAK);
|
|
/* Stop transaction */
|
|
twsi_stop(adap, 0);
|
|
/* Return 0, or the status of the first failure */
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Begin write, send address byte(s), begin read, receive data bytes, end.
|
|
*
|
|
* NOTE: Some devices want a stop right before the second start, while some
|
|
* will choke if it is there. Since deciding this is not yet supported in
|
|
* higher level APIs, we need to make a decision here, and for the moment that
|
|
* will be a repeated start without a preceding stop.
|
|
*/
|
|
static int twsi_i2c_read(struct i2c_adapter *adap, uchar chip, uint addr,
|
|
int alen, uchar *data, int length)
|
|
{
|
|
int status;
|
|
|
|
/* Begin i2c write to send the address bytes */
|
|
status = i2c_begin(adap, MVTWSI_STATUS_START, (chip << 1));
|
|
/* Send address bytes */
|
|
while ((status == 0) && alen--)
|
|
status = twsi_send(adap, addr >> (8*alen),
|
|
MVTWSI_STATUS_DATA_W_ACK);
|
|
/* Begin i2c read to receive data bytes */
|
|
if (status == 0)
|
|
status = i2c_begin(adap, MVTWSI_STATUS_REPEATED_START,
|
|
(chip << 1) | 1);
|
|
/* Receive actual data bytes; set NAK if we if we have nothing more to
|
|
* read */
|
|
while ((status == 0) && length--)
|
|
status = twsi_recv(adap, data++,
|
|
length > 0 ?
|
|
MVTWSI_READ_ACK : MVTWSI_READ_NAK);
|
|
/* Stop transaction */
|
|
status = twsi_stop(adap, status);
|
|
/* Return 0, or the status of the first failure */
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Begin write, send address byte(s), send data bytes, end.
|
|
*/
|
|
static int twsi_i2c_write(struct i2c_adapter *adap, uchar chip, uint addr,
|
|
int alen, uchar *data, int length)
|
|
{
|
|
int status;
|
|
|
|
/* Begin i2c write to send first the address bytes, then the
|
|
* data bytes */
|
|
status = i2c_begin(adap, MVTWSI_STATUS_START, (chip << 1));
|
|
/* Send address bytes */
|
|
while ((status == 0) && alen--)
|
|
status = twsi_send(adap, addr >> (8*alen),
|
|
MVTWSI_STATUS_DATA_W_ACK);
|
|
/* Send data bytes */
|
|
while ((status == 0) && (length-- > 0))
|
|
status = twsi_send(adap, *(data++), MVTWSI_STATUS_DATA_W_ACK);
|
|
/* Stop transaction */
|
|
status = twsi_stop(adap, status);
|
|
/* Return 0, or the status of the first failure */
|
|
return status;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE0
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi0, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 0)
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE1
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi1, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 1)
|
|
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE2
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi2, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 2)
|
|
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE3
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi3, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 3)
|
|
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE4
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi4, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 4)
|
|
|
|
#endif
|
|
#ifdef CONFIG_I2C_MVTWSI_BASE5
|
|
U_BOOT_I2C_ADAP_COMPLETE(twsi5, twsi_i2c_init, twsi_i2c_probe,
|
|
twsi_i2c_read, twsi_i2c_write,
|
|
twsi_i2c_set_bus_speed,
|
|
CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE, 5)
|
|
|
|
#endif
|