mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-14 23:33:00 +00:00
367 lines
9 KiB
C
367 lines
9 KiB
C
|
// SPDX-License-Identifier: GPL-2.0+
|
||
|
/*
|
||
|
* Copyright ASPEED Technology Inc.
|
||
|
*/
|
||
|
#include <common.h>
|
||
|
#include <clk.h>
|
||
|
#include <dm.h>
|
||
|
#include <errno.h>
|
||
|
#include <fdtdec.h>
|
||
|
#include <i2c.h>
|
||
|
#include <log.h>
|
||
|
#include <regmap.h>
|
||
|
#include <reset.h>
|
||
|
#include <asm/io.h>
|
||
|
#include <linux/iopoll.h>
|
||
|
#include "ast2600_i2c.h"
|
||
|
|
||
|
/* Device private data */
|
||
|
struct ast2600_i2c_priv {
|
||
|
struct clk clk;
|
||
|
struct ast2600_i2c_regs *regs;
|
||
|
void __iomem *global;
|
||
|
};
|
||
|
|
||
|
static int ast2600_i2c_read_data(struct ast2600_i2c_priv *priv, u8 chip_addr,
|
||
|
u8 *buffer, size_t len, bool send_stop)
|
||
|
{
|
||
|
int rx_cnt, ret = 0;
|
||
|
u32 cmd, isr;
|
||
|
|
||
|
for (rx_cnt = 0; rx_cnt < len; rx_cnt++, buffer++) {
|
||
|
cmd = I2CM_PKT_EN | I2CM_PKT_ADDR(chip_addr) |
|
||
|
I2CM_RX_CMD;
|
||
|
if (!rx_cnt)
|
||
|
cmd |= I2CM_START_CMD;
|
||
|
|
||
|
if ((len - 1) == rx_cnt)
|
||
|
cmd |= I2CM_RX_CMD_LAST;
|
||
|
|
||
|
if (send_stop && ((len - 1) == rx_cnt))
|
||
|
cmd |= I2CM_STOP_CMD;
|
||
|
|
||
|
writel(cmd, &priv->regs->cmd_sts);
|
||
|
|
||
|
ret = readl_poll_timeout(&priv->regs->isr, isr,
|
||
|
isr & I2CM_PKT_DONE,
|
||
|
I2C_TIMEOUT_US);
|
||
|
if (ret)
|
||
|
return -ETIMEDOUT;
|
||
|
|
||
|
*buffer =
|
||
|
I2CC_GET_RX_BUFF(readl(&priv->regs->trx_buff));
|
||
|
|
||
|
writel(I2CM_PKT_DONE, &priv->regs->isr);
|
||
|
|
||
|
if (isr & I2CM_TX_NAK)
|
||
|
return -EREMOTEIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_write_data(struct ast2600_i2c_priv *priv, u8 chip_addr,
|
||
|
u8 *buffer, size_t len, bool send_stop)
|
||
|
{
|
||
|
int tx_cnt, ret = 0;
|
||
|
u32 cmd, isr;
|
||
|
|
||
|
if (!len) {
|
||
|
cmd = I2CM_PKT_EN | I2CM_PKT_ADDR(chip_addr) |
|
||
|
I2CM_START_CMD;
|
||
|
writel(cmd, &priv->regs->cmd_sts);
|
||
|
ret = readl_poll_timeout(&priv->regs->isr, isr,
|
||
|
isr & I2CM_PKT_DONE,
|
||
|
I2C_TIMEOUT_US);
|
||
|
if (ret)
|
||
|
return -ETIMEDOUT;
|
||
|
|
||
|
writel(I2CM_PKT_DONE, &priv->regs->isr);
|
||
|
|
||
|
if (isr & I2CM_TX_NAK)
|
||
|
return -EREMOTEIO;
|
||
|
}
|
||
|
|
||
|
for (tx_cnt = 0; tx_cnt < len; tx_cnt++, buffer++) {
|
||
|
cmd = I2CM_PKT_EN | I2CM_PKT_ADDR(chip_addr);
|
||
|
cmd |= I2CM_TX_CMD;
|
||
|
|
||
|
if (!tx_cnt)
|
||
|
cmd |= I2CM_START_CMD;
|
||
|
|
||
|
if (send_stop && ((len - 1) == tx_cnt))
|
||
|
cmd |= I2CM_STOP_CMD;
|
||
|
|
||
|
writel(*buffer, &priv->regs->trx_buff);
|
||
|
writel(cmd, &priv->regs->cmd_sts);
|
||
|
ret = readl_poll_timeout(&priv->regs->isr, isr,
|
||
|
isr & I2CM_PKT_DONE,
|
||
|
I2C_TIMEOUT_US);
|
||
|
if (ret)
|
||
|
return -ETIMEDOUT;
|
||
|
|
||
|
writel(I2CM_PKT_DONE, &priv->regs->isr);
|
||
|
|
||
|
if (isr & I2CM_TX_NAK)
|
||
|
return -EREMOTEIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_deblock(struct udevice *dev)
|
||
|
{
|
||
|
struct ast2600_i2c_priv *priv = dev_get_priv(dev);
|
||
|
u32 csr = readl(&priv->regs->cmd_sts);
|
||
|
u32 isr;
|
||
|
int ret;
|
||
|
|
||
|
/* reinit */
|
||
|
writel(0, &priv->regs->fun_ctrl);
|
||
|
/* Enable Master Mode. Assuming single-master */
|
||
|
writel(I2CC_BUS_AUTO_RELEASE | I2CC_MASTER_EN |
|
||
|
I2CC_MULTI_MASTER_DIS,
|
||
|
&priv->regs->fun_ctrl);
|
||
|
|
||
|
csr = readl(&priv->regs->cmd_sts);
|
||
|
|
||
|
if (!(csr & I2CC_SDA_LINE_STS) &&
|
||
|
(csr & I2CC_SCL_LINE_STS)) {
|
||
|
debug("Bus stuck (%x), attempting recovery\n", csr);
|
||
|
writel(I2CM_RECOVER_CMD_EN, &priv->regs->cmd_sts);
|
||
|
ret = readl_poll_timeout(&priv->regs->isr, isr,
|
||
|
isr & (I2CM_BUS_RECOVER_FAIL |
|
||
|
I2CM_BUS_RECOVER),
|
||
|
I2C_TIMEOUT_US);
|
||
|
writel(~0, &priv->regs->isr);
|
||
|
if (ret)
|
||
|
return -EREMOTEIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs)
|
||
|
{
|
||
|
struct ast2600_i2c_priv *priv = dev_get_priv(dev);
|
||
|
int ret;
|
||
|
|
||
|
if (readl(&priv->regs->trx_buff) & I2CC_BUS_BUSY_STS)
|
||
|
return -EREMOTEIO;
|
||
|
|
||
|
for (; nmsgs > 0; nmsgs--, msg++) {
|
||
|
if (msg->flags & I2C_M_RD) {
|
||
|
debug("i2c_read: chip=0x%x, len=0x%x, flags=0x%x\n",
|
||
|
msg->addr, msg->len, msg->flags);
|
||
|
ret = ast2600_i2c_read_data(priv, msg->addr, msg->buf,
|
||
|
msg->len, (nmsgs == 1));
|
||
|
} else {
|
||
|
debug("i2c_write: chip=0x%x, len=0x%x, flags=0x%x\n",
|
||
|
msg->addr, msg->len, msg->flags);
|
||
|
ret = ast2600_i2c_write_data(priv, msg->addr, msg->buf,
|
||
|
msg->len, (nmsgs == 1));
|
||
|
}
|
||
|
if (ret) {
|
||
|
debug("%s: error (%d)\n", __func__, ret);
|
||
|
return -EREMOTEIO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_set_speed(struct udevice *dev, unsigned int speed)
|
||
|
{
|
||
|
struct ast2600_i2c_priv *priv = dev_get_priv(dev);
|
||
|
unsigned long base_clk1, base_clk2, base_clk3, base_clk4;
|
||
|
int multiply = 10;
|
||
|
int baseclk_idx;
|
||
|
u32 clk_div_reg;
|
||
|
u32 apb_clk;
|
||
|
u32 scl_low;
|
||
|
u32 scl_high;
|
||
|
int divisor;
|
||
|
int inc = 0;
|
||
|
u32 data;
|
||
|
|
||
|
debug("Setting speed for I2C%d to <%u>\n", dev->seq_, speed);
|
||
|
if (!speed) {
|
||
|
debug("No valid speed specified\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
apb_clk = clk_get_rate(&priv->clk);
|
||
|
|
||
|
clk_div_reg = readl(priv->global + I2CG_CLK_DIV_CTRL);
|
||
|
|
||
|
base_clk1 = (apb_clk * multiply) / (((GET_CLK1_DIV(clk_div_reg) + 2) * multiply) / 2);
|
||
|
base_clk2 = (apb_clk * multiply) / (((GET_CLK2_DIV(clk_div_reg) + 2) * multiply) / 2);
|
||
|
base_clk3 = (apb_clk * multiply) / (((GET_CLK3_DIV(clk_div_reg) + 2) * multiply) / 2);
|
||
|
base_clk4 = (apb_clk * multiply) / (((GET_CLK4_DIV(clk_div_reg) + 2) * multiply) / 2);
|
||
|
|
||
|
if ((apb_clk / speed) <= 32) {
|
||
|
baseclk_idx = 0;
|
||
|
divisor = DIV_ROUND_UP(apb_clk, speed);
|
||
|
} else if ((base_clk1 / speed) <= 32) {
|
||
|
baseclk_idx = 1;
|
||
|
divisor = DIV_ROUND_UP(base_clk1, speed);
|
||
|
} else if ((base_clk2 / speed) <= 32) {
|
||
|
baseclk_idx = 2;
|
||
|
divisor = DIV_ROUND_UP(base_clk2, speed);
|
||
|
} else if ((base_clk3 / speed) <= 32) {
|
||
|
baseclk_idx = 3;
|
||
|
divisor = DIV_ROUND_UP(base_clk3, speed);
|
||
|
} else {
|
||
|
baseclk_idx = 4;
|
||
|
divisor = DIV_ROUND_UP(base_clk4, speed);
|
||
|
inc = 0;
|
||
|
while ((divisor + inc) > 32) {
|
||
|
inc |= divisor & 0x1;
|
||
|
divisor >>= 1;
|
||
|
baseclk_idx++;
|
||
|
}
|
||
|
divisor += inc;
|
||
|
}
|
||
|
divisor = min_t(int, divisor, 32);
|
||
|
baseclk_idx &= 0xf;
|
||
|
scl_low = ((divisor * 9) / 16) - 1;
|
||
|
scl_low = min_t(u32, scl_low, 0xf);
|
||
|
scl_high = (divisor - scl_low - 2) & 0xf;
|
||
|
/* Divisor : Base Clock : tCKHighMin : tCK High : tCK Low */
|
||
|
data = ((scl_high - 1) << 20) | (scl_high << 16) | (scl_low << 12) |
|
||
|
baseclk_idx;
|
||
|
/* Set AC Timing */
|
||
|
writel(data, &priv->regs->ac_timing);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_probe(struct udevice *dev)
|
||
|
{
|
||
|
struct ast2600_i2c_priv *priv = dev_get_priv(dev);
|
||
|
ofnode i2c_global_node;
|
||
|
|
||
|
/* find global base address */
|
||
|
i2c_global_node = ofnode_get_parent(dev_ofnode(dev));
|
||
|
priv->global = (void *)ofnode_get_addr(i2c_global_node);
|
||
|
if (IS_ERR(priv->global)) {
|
||
|
debug("%s(): can't get global\n", __func__);
|
||
|
return PTR_ERR(priv->global);
|
||
|
}
|
||
|
|
||
|
/* Reset device */
|
||
|
writel(0, &priv->regs->fun_ctrl);
|
||
|
/* Enable Master Mode. Assuming single-master */
|
||
|
writel(I2CC_BUS_AUTO_RELEASE | I2CC_MASTER_EN |
|
||
|
I2CC_MULTI_MASTER_DIS,
|
||
|
&priv->regs->fun_ctrl);
|
||
|
|
||
|
writel(0, &priv->regs->ier);
|
||
|
/* Clear Interrupt */
|
||
|
writel(~0, &priv->regs->isr);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ast2600_i2c_of_to_plat(struct udevice *dev)
|
||
|
{
|
||
|
struct ast2600_i2c_priv *priv = dev_get_priv(dev);
|
||
|
int ret;
|
||
|
|
||
|
priv->regs = dev_read_addr_ptr(dev);
|
||
|
if (!priv->regs)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = clk_get_by_index(dev, 0, &priv->clk);
|
||
|
if (ret < 0) {
|
||
|
debug("%s: Can't get clock for %s: %d\n", __func__, dev->name,
|
||
|
ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dm_i2c_ops ast2600_i2c_ops = {
|
||
|
.xfer = ast2600_i2c_xfer,
|
||
|
.deblock = ast2600_i2c_deblock,
|
||
|
.set_bus_speed = ast2600_i2c_set_speed,
|
||
|
};
|
||
|
|
||
|
static const struct udevice_id ast2600_i2c_ids[] = {
|
||
|
{ .compatible = "aspeed,ast2600-i2c" },
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
U_BOOT_DRIVER(ast2600_i2c) = {
|
||
|
.name = "ast2600_i2c",
|
||
|
.id = UCLASS_I2C,
|
||
|
.of_match = ast2600_i2c_ids,
|
||
|
.probe = ast2600_i2c_probe,
|
||
|
.of_to_plat = ast2600_i2c_of_to_plat,
|
||
|
.priv_auto = sizeof(struct ast2600_i2c_priv),
|
||
|
.ops = &ast2600_i2c_ops,
|
||
|
};
|
||
|
|
||
|
struct ast2600_i2c_global_priv {
|
||
|
void __iomem *regs;
|
||
|
struct reset_ctl reset;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* APB clk : 100Mhz
|
||
|
* div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16]
|
||
|
* I2CG10[31:24] base clk4 for i2c auto recovery timeout counter (0xC6)
|
||
|
* I2CG10[23:16] base clk3 for Standard-mode (100Khz) min tBuf 4.7us
|
||
|
* 0x3c : 100.8Khz : 3.225Mhz : 4.96us
|
||
|
* 0x3d : 99.2Khz : 3.174Mhz : 5.04us
|
||
|
* 0x3e : 97.65Khz : 3.125Mhz : 5.12us
|
||
|
* 0x40 : 97.75Khz : 3.03Mhz : 5.28us
|
||
|
* 0x41 : 99.5Khz : 2.98Mhz : 5.36us (default)
|
||
|
* I2CG10[15:8] base clk2 for Fast-mode (400Khz) min tBuf 1.3us
|
||
|
* 0x12 : 400Khz : 10Mhz : 1.6us
|
||
|
* I2CG10[7:0] base clk1 for Fast-mode Plus (1Mhz) min tBuf 0.5us
|
||
|
* 0x08 : 1Mhz : 20Mhz : 0.8us
|
||
|
*/
|
||
|
|
||
|
static int aspeed_i2c_global_probe(struct udevice *dev)
|
||
|
{
|
||
|
struct ast2600_i2c_global_priv *i2c_global = dev_get_priv(dev);
|
||
|
void __iomem *regs;
|
||
|
int ret = 0;
|
||
|
|
||
|
i2c_global->regs = dev_read_addr_ptr(dev);
|
||
|
if (!i2c_global->regs)
|
||
|
return -EINVAL;
|
||
|
|
||
|
debug("%s(dev=%p)\n", __func__, dev);
|
||
|
|
||
|
regs = i2c_global->regs;
|
||
|
|
||
|
ret = reset_get_by_index(dev, 0, &i2c_global->reset);
|
||
|
if (ret) {
|
||
|
printf("%s(): Failed to get reset signal\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
reset_deassert(&i2c_global->reset);
|
||
|
|
||
|
writel(GLOBAL_INIT, regs + I2CG_CTRL);
|
||
|
writel(I2CCG_DIV_CTRL, regs + I2CG_CLK_DIV_CTRL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct udevice_id aspeed_i2c_global_ids[] = {
|
||
|
{ .compatible = "aspeed,ast2600-i2c-global", },
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
U_BOOT_DRIVER(aspeed_i2c_global) = {
|
||
|
.name = "aspeed_i2c_global",
|
||
|
.id = UCLASS_MISC,
|
||
|
.of_match = aspeed_i2c_global_ids,
|
||
|
.probe = aspeed_i2c_global_probe,
|
||
|
.priv_auto = sizeof(struct ast2600_i2c_global_priv),
|
||
|
};
|