0f2dd1eff9
Split `i2c.h` into three pieces to ease reuse of the generic defi- nitions. No code is changed. * `i2c.h` - keeps the generic definitions * `i2c_simple.h` - holds the current, limited to one controller driver per board, devicetree independent I2C interface * `i2c_bus.h` - will become the devicetree compatible interface for native I2C (e.g. non-SMBus) controllers Change-Id: I382d45c70f9314588663e1284f264f877469c74d Signed-off-by: Nico Huber <nico.huber@secunet.com> Reviewed-on: https://review.coreboot.org/20845 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org>
294 lines
5.8 KiB
C
294 lines
5.8 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.
|
|
*/
|
|
|
|
#include <compiler.h>
|
|
#include <arch/io.h>
|
|
#include <assert.h>
|
|
#include <console/console.h>
|
|
#include <delay.h>
|
|
#include <device/i2c_simple.h>
|
|
#include <soc/clk.h>
|
|
#include <soc/i2c.h>
|
|
#include <soc/periph.h>
|
|
|
|
struct __packed i2c_regs
|
|
{
|
|
uint8_t con;
|
|
uint8_t _1[3];
|
|
uint8_t stat;
|
|
uint8_t _2[3];
|
|
uint8_t add;
|
|
uint8_t _3[3];
|
|
uint8_t ds;
|
|
uint8_t _4[3];
|
|
uint8_t lc;
|
|
uint8_t _5[3];
|
|
};
|
|
|
|
struct s3c24x0_i2c_bus {
|
|
int bus_num;
|
|
struct i2c_regs *regs;
|
|
enum periph_id periph_id;
|
|
};
|
|
|
|
enum {
|
|
I2cConIntPending = 0x1 << 4,
|
|
I2cConIntEn = 0x1 << 5,
|
|
I2cConAckGen = 0x1 << 7
|
|
};
|
|
|
|
enum {
|
|
I2cStatAck = 0x1 << 0,
|
|
I2cStatAddrZero = 0x1 << 1,
|
|
I2cStatAddrSlave = 0x1 << 2,
|
|
I2cStatArb = 0x1 << 3,
|
|
I2cStatEnable = 0x1 << 4,
|
|
I2cStatStartStop = 0x1 << 5,
|
|
I2cStatBusy = 0x1 << 5,
|
|
|
|
I2cStatModeMask = 0x3 << 6,
|
|
I2cStatSlaveRecv = 0x0 << 6,
|
|
I2cStatSlaveXmit = 0x1 << 6,
|
|
I2cStatMasterRecv = 0x2 << 6,
|
|
I2cStatMasterXmit = 0x3 << 6
|
|
};
|
|
|
|
static struct s3c24x0_i2c_bus i2c_busses[] = {
|
|
{
|
|
.bus_num = 0,
|
|
.regs = (void *)0x12c60000,
|
|
.periph_id = PERIPH_ID_I2C0,
|
|
},
|
|
{
|
|
.bus_num = 1,
|
|
.regs = (void *)0x12c70000,
|
|
.periph_id = PERIPH_ID_I2C1,
|
|
},
|
|
{
|
|
.bus_num = 2,
|
|
.regs = (void *)0x12c80000,
|
|
.periph_id = PERIPH_ID_I2C2,
|
|
},
|
|
{
|
|
.bus_num = 3,
|
|
.regs = (void *)0x12c90000,
|
|
.periph_id = PERIPH_ID_I2C3,
|
|
},
|
|
{
|
|
.bus_num = 4,
|
|
.regs = (void *)0x12ca0000,
|
|
.periph_id = PERIPH_ID_I2C4,
|
|
},
|
|
{
|
|
.bus_num = 5,
|
|
.regs = (void *)0x12cb0000,
|
|
.periph_id = PERIPH_ID_I2C5,
|
|
},
|
|
{
|
|
.bus_num = 6,
|
|
.regs = (void *)0x12cc0000,
|
|
.periph_id = PERIPH_ID_I2C6,
|
|
},
|
|
{
|
|
.bus_num = 7,
|
|
.regs = (void *)0x12cd0000,
|
|
.periph_id = PERIPH_ID_I2C7,
|
|
},
|
|
};
|
|
|
|
|
|
|
|
|
|
static int i2c_int_pending(struct i2c_regs *regs)
|
|
{
|
|
return read8(®s->con) & I2cConIntPending;
|
|
}
|
|
|
|
static void i2c_clear_int(struct i2c_regs *regs)
|
|
{
|
|
write8(®s->con, read8(®s->con) & ~I2cConIntPending);
|
|
}
|
|
|
|
static void i2c_ack_enable(struct i2c_regs *regs)
|
|
{
|
|
write8(®s->con, read8(®s->con) | I2cConAckGen);
|
|
}
|
|
|
|
static void i2c_ack_disable(struct i2c_regs *regs)
|
|
{
|
|
write8(®s->con, read8(®s->con) & ~I2cConAckGen);
|
|
}
|
|
|
|
static int i2c_got_ack(struct i2c_regs *regs)
|
|
{
|
|
return !(read8(®s->stat) & I2cStatAck);
|
|
}
|
|
|
|
static int i2c_wait_for_idle(struct i2c_regs *regs)
|
|
{
|
|
int timeout = 1000 * 100; // 1s.
|
|
while (timeout--) {
|
|
if (!(read8(®s->stat) & I2cStatBusy))
|
|
return 0;
|
|
udelay(10);
|
|
}
|
|
printk(BIOS_ERR, "I2C timeout waiting for idle.\n");
|
|
return 1;
|
|
}
|
|
|
|
static int i2c_wait_for_int(struct i2c_regs *regs)
|
|
{
|
|
int timeout = 1000 * 100; // 1s.
|
|
while (timeout--) {
|
|
if (i2c_int_pending(regs))
|
|
return 0;
|
|
udelay(10);
|
|
}
|
|
printk(BIOS_ERR, "I2C timeout waiting for I2C interrupt.\n");
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
static int i2c_send_stop(struct i2c_regs *regs)
|
|
{
|
|
uint8_t mode = read8(®s->stat) & (I2cStatModeMask);
|
|
write8(®s->stat, mode | I2cStatEnable);
|
|
i2c_clear_int(regs);
|
|
return i2c_wait_for_idle(regs);
|
|
}
|
|
|
|
static int i2c_send_start(struct i2c_regs *regs, int read, int chip)
|
|
{
|
|
write8(®s->ds, chip << 1);
|
|
uint8_t mode = read ? I2cStatMasterRecv : I2cStatMasterXmit;
|
|
write8(®s->stat, mode | I2cStatStartStop | I2cStatEnable);
|
|
i2c_clear_int(regs);
|
|
|
|
if (i2c_wait_for_int(regs))
|
|
return 1;
|
|
|
|
if (!i2c_got_ack(regs)) {
|
|
// Nobody home, but they may just be asleep.
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xmit_buf(struct i2c_regs *regs, uint8_t *data, int len)
|
|
{
|
|
ASSERT(len);
|
|
|
|
i2c_ack_enable(regs);
|
|
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
write8(®s->ds, data[i]);
|
|
|
|
i2c_clear_int(regs);
|
|
if (i2c_wait_for_int(regs))
|
|
return 1;
|
|
|
|
if (!i2c_got_ack(regs)) {
|
|
printk(BIOS_INFO, "I2c nacked.\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_recv_buf(struct i2c_regs *regs, uint8_t *data, int len)
|
|
{
|
|
ASSERT(len);
|
|
|
|
i2c_ack_enable(regs);
|
|
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
if (i == len - 1)
|
|
i2c_ack_disable(regs);
|
|
|
|
i2c_clear_int(regs);
|
|
if (i2c_wait_for_int(regs))
|
|
return 1;
|
|
|
|
data[i] = read8(®s->ds);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int platform_i2c_transfer(unsigned bus, struct i2c_msg *segments,
|
|
int seg_count)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];
|
|
struct i2c_regs *regs = i2c->regs;
|
|
int res = 0;
|
|
|
|
if (!regs || i2c_wait_for_idle(regs))
|
|
return 1;
|
|
|
|
write8(®s->stat, I2cStatMasterXmit | I2cStatEnable);
|
|
|
|
int i;
|
|
for (i = 0; i < seg_count; i++) {
|
|
struct i2c_msg *seg = &segments[i];
|
|
|
|
res = i2c_send_start(regs, seg->flags & I2C_M_RD, seg->slave);
|
|
if (res)
|
|
break;
|
|
if (seg->flags & I2C_M_RD)
|
|
res = i2c_recv_buf(regs, seg->buf, seg->len);
|
|
else
|
|
res = i2c_xmit_buf(regs, seg->buf, seg->len);
|
|
if (res)
|
|
break;
|
|
}
|
|
|
|
return i2c_send_stop(regs) || res;
|
|
}
|
|
|
|
void i2c_init(unsigned bus, int speed, int slaveadd)
|
|
{
|
|
struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];
|
|
|
|
unsigned long freq, pres = 16, div;
|
|
unsigned long val;
|
|
|
|
freq = clock_get_periph_rate(i2c->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);
|
|
write32(&i2c->regs->con, val);
|
|
|
|
// Init to SLAVE RECEIVE mode and clear I2CADDn.
|
|
write32(&i2c->regs->stat, 0);
|
|
write32(&i2c->regs->add, slaveadd);
|
|
// program Master Transmit (and implicit STOP).
|
|
write32(&i2c->regs->stat, I2cStatMasterXmit | I2cStatEnable);
|
|
}
|