Add (hacked-up) s3c24x0_i2c files
These are needed for communicating with the PMIC on Snow. We'll tidy them up as we go along... Change-Id: I197f59927eae0ad66191862d052de2a8873fb22f Signed-off-by: David Hendricks <dhendrix@chromium.org> Reviewed-on: http://review.coreboot.org/2078 Tested-by: build bot (Jenkins) Reviewed-by: Hung-Te Lin <hungte@chromium.org>
This commit is contained in:
parent
d3c7530908
commit
45256b3bfc
|
@ -2,6 +2,7 @@ romstage-y += cpu_info.c
|
||||||
romstage-y += pwm.c # needed by timer.c
|
romstage-y += pwm.c # needed by timer.c
|
||||||
romstage-y += s5p_gpio.c
|
romstage-y += s5p_gpio.c
|
||||||
romstage-y += timer.c
|
romstage-y += timer.c
|
||||||
|
romstage-y += s3c24x0_i2c.c
|
||||||
|
|
||||||
#romstage-y += sromc.c
|
#romstage-y += sromc.c
|
||||||
#romstage-y += wdt.c
|
#romstage-y += wdt.c
|
||||||
|
@ -10,3 +11,4 @@ ramstage-y += cpu_info.c
|
||||||
ramstage-y += pwm.c # needed by timer.c
|
ramstage-y += pwm.c # needed by timer.c
|
||||||
ramstage-y += timer.c
|
ramstage-y += timer.c
|
||||||
ramstage-y += s5p_gpio.c
|
ramstage-y += s5p_gpio.c
|
||||||
|
ramstage-y += s3c24x0_i2c.c
|
||||||
|
|
|
@ -0,0 +1,640 @@
|
||||||
|
/*
|
||||||
|
* (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"
|
||||||
|
|
||||||
|
/* for board_i2c_* */
|
||||||
|
#include "cpu/samsung/exynos5-common/spl.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).
|
||||||
|
*/
|
||||||
|
if (board_i2c_claim_bus(i2c->node)) {
|
||||||
|
debug("I2C cannot claim bus %d\n", i2c->bus_num);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, 0, 0, buf, 1);
|
||||||
|
board_i2c_release_bus(i2c->node);
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (board_i2c_claim_bus(i2c->node)) {
|
||||||
|
debug("I2C cannot claim bus %d\n", i2c->bus_num);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, &xaddr[4 - alen],
|
||||||
|
alen, buffer, len);
|
||||||
|
board_i2c_release_bus(i2c->node);
|
||||||
|
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;
|
||||||
|
if (board_i2c_claim_bus(i2c->node)) {
|
||||||
|
debug("I2C cannot claim bus %d\n", i2c->bus_num);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1, &xaddr[4 - alen],
|
||||||
|
alen, buffer, len);
|
||||||
|
board_i2c_release_bus(i2c->node);
|
||||||
|
|
||||||
|
return ret != 0;
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 Samsung Electronics
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _S3C24X0_I2C_H
|
||||||
|
#define _S3C24X0_I2C_H
|
||||||
|
|
||||||
|
/* FIXME: gross hack */
|
||||||
|
#include "cpu/samsung/exynos5250/periph.h"
|
||||||
|
|
||||||
|
struct s3c24x0_i2c {
|
||||||
|
u32 iiccon;
|
||||||
|
u32 iicstat;
|
||||||
|
u32 iicadd;
|
||||||
|
u32 iicds;
|
||||||
|
u32 iiclc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct s3c24x0_i2c_bus {
|
||||||
|
int node; /* device tree node */
|
||||||
|
int bus_num; /* i2c bus number */
|
||||||
|
struct s3c24x0_i2c *regs;
|
||||||
|
enum periph_id id;
|
||||||
|
};
|
||||||
|
#endif /* _S3C24X0_I2C_H */
|
Loading…
Reference in New Issue