Commit 54c76ed3 authored by Wolfram Sang's avatar Wolfram Sang Committed by Wolfram Sang

i2c: rcar: improve accuracy for R-Car Gen3+

With some new registers, SCL can be calculated to be closer to the
desired rate. Apply the new formula for R-Car Gen3 device types.
Signed-off-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: default avatarWolfram Sang <wsa@kernel.org>
parent 0e864b55
......@@ -41,6 +41,10 @@
#define ICSAR 0x1C /* slave address */
#define ICMAR 0x20 /* master address */
#define ICRXTX 0x24 /* data port */
#define ICCCR2 0x28 /* Clock control 2 */
#define ICMPR 0x2C /* SCL mask control */
#define ICHPR 0x30 /* SCL HIGH control */
#define ICLPR 0x34 /* SCL LOW control */
#define ICFBSCR 0x38 /* first bit setup cycle (Gen3) */
#define ICDMAER 0x3c /* DMA enable (Gen3) */
......@@ -84,11 +88,25 @@
#define RMDMAE BIT(1) /* DMA Master Received Enable */
#define TMDMAE BIT(0) /* DMA Master Transmitted Enable */
/* ICCCR2 */
#define CDFD BIT(2) /* CDF Disable */
#define HLSE BIT(1) /* HIGH/LOW Separate Control Enable */
#define SME BIT(0) /* SCL Mask Enable */
/* ICFBSCR */
#define TCYC17 0x0f /* 17*Tcyc delay 1st bit between SDA and SCL */
#define RCAR_MIN_DMA_LEN 8
/* SCL low/high ratio 5:4 to meet all I2C timing specs (incl safety margin) */
#define RCAR_SCLD_RATIO 5
#define RCAR_SCHD_RATIO 4
/*
* SMD should be smaller than SCLD/SCHD and is always around 20 in the docs.
* Thus, we simply use 20 which works for low and high speeds.
*/
#define RCAR_DEFAULT_SMD 20
#define RCAR_BUS_PHASE_START (MDBS | MIE | ESG)
#define RCAR_BUS_PHASE_DATA (MDBS | MIE)
#define RCAR_BUS_PHASE_STOP (MDBS | MIE | FSB)
......@@ -128,6 +146,8 @@ struct rcar_i2c_priv {
int pos;
u32 icccr;
u16 schd;
u16 scld;
u8 recovery_icmcr; /* protected by adapter lock */
enum rcar_i2c_type devtype;
struct i2c_client *slave;
......@@ -216,11 +236,16 @@ static void rcar_i2c_init(struct rcar_i2c_priv *priv)
rcar_i2c_write(priv, ICMCR, MDBS);
rcar_i2c_write(priv, ICMSR, 0);
/* start clock */
if (priv->devtype < I2C_RCAR_GEN3) {
rcar_i2c_write(priv, ICCCR, priv->icccr);
if (priv->devtype == I2C_RCAR_GEN3)
} else {
rcar_i2c_write(priv, ICCCR2, CDFD | HLSE | SME);
rcar_i2c_write(priv, ICCCR, priv->icccr);
rcar_i2c_write(priv, ICMPR, RCAR_DEFAULT_SMD);
rcar_i2c_write(priv, ICHPR, priv->schd);
rcar_i2c_write(priv, ICLPR, priv->scld);
rcar_i2c_write(priv, ICFBSCR, TCYC17);
}
}
static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv)
......@@ -241,7 +266,7 @@ static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv)
static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv)
{
u32 scgd, cdf, round, ick, sum, scl, cdf_width;
u32 cdf, round, ick, sum, scl, cdf_width;
unsigned long rate;
struct device *dev = rcar_i2c_priv_to_dev(priv);
struct i2c_timings t = {
......@@ -254,27 +279,17 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv)
/* Fall back to previously used values if not supplied */
i2c_parse_fw_timings(dev, &t, false);
switch (priv->devtype) {
case I2C_RCAR_GEN1:
cdf_width = 2;
break;
case I2C_RCAR_GEN2:
case I2C_RCAR_GEN3:
cdf_width = 3;
break;
default:
dev_err(dev, "device type error\n");
return -EIO;
}
/*
* calculate SCL clock
* see
* ICCCR
* ICCCR (and ICCCR2 for Gen3+)
*
* ick = clkp / (1 + CDF)
* SCL = ick / (20 + SCGD * 8 + F[(ticf + tr + intd) * ick])
*
* for Gen3+:
* SCL = clkp / (8 + SMD * 2 + SCLD + SCHD +F[(ticf + tr + intd) * clkp])
*
* ick : I2C internal clock < 20 MHz
* ticf : I2C SCL falling time
* tr : I2C SCL rising time
......@@ -284,11 +299,12 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv)
*/
rate = clk_get_rate(priv->clk);
cdf = rate / 20000000;
if (cdf >= 1U << cdf_width) {
dev_err(dev, "Input clock %lu too high\n", rate);
return -EIO;
}
ick = rate / (cdf + 1);
cdf_width = (priv->devtype == I2C_RCAR_GEN1) ? 2 : 3;
if (cdf >= 1U << cdf_width)
goto err_no_val;
/* On Gen3+, we use cdf only for the filters, not as a SCL divider */
ick = rate / (priv->devtype < I2C_RCAR_GEN3 ? (cdf + 1) : 1);
/*
* It is impossible to calculate a large scale number on u32. Separate it.
......@@ -301,6 +317,8 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv)
round = DIV_ROUND_CLOSEST(ick, 1000000);
round = DIV_ROUND_CLOSEST(round * sum, 1000);
if (priv->devtype < I2C_RCAR_GEN3) {
u32 scgd;
/*
* SCL = ick / (20 + 8 * SCGD + F[(ticf + tr + intd) * ick])
* 20 + 8 * SCGD + F[...] = ick / SCL
......@@ -317,8 +335,40 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv)
dev_dbg(dev, "clk %u/%u(%lu), round %u, CDF: %u, SCGD: %u\n",
scl, t.bus_freq_hz, rate, round, cdf, scgd);
/* keep icccr value */
priv->icccr = scgd << cdf_width | cdf;
} else {
u32 x, sum_ratio = RCAR_SCHD_RATIO + RCAR_SCLD_RATIO;
/*
* SCLD/SCHD ratio and SMD default value are explained above
* where they are defined. With these definitions, we can compute
* x as a base value for the SCLD/SCHD ratio:
*
* SCL = clkp / (8 + 2 * SMD + SCLD + SCHD + F[(ticf + tr + intd) * clkp])
* SCL = clkp / (8 + 2 * RCAR_DEFAULT_SMD + RCAR_SCLD_RATIO * x
* + RCAR_SCHD_RATIO * x + F[...])
*
* with: sum_ratio = RCAR_SCLD_RATIO + RCAR_SCHD_RATIO
* and: smd = RCAR_DEFAULT_SMD
*
* SCL = clkp / (8 + 2 * smd + sum_ratio * x + F[...])
* 8 + 2 * smd + sum_ratio * x + F[...] = clkp / SCL
* x = ((clkp / SCL) - 8 - 2 * smd - F[...]) / sum_ratio
*/
x = DIV_ROUND_UP(rate, t.bus_freq_hz ?: 1);
x = DIV_ROUND_UP(x - 8 - 2 * RCAR_DEFAULT_SMD - round, sum_ratio);
scl = rate / (8 + 2 * RCAR_DEFAULT_SMD + sum_ratio * x + round);
/* Bail out if values don't fit into 16 bit or SMD became too large */
if (x * RCAR_SCLD_RATIO > 0xffff || RCAR_DEFAULT_SMD > x * RCAR_SCHD_RATIO)
goto err_no_val;
priv->icccr = cdf;
priv->schd = RCAR_SCHD_RATIO * x;
priv->scld = RCAR_SCLD_RATIO * x;
dev_dbg(dev, "clk %u/%u(%lu), round %u, CDF: %u SCHD %u SCLD %u\n",
scl, t.bus_freq_hz, rate, round, cdf, priv->schd, priv->scld);
}
return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment