When starting the Exynos5250 port, a lot of unneeded u-boot code was imported. This is an attempt to get rid of a lot of unneeded code before the port is used as a basis for further ARM ports. There is a lot more that can be done, including cleaning up the 5250's Kconfig file. Change-Id: I2d88676c436eea4b21bcb62f40018af9fabb3016 Signed-off-by: Stefan Reinauer <reinauer@google.com> Signed-off-by: Gabe Black <gabeblack@chromium.org> Reviewed-on: http://review.coreboot.org/3642 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
406 lines
9.4 KiB
C
406 lines
9.4 KiB
C
/*
|
|
* This file is part of the coreboot project.
|
|
*
|
|
* (C) Copyright 2002
|
|
* David Mueller, ELSOFT AG, d.mueller@elsoft.ch
|
|
*
|
|
* 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; version 2 of the License.
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <console/console.h>
|
|
#include <delay.h>
|
|
#include <arch/io.h>
|
|
#include <device/i2c.h>
|
|
#include "clk.h"
|
|
#include "i2c.h"
|
|
#include "pinmux.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 */
|
|
};
|
|
|
|
static struct s3c24x0_i2c_bus i2c_buses[] = {
|
|
{
|
|
.bus_num = 0,
|
|
.regs = (struct s3c24x0_i2c *)0x12c60000,
|
|
.periph_id = PERIPH_ID_I2C0,
|
|
},
|
|
{
|
|
.bus_num = 1,
|
|
.regs = (struct s3c24x0_i2c *)0x12c70000,
|
|
.periph_id = PERIPH_ID_I2C1,
|
|
},
|
|
{
|
|
.bus_num = 2,
|
|
.regs = (struct s3c24x0_i2c *)0x12c80000,
|
|
.periph_id = PERIPH_ID_I2C2,
|
|
},
|
|
{
|
|
.bus_num = 3,
|
|
.regs = (struct s3c24x0_i2c *)0x12c90000,
|
|
.periph_id = PERIPH_ID_I2C3,
|
|
},
|
|
{
|
|
.bus_num = 4,
|
|
.regs = (struct s3c24x0_i2c *)0x12ca0000,
|
|
.periph_id = PERIPH_ID_I2C4,
|
|
},
|
|
{
|
|
.bus_num = 5,
|
|
.regs = (struct s3c24x0_i2c *)0x12cb0000,
|
|
.periph_id = PERIPH_ID_I2C5,
|
|
},
|
|
{
|
|
.bus_num = 6,
|
|
.regs = (struct s3c24x0_i2c *)0x12cc0000,
|
|
.periph_id = PERIPH_ID_I2C6,
|
|
},
|
|
{
|
|
.bus_num = 7,
|
|
.regs = (struct s3c24x0_i2c *)0x12cd0000,
|
|
.periph_id = PERIPH_ID_I2C7,
|
|
},
|
|
};
|
|
|
|
static int WaitForXfer(struct s3c24x0_i2c *i2c)
|
|
{
|
|
int i;
|
|
|
|
i = I2C_XFER_TIMEOUT_MS * 20;
|
|
while (!(readl(&i2c->iiccon) & I2CCON_IRPND)) {
|
|
if (i == 0) {
|
|
printk(BIOS_ERR, "%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);
|
|
}
|
|
|
|
static void i2c_ch_init(struct s3c24x0_i2c_bus *bus, int speed, int slaveadd)
|
|
{
|
|
unsigned long freq, pres = 16, div;
|
|
unsigned long val;
|
|
|
|
freq = clock_get_periph_rate(bus->periph_id);
|
|
/* 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 */
|
|
val = (div & 0x0F) | 0xA0 | ((pres == 512) ? 0x40 : 0);
|
|
writel(val, &bus->regs->iiccon);
|
|
|
|
/* init to SLAVE RECEIVE mode and clear I2CADDn */
|
|
writel(0, &bus->regs->iicstat);
|
|
writel(slaveadd, &bus->regs->iicadd);
|
|
/* program Master Transmit (and implicit STOP) */
|
|
writel(I2C_MODE_MT | I2C_TXRX_ENA, &bus->regs->iicstat);
|
|
}
|
|
|
|
/*
|
|
* MULTI BUS I2C support
|
|
*/
|
|
static void i2c_bus_init(struct s3c24x0_i2c_bus *bus, int speed, int slaveadd)
|
|
{
|
|
exynos_pinmux_config(bus->periph_id, 0);
|
|
i2c_ch_init(bus, speed, slaveadd);
|
|
}
|
|
|
|
/*
|
|
* 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(unsigned bus_num, int speed, int slaveadd)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
int i;
|
|
|
|
i2c = &i2c_buses[bus_num];
|
|
i2c_bus_init(i2c, speed, slaveadd);
|
|
|
|
/* 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--;
|
|
}
|
|
|
|
i2c_ch_init(i2c, 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 */
|
|
printk(BIOS_ERR, "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) {
|
|
printk(BIOS_ERR, "%s: bus busy\n", __func__);
|
|
return I2C_NOK_TOUT;
|
|
}
|
|
|
|
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) {
|
|
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:
|
|
printk(BIOS_ERR, "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_read(unsigned bus, unsigned chip, unsigned addr,
|
|
unsigned alen, unsigned char *buf, unsigned len)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
unsigned char xaddr[4];
|
|
int ret;
|
|
|
|
if (alen > 4) {
|
|
printk(BIOS_ERR, "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;
|
|
}
|
|
|
|
i2c = &i2c_buses[bus];
|
|
ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, &xaddr[4 - alen],
|
|
alen, buf, len);
|
|
if (ret) {
|
|
printk(BIOS_ERR, "I2c read: failed %d\n", ret);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int i2c_write(unsigned bus, unsigned chip, unsigned addr,
|
|
unsigned alen, unsigned char *buf, unsigned len)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c;
|
|
unsigned char xaddr[4];
|
|
int ret;
|
|
|
|
if (alen > 4) {
|
|
printk(BIOS_ERR, "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;
|
|
}
|
|
|
|
i2c = &i2c_buses[bus];
|
|
ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1, &xaddr[4 - alen],
|
|
alen, buf, len);
|
|
|
|
return ret != 0;
|
|
}
|