Snow's AP, EC, PMU, and smarty battery share a bus. Both the AP and EC can act as a master, so to avoid conflicts an arbitration mechanism consisting of two GPIOs is used. By default, the AP "owns" the bus unless it is off (in which case the EC doesn't monitor the arbitration pins). This means the boot firmware does not need to worry about these lines. The payload may if it needs to communicate with the EC, though. In any case, board-specific bus arbitration logic does not belong in a low-level driver that is supposed to be generic for an entire CPU family. If the payload needs to talk to the EC, we'll deal with it there. Change-Id: I0774d4592af2b21b6ad668441532c5ceab988404 Signed-off-by: David Hendricks <dhendrix@chromium.org> Reviewed-on: http://review.coreboot.org/2272 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
624 lines
15 KiB
C
624 lines
15 KiB
C
/*
|
|
* (C) Copyright 2002
|
|
* David Mueller, ELSOFT AG, d.mueller@elsoft.ch
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* 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 2 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*/
|
|
|
|
/* This code should work for both the S3C2400 and the S3C2410
|
|
* as they seem to have the same I2C controller inside.
|
|
* The different address mapping is handled by the s3c24xx.h files below.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <arch/io.h>
|
|
#include "clk.h"
|
|
#include "cpu/samsung/exynos5-common/clk.h"
|
|
#include "cpu/samsung/exynos5250/cpu.h"
|
|
#include "gpio.h"
|
|
#include "cpu/samsung/exynos5250/gpio.h"
|
|
#include "cpu/samsung/exynos5250/pinmux.h"
|
|
|
|
//#include <fdtdec.h>
|
|
#include "device/i2c.h"
|
|
#include "s3c24x0_i2c.h"
|
|
|
|
#define I2C_WRITE 0
|
|
#define I2C_READ 1
|
|
|
|
#define I2C_OK 0
|
|
#define I2C_NOK 1
|
|
#define I2C_NACK 2
|
|
#define I2C_NOK_LA 3 /* Lost arbitration */
|
|
#define I2C_NOK_TOUT 4 /* time out */
|
|
|
|
#define I2CSTAT_BSY 0x20 /* Busy bit */
|
|
#define I2CSTAT_NACK 0x01 /* Nack bit */
|
|
#define I2CCON_ACKGEN 0x80 /* Acknowledge generation */
|
|
#define I2CCON_IRPND 0x10 /* Interrupt pending bit */
|
|
#define I2C_MODE_MT 0xC0 /* Master Transmit Mode */
|
|
#define I2C_MODE_MR 0x80 /* Master Receive Mode */
|
|
#define I2C_START_STOP 0x20 /* START / STOP */
|
|
#define I2C_TXRX_ENA 0x10 /* I2C Tx/Rx enable */
|
|
|
|
/* The timeouts we live by */
|
|
enum {
|
|
I2C_XFER_TIMEOUT_MS = 35, /* xfer to complete */
|
|
I2C_INIT_TIMEOUT_MS = 1000, /* bus free on init */
|
|
I2C_IDLE_TIMEOUT_MS = 100, /* waiting for bus idle */
|
|
I2C_STOP_TIMEOUT_US = 200, /* waiting for stop events */
|
|
};
|
|
|
|
/* We should not rely on any particular ordering of these IDs */
|
|
#if 0
|
|
#ifndef CONFIG_OF_CONTROL
|
|
static enum periph_id periph_for_dev[EXYNOS_I2C_MAX_CONTROLLERS] = {
|
|
PERIPH_ID_I2C0,
|
|
PERIPH_ID_I2C1,
|
|
PERIPH_ID_I2C2,
|
|
PERIPH_ID_I2C3,
|
|
PERIPH_ID_I2C4,
|
|
PERIPH_ID_I2C5,
|
|
PERIPH_ID_I2C6,
|
|
PERIPH_ID_I2C7,
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
static unsigned int g_current_bus __attribute__((section(".data")));
|
|
static struct s3c24x0_i2c *g_early_i2c_config __attribute__((section(".data")));
|
|
|
|
static struct s3c24x0_i2c_bus i2c_bus[EXYNOS_I2C_MAX_CONTROLLERS]
|
|
__attribute__((section(".data")));
|
|
static int i2c_busses __attribute__((section(".data")));
|
|
|
|
void i2c_set_early_reg(unsigned int base)
|
|
{
|
|
g_early_i2c_config = (struct s3c24x0_i2c *)base;
|
|
}
|
|
|
|
static struct s3c24x0_i2c_bus *get_bus(int bus_idx)
|
|
{
|
|
/* If an early i2c config exists we just use that */
|
|
if (g_early_i2c_config) {
|
|
/* FIXME: value not retained from i2c_set_early_reg()? (but then, how
|
|
* did if (!i2c) check pass earlier on? Corrupt value? */
|
|
i2c_bus[0].regs = g_early_i2c_config;
|
|
return &i2c_bus[0];
|
|
}
|
|
|
|
if (bus_idx < i2c_busses)
|
|
return &i2c_bus[bus_idx];
|
|
debug("Undefined bus: %d\n", bus_idx);
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct exynos5_gpio_part1 *exynos_get_base_gpio1(void)
|
|
{
|
|
return (struct exynos5_gpio_part1 *)(EXYNOS5_GPIO_PART1_BASE);
|
|
}
|
|
|
|
static int WaitForXfer(struct s3c24x0_i2c *i2c)
|
|
{
|
|
int i;
|
|
|
|
i = I2C_XFER_TIMEOUT_MS * 20;
|
|
while (!(readl(&i2c->iiccon) & I2CCON_IRPND)) {
|
|
if (i == 0) {
|
|
debug("%s: i2c xfer timeout\n", __func__);
|
|
return I2C_NOK_TOUT;
|
|
}
|
|
udelay(50);
|
|
i--;
|
|
}
|
|
|
|
return I2C_OK;
|
|
}
|
|
|
|
static int IsACK(struct s3c24x0_i2c *i2c)
|
|
{
|
|
return !(readl(&i2c->iicstat) & I2CSTAT_NACK);
|
|
}
|
|
|
|
static void ReadWriteByte(struct s3c24x0_i2c *i2c)
|
|
{
|
|
uint32_t x;
|
|
|
|
x = readl(&i2c->iiccon);
|
|
writel(x & ~I2CCON_IRPND, &i2c->iiccon);
|
|
/* FIXME(dhendrix): cannot use nested macro (compilation failure) */
|
|
// writel(readl(&i2c->iiccon) & ~I2CCON_IRPND, &i2c->iiccon);
|
|
}
|
|
|
|
static void i2c_ch_init(struct s3c24x0_i2c *i2c, int speed, int slaveadd)
|
|
{
|
|
ulong freq, pres = 16, div;
|
|
|
|
freq = clock_get_periph_rate(PERIPH_ID_I2C0);
|
|
/* calculate prescaler and divisor values */
|
|
if ((freq / pres / (16 + 1)) > speed)
|
|
/* set prescaler to 512 */
|
|
pres = 512;
|
|
|
|
div = 0;
|
|
|
|
while ((freq / pres / (div + 1)) > speed)
|
|
div++;
|
|
|
|
/* set prescaler, divisor according to freq, also set ACKGEN, IRQ */
|
|
writel((div & 0x0F) | 0xA0 | ((pres == 512) ? 0x40 : 0), &i2c->iiccon);
|
|
|
|
/* init to SLAVE REVEIVE and set slaveaddr */
|
|
writel(0, &i2c->iicstat);
|
|
writel(slaveadd, &i2c->iicadd);
|
|
/* program Master Transmit (and implicit STOP) */
|
|
writel(I2C_MODE_MT | I2C_TXRX_ENA, &i2c->iicstat);
|
|
}
|
|
|
|
/* TODO: determine if this is necessary to init board using FDT-provided info */
|
|
#if 0
|
|
void board_i2c_init(const void *blob)
|
|
{
|
|
/*
|
|
* Turn off the early i2c configuration and init the i2c properly,
|
|
* this is done here to enable the use of i2c configs from FDT.
|
|
*/
|
|
i2c_set_early_reg(0);
|
|
|
|
#ifdef CONFIG_OF_CONTROL
|
|
int node_list[EXYNOS_I2C_MAX_CONTROLLERS];
|
|
int i, count;
|
|
|
|
count = fdtdec_find_aliases_for_id(blob, "i2c",
|
|
COMPAT_SAMSUNG_S3C2440_I2C, node_list,
|
|
EXYNOS_I2C_MAX_CONTROLLERS);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct s3c24x0_i2c_bus *bus;
|
|
int node = node_list[i];
|
|
|
|
if (node < 0)
|
|
continue;
|
|
bus = &i2c_bus[i2c_busses];
|
|
bus->regs = (struct s3c24x0_i2c *)
|
|
fdtdec_get_addr(blob, node, "reg");
|
|
bus->id = (enum periph_id)
|
|
fdtdec_get_int(blob, node, "samsung,periph-id", -1);
|
|
bus->node = node;
|
|
bus->bus_num = i2c_busses++;
|
|
}
|
|
#else
|
|
int i;
|
|
|
|
for (i = 0; i < EXYNOS_I2C_MAX_CONTROLLERS; i++) {
|
|
uintptr_t reg_addr = samsung_get_base_i2c() +
|
|
EXYNOS_I2C_SPACING * i;
|
|
|
|
i2c_bus[i].regs = (struct s3c24x0_i2c_bus *)reg_addr;
|
|
i2c_bus[i].id = periph_for_dev[i];
|
|
}
|
|
i2c_busses = EXYNOS_I2C_MAX_CONTROLLERS;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* MULTI BUS I2C support
|
|
*/
|
|
/*
|
|
* FIXME(dhendrix): not sure why this had to be guarded, but the code
|
|
* should probably go into an exynos5-specific .c file if it really is
|
|
* not generic.
|
|
*/
|
|
//#ifdef CONFIG_EXYNOS5
|
|
static void i2c_bus_init(struct s3c24x0_i2c_bus *i2c, unsigned int bus)
|
|
{
|
|
exynos_pinmux_config(i2c->id, 0);
|
|
|
|
i2c_ch_init(i2c->regs, CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
|
|
}
|
|
//#else
|
|
//#error "should not be here"
|
|
//static void i2c_bus_init(struct s3c24x0_i2c_bus *i2c, unsigned int bus) {}
|
|
//#endif
|
|
|
|
#ifdef CONFIG_I2C_MULTI_BUS
|
|
int i2c_set_bus_num(unsigned int bus)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
|
|
i2c = get_bus(bus);
|
|
if (!i2c)
|
|
return -1;
|
|
g_current_bus = bus;
|
|
i2c_bus_init(i2c, g_current_bus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int i2c_get_bus_num(void)
|
|
{
|
|
return g_current_bus;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_OF_CONTROL
|
|
int i2c_get_bus_num_fdt(const void *blob, int node)
|
|
{
|
|
enum fdt_compat_id compat;
|
|
fdt_addr_t reg;
|
|
int i;
|
|
|
|
compat = fdtdec_lookup(blob, node);
|
|
if (compat != COMPAT_SAMSUNG_S3C2440_I2C) {
|
|
debug("%s: Not a supported I2C node\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
reg = fdtdec_get_addr(blob, node, "reg");
|
|
for (i = 0; i < i2c_busses; i++)
|
|
if (reg == (fdt_addr_t)(uintptr_t)i2c_bus[i].regs)
|
|
return i;
|
|
|
|
debug("%s: Can't find any matched I2C bus\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
int i2c_reset_port_fdt(const void *blob, int node)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
|
|
int bus;
|
|
|
|
bus = i2c_get_bus_num_fdt(blob, node);
|
|
if (bus < 0) {
|
|
printf("could not get bus for node %d\n", node);
|
|
return -1;
|
|
}
|
|
i2c = get_bus(bus);
|
|
if (!i2c) {
|
|
printf("get_bus() failed for node node %d\n", node);
|
|
return -1;
|
|
}
|
|
|
|
i2c_ch_init(i2c->regs, CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Verify the whether I2C ACK was received or not
|
|
*
|
|
* @param i2c pointer to I2C register base
|
|
* @param buf array of data
|
|
* @param len length of data
|
|
* return I2C_OK when transmission done
|
|
* I2C_NACK otherwise
|
|
*/
|
|
static int i2c_send_verify(struct s3c24x0_i2c *i2c, unsigned char buf[],
|
|
unsigned char len)
|
|
{
|
|
int i, result = I2C_OK;
|
|
|
|
if (IsACK(i2c)) {
|
|
for (i = 0; (i < len) && (result == I2C_OK); i++) {
|
|
writel(buf[i], &i2c->iicds);
|
|
ReadWriteByte(i2c);
|
|
result = WaitForXfer(i2c);
|
|
if (result == I2C_OK && !IsACK(i2c))
|
|
result = I2C_NACK;
|
|
}
|
|
} else {
|
|
result = I2C_NACK;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void i2c_init(int speed, int slaveadd)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
struct exynos5_gpio_part1 *gpio;
|
|
int i;
|
|
uint32_t x;
|
|
|
|
/* By default i2c channel 0 is the current bus */
|
|
g_current_bus = 0;
|
|
|
|
i2c = get_bus(g_current_bus);
|
|
if (!i2c)
|
|
return;
|
|
|
|
i2c_bus_init(i2c, g_current_bus);
|
|
|
|
/* wait for some time to give previous transfer a chance to finish */
|
|
i = I2C_INIT_TIMEOUT_MS * 20;
|
|
while ((readl(&i2c->regs->iicstat) & I2CSTAT_BSY) && (i > 0)) {
|
|
udelay(50);
|
|
i--;
|
|
}
|
|
|
|
gpio = exynos_get_base_gpio1();
|
|
/* FIXME(dhendrix): cannot use nested macro (compilation failure) */
|
|
// writel((readl(&gpio->b3.con) & ~0x00FF) | 0x0022, &gpio->b3.con);
|
|
x = readl(&gpio->b3.con);
|
|
writel((x & ~0x00FF) | 0x0022, &gpio->b3.con);
|
|
|
|
i2c_ch_init(i2c->regs, speed, slaveadd);
|
|
}
|
|
|
|
/*
|
|
* Send a STOP event and wait for it to have completed
|
|
*
|
|
* @param mode If it is a master transmitter or receiver
|
|
* @return I2C_OK if the line became idle before timeout I2C_NOK_TOUT otherwise
|
|
*/
|
|
static int i2c_send_stop(struct s3c24x0_i2c *i2c, int mode)
|
|
{
|
|
int timeout;
|
|
|
|
/* Setting the STOP event to fire */
|
|
writel(mode | I2C_TXRX_ENA, &i2c->iicstat);
|
|
ReadWriteByte(i2c);
|
|
|
|
/* Wait for the STOP to send and the bus to go idle */
|
|
for (timeout = I2C_STOP_TIMEOUT_US; timeout > 0; timeout -= 5) {
|
|
if (!(readl(&i2c->iicstat) & I2CSTAT_BSY))
|
|
return I2C_OK;
|
|
udelay(5);
|
|
}
|
|
|
|
return I2C_NOK_TOUT;
|
|
}
|
|
|
|
/*
|
|
* cmd_type is 0 for write, 1 for read.
|
|
*
|
|
* addr_len can take any value from 0-255, it is only limited
|
|
* by the char, we could make it larger if needed. If it is
|
|
* 0 we skip the address write cycle.
|
|
*/
|
|
static int i2c_transfer(struct s3c24x0_i2c *i2c,
|
|
unsigned char cmd_type,
|
|
unsigned char chip,
|
|
unsigned char addr[],
|
|
unsigned char addr_len,
|
|
unsigned char data[],
|
|
unsigned short data_len)
|
|
{
|
|
int i, result, stop_bit_result;
|
|
uint32_t x;
|
|
|
|
if (data == 0 || data_len == 0) {
|
|
/* Don't support data transfer of no length or to address 0 */
|
|
debug("i2c_transfer: bad call\n");
|
|
return I2C_NOK;
|
|
}
|
|
|
|
/* Check I2C bus idle */
|
|
i = I2C_IDLE_TIMEOUT_MS * 20;
|
|
while ((readl(&i2c->iicstat) & I2CSTAT_BSY) && (i > 0)) {
|
|
udelay(50);
|
|
i--;
|
|
}
|
|
|
|
if (readl(&i2c->iicstat) & I2CSTAT_BSY) {
|
|
debug("%s: bus busy\n", __func__);
|
|
return I2C_NOK_TOUT;
|
|
}
|
|
|
|
/* FIXME(dhendrix): cannot use nested macro (compilation failure) */
|
|
//writel(readl(&i2c->iiccon) | I2CCON_ACKGEN, &i2c->iiccon);
|
|
x = readl(&i2c->iiccon);
|
|
writel(x | I2CCON_ACKGEN, &i2c->iiccon);
|
|
|
|
if (addr && addr_len) {
|
|
writel(chip, &i2c->iicds);
|
|
/* send START */
|
|
writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP,
|
|
&i2c->iicstat);
|
|
if (WaitForXfer(i2c) == I2C_OK)
|
|
result = i2c_send_verify(i2c, addr, addr_len);
|
|
else
|
|
result = I2C_NACK;
|
|
} else
|
|
result = I2C_NACK;
|
|
|
|
switch (cmd_type) {
|
|
case I2C_WRITE:
|
|
if (result == I2C_OK)
|
|
result = i2c_send_verify(i2c, data, data_len);
|
|
else {
|
|
writel(chip, &i2c->iicds);
|
|
/* send START */
|
|
writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP,
|
|
&i2c->iicstat);
|
|
if (WaitForXfer(i2c) == I2C_OK)
|
|
result = i2c_send_verify(i2c, data, data_len);
|
|
}
|
|
|
|
if (result == I2C_OK)
|
|
result = WaitForXfer(i2c);
|
|
|
|
stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MT);
|
|
break;
|
|
|
|
case I2C_READ:
|
|
{
|
|
int was_ok = (result == I2C_OK);
|
|
|
|
writel(chip, &i2c->iicds);
|
|
/* resend START */
|
|
writel(I2C_MODE_MR | I2C_TXRX_ENA |
|
|
I2C_START_STOP, &i2c->iicstat);
|
|
ReadWriteByte(i2c);
|
|
result = WaitForXfer(i2c);
|
|
|
|
if (was_ok || IsACK(i2c)) {
|
|
i = 0;
|
|
while ((i < data_len) && (result == I2C_OK)) {
|
|
/* disable ACK for final READ */
|
|
if (i == data_len - 1) {
|
|
/* FIXME(dhendrix): nested macro */
|
|
#if 0
|
|
writel(readl(&i2c->iiccon) &
|
|
~I2CCON_ACKGEN,
|
|
&i2c->iiccon);
|
|
#endif
|
|
x = readl(&i2c->iiccon) & ~I2CCON_ACKGEN;
|
|
writel(x, &i2c->iiccon);
|
|
}
|
|
ReadWriteByte(i2c);
|
|
result = WaitForXfer(i2c);
|
|
data[i] = readl(&i2c->iicds);
|
|
i++;
|
|
}
|
|
} else {
|
|
result = I2C_NACK;
|
|
}
|
|
|
|
stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MR);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
debug("i2c_transfer: bad call\n");
|
|
result = stop_bit_result = I2C_NOK;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the transmission went fine, then only the stop bit was left to
|
|
* fail. Otherwise, the real failure we're interested in came before
|
|
* that, during the actual transmission.
|
|
*/
|
|
return (result == I2C_OK) ? stop_bit_result : result;
|
|
}
|
|
|
|
int i2c_probe(uchar chip)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
uchar buf[1];
|
|
int ret;
|
|
|
|
i2c = get_bus(g_current_bus);
|
|
if (!i2c)
|
|
return -1;
|
|
buf[0] = 0;
|
|
|
|
/*
|
|
* What is needed is to send the chip address and verify that the
|
|
* address was <ACK>ed (i.e. there was a chip at that address which
|
|
* drove the data line low).
|
|
*/
|
|
ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, 0, 0, buf, 1);
|
|
|
|
return ret != I2C_OK;
|
|
}
|
|
|
|
int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
uchar xaddr[4];
|
|
int ret;
|
|
|
|
if (alen > 4) {
|
|
debug("I2C read: addr len %d not supported\n", alen);
|
|
return 1;
|
|
}
|
|
|
|
if (alen > 0) {
|
|
xaddr[0] = (addr >> 24) & 0xFF;
|
|
xaddr[1] = (addr >> 16) & 0xFF;
|
|
xaddr[2] = (addr >> 8) & 0xFF;
|
|
xaddr[3] = addr & 0xFF;
|
|
}
|
|
|
|
#ifdef CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW
|
|
/*
|
|
* EEPROM chips that implement "address overflow" are ones
|
|
* like Catalyst 24WC04/08/16 which has 9/10/11 bits of
|
|
* address and the extra bits end up in the "chip address"
|
|
* bit slots. This makes a 24WC08 (1Kbyte) chip look like
|
|
* four 256 byte chips.
|
|
*
|
|
* Note that we consider the length of the address field to
|
|
* still be one byte because the extra address bits are
|
|
* hidden in the chip address.
|
|
*/
|
|
if (alen > 0)
|
|
chip |= ((addr >> (alen * 8)) &
|
|
CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW);
|
|
#endif
|
|
i2c = get_bus(g_current_bus);
|
|
if (!i2c)
|
|
return -1;
|
|
ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, &xaddr[4 - alen],
|
|
alen, buffer, len);
|
|
if (ret) {
|
|
debug("I2c read: failed %d\n", ret);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
uchar xaddr[4];
|
|
int ret;
|
|
|
|
if (alen > 4) {
|
|
debug("I2C write: addr len %d not supported\n", alen);
|
|
return 1;
|
|
}
|
|
|
|
if (alen > 0) {
|
|
xaddr[0] = (addr >> 24) & 0xFF;
|
|
xaddr[1] = (addr >> 16) & 0xFF;
|
|
xaddr[2] = (addr >> 8) & 0xFF;
|
|
xaddr[3] = addr & 0xFF;
|
|
}
|
|
#ifdef CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW
|
|
/*
|
|
* EEPROM chips that implement "address overflow" are ones
|
|
* like Catalyst 24WC04/08/16 which has 9/10/11 bits of
|
|
* address and the extra bits end up in the "chip address"
|
|
* bit slots. This makes a 24WC08 (1Kbyte) chip look like
|
|
* four 256 byte chips.
|
|
*
|
|
* Note that we consider the length of the address field to
|
|
* still be one byte because the extra address bits are
|
|
* hidden in the chip address.
|
|
*/
|
|
if (alen > 0)
|
|
chip |= ((addr >> (alen * 8)) &
|
|
CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW);
|
|
#endif
|
|
i2c = get_bus(g_current_bus);
|
|
if (!i2c)
|
|
return -1;
|
|
|
|
ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1, &xaddr[4 - alen],
|
|
alen, buffer, len);
|
|
|
|
return ret != 0;
|
|
}
|