This commit adds on-chip I2C driver used on some old Panasonic UniPhier SoCs. Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com> Reviewed-by: Simon Glass <sjg@chromium.org> Acked-by: Simon Glass <sjg@chromium.org> Acked-by: Heiko Schocher <hs@denx.de>master
parent
b6036bcd2a
commit
26f820f3f1
@ -0,0 +1,239 @@ |
||||
/*
|
||||
* Copyright (C) 2014 Panasonic Corporation |
||||
* Author: Masahiro Yamada <yamada.m@jp.panasonic.com> |
||||
* |
||||
* SPDX-License-Identifier: GPL-2.0+ |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <linux/types.h> |
||||
#include <asm/io.h> |
||||
#include <asm/errno.h> |
||||
#include <dm/device.h> |
||||
#include <dm/root.h> |
||||
#include <i2c.h> |
||||
#include <fdtdec.h> |
||||
|
||||
DECLARE_GLOBAL_DATA_PTR; |
||||
|
||||
struct uniphier_i2c_regs { |
||||
u32 dtrm; /* data transmission */ |
||||
#define I2C_DTRM_STA (1 << 10) |
||||
#define I2C_DTRM_STO (1 << 9) |
||||
#define I2C_DTRM_NACK (1 << 8) |
||||
#define I2C_DTRM_RD (1 << 0) |
||||
u32 drec; /* data reception */ |
||||
#define I2C_DREC_STS (1 << 12) |
||||
#define I2C_DREC_LRB (1 << 11) |
||||
#define I2C_DREC_LAB (1 << 9) |
||||
u32 myad; /* slave address */ |
||||
u32 clk; /* clock frequency control */ |
||||
u32 brst; /* bus reset */ |
||||
#define I2C_BRST_FOEN (1 << 1) |
||||
#define I2C_BRST_BRST (1 << 0) |
||||
u32 hold; /* hold time control */ |
||||
u32 bsts; /* bus status monitor */ |
||||
u32 noise; /* noise filter control */ |
||||
u32 setup; /* setup time control */ |
||||
}; |
||||
|
||||
#define IOBUS_FREQ 100000000 |
||||
|
||||
struct uniphier_i2c_dev { |
||||
struct uniphier_i2c_regs __iomem *regs; /* register base */ |
||||
unsigned long input_clk; /* master clock (Hz) */ |
||||
unsigned long wait_us; /* wait for every byte transfer (us) */ |
||||
}; |
||||
|
||||
static int uniphier_i2c_probe(struct udevice *dev) |
||||
{ |
||||
fdt_addr_t addr; |
||||
fdt_size_t size; |
||||
struct uniphier_i2c_dev *priv = dev_get_priv(dev); |
||||
|
||||
addr = fdtdec_get_addr_size(gd->fdt_blob, dev->of_offset, "reg", &size); |
||||
|
||||
priv->regs = map_sysmem(addr, size); |
||||
|
||||
if (!priv->regs) |
||||
return -ENOMEM; |
||||
|
||||
priv->input_clk = IOBUS_FREQ; |
||||
|
||||
/* deassert reset */ |
||||
writel(0x3, &priv->regs->brst); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int uniphier_i2c_remove(struct udevice *dev) |
||||
{ |
||||
struct uniphier_i2c_dev *priv = dev_get_priv(dev); |
||||
|
||||
unmap_sysmem(priv->regs); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int uniphier_i2c_child_pre_probe(struct udevice *dev) |
||||
{ |
||||
struct dm_i2c_chip *i2c_chip = dev_get_parentdata(dev); |
||||
|
||||
if (dev->of_offset == -1) |
||||
return 0; |
||||
return i2c_chip_ofdata_to_platdata(gd->fdt_blob, dev->of_offset, |
||||
i2c_chip); |
||||
} |
||||
|
||||
static int send_and_recv_byte(struct uniphier_i2c_dev *dev, u32 dtrm) |
||||
{ |
||||
writel(dtrm, &dev->regs->dtrm); |
||||
|
||||
/*
|
||||
* This controller only provides interruption to inform the completion |
||||
* of each byte transfer. (No status register to poll it.) |
||||
* Unfortunately, U-Boot does not have a good support of interrupt. |
||||
* Wait for a while. |
||||
*/ |
||||
udelay(dev->wait_us); |
||||
|
||||
return readl(&dev->regs->drec); |
||||
} |
||||
|
||||
static int send_byte(struct uniphier_i2c_dev *dev, u32 dtrm, bool *stop) |
||||
{ |
||||
int ret = 0; |
||||
u32 drec; |
||||
|
||||
drec = send_and_recv_byte(dev, dtrm); |
||||
|
||||
if (drec & I2C_DREC_LAB) { |
||||
debug("uniphier_i2c: bus arbitration failed\n"); |
||||
*stop = false; |
||||
ret = -EREMOTEIO; |
||||
} |
||||
if (drec & I2C_DREC_LRB) { |
||||
debug("uniphier_i2c: slave did not return ACK\n"); |
||||
ret = -EREMOTEIO; |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
static int uniphier_i2c_transmit(struct uniphier_i2c_dev *dev, uint addr, |
||||
uint len, const u8 *buf, bool *stop) |
||||
{ |
||||
int ret; |
||||
|
||||
debug("%s: addr = %x, len = %d\n", __func__, addr, len); |
||||
|
||||
ret = send_byte(dev, I2C_DTRM_STA | I2C_DTRM_NACK | addr << 1, stop); |
||||
if (ret < 0) |
||||
goto fail; |
||||
|
||||
while (len--) { |
||||
ret = send_byte(dev, I2C_DTRM_NACK | *buf++, stop); |
||||
if (ret < 0) |
||||
goto fail; |
||||
} |
||||
|
||||
fail: |
||||
if (*stop) |
||||
writel(I2C_DTRM_STO | I2C_DTRM_NACK, &dev->regs->dtrm); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int uniphier_i2c_receive(struct uniphier_i2c_dev *dev, uint addr, |
||||
uint len, u8 *buf, bool *stop) |
||||
{ |
||||
int ret; |
||||
|
||||
debug("%s: addr = %x, len = %d\n", __func__, addr, len); |
||||
|
||||
ret = send_byte(dev, I2C_DTRM_STA | I2C_DTRM_NACK | |
||||
I2C_DTRM_RD | addr << 1, stop); |
||||
if (ret < 0) |
||||
goto fail; |
||||
|
||||
while (len--) |
||||
*buf++ = send_and_recv_byte(dev, len ? 0 : I2C_DTRM_NACK); |
||||
|
||||
fail: |
||||
if (*stop) |
||||
writel(I2C_DTRM_STO | I2C_DTRM_NACK, &dev->regs->dtrm); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int uniphier_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, |
||||
int nmsgs) |
||||
{ |
||||
int ret = 0; |
||||
struct uniphier_i2c_dev *dev = dev_get_priv(bus); |
||||
bool stop; |
||||
|
||||
for (; nmsgs > 0; nmsgs--, msg++) { |
||||
/* If next message is read, skip the stop condition */ |
||||
stop = nmsgs > 1 && msg[1].flags & I2C_M_RD ? false : true; |
||||
|
||||
if (msg->flags & I2C_M_RD) |
||||
ret = uniphier_i2c_receive(dev, msg->addr, msg->len, |
||||
msg->buf, &stop); |
||||
else |
||||
ret = uniphier_i2c_transmit(dev, msg->addr, msg->len, |
||||
msg->buf, &stop); |
||||
|
||||
if (ret < 0) |
||||
break; |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int uniphier_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) |
||||
{ |
||||
struct uniphier_i2c_dev *priv = dev_get_priv(bus); |
||||
|
||||
/* max supported frequency is 400 kHz */ |
||||
if (speed > 400000) |
||||
return -EINVAL; |
||||
|
||||
/* bus reset: make sure the bus is idle when change the frequency */ |
||||
writel(0x1, &priv->regs->brst); |
||||
|
||||
writel((priv->input_clk / speed / 2 << 16) | (priv->input_clk / speed), |
||||
&priv->regs->clk); |
||||
|
||||
writel(0x3, &priv->regs->brst); |
||||
|
||||
/*
|
||||
* Theoretically, each byte can be transferred in |
||||
* 1000000 * 9 / speed usec. For safety, wait more than double. |
||||
*/ |
||||
priv->wait_us = 20000000 / speed; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static const struct dm_i2c_ops uniphier_i2c_ops = { |
||||
.xfer = uniphier_i2c_xfer, |
||||
.set_bus_speed = uniphier_i2c_set_bus_speed, |
||||
}; |
||||
|
||||
static const struct udevice_id uniphier_i2c_of_match[] = { |
||||
{ .compatible = "panasonic,uniphier-i2c" }, |
||||
{}, |
||||
}; |
||||
|
||||
U_BOOT_DRIVER(uniphier_i2c) = { |
||||
.name = "uniphier-i2c", |
||||
.id = UCLASS_I2C, |
||||
.of_match = uniphier_i2c_of_match, |
||||
.probe = uniphier_i2c_probe, |
||||
.remove = uniphier_i2c_remove, |
||||
.per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), |
||||
.child_pre_probe = uniphier_i2c_child_pre_probe, |
||||
.priv_auto_alloc_size = sizeof(struct uniphier_i2c_dev), |
||||
.ops = &uniphier_i2c_ops, |
||||
}; |
Loading…
Reference in new issue