Commit 17ccba67 authored by Brendan Higgins's avatar Brendan Higgins Committed by Wolfram Sang

i2c: aspeed: fix invalid clock parameters for very large divisors

The function that computes clock parameters from divisors did not
respect the maximum size of the bitfields that the parameters were
written to. This fixes the bug.

This bug can be reproduced with (and this fix verified with) the test
at: https://kunit-review.googlesource.com/c/linux/+/1035/

Discovered-by-KUnit: https://kunit-review.googlesource.com/c/linux/+/1035/Signed-off-by: default avatarBrendan Higgins <brendanhiggins@google.com>
Reviewed-by: default avatarJae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent f8878fad
...@@ -142,7 +142,8 @@ struct aspeed_i2c_bus { ...@@ -142,7 +142,8 @@ struct aspeed_i2c_bus {
/* Synchronizes I/O mem access to base. */ /* Synchronizes I/O mem access to base. */
spinlock_t lock; spinlock_t lock;
struct completion cmd_complete; struct completion cmd_complete;
u32 (*get_clk_reg_val)(u32 divisor); u32 (*get_clk_reg_val)(struct device *dev,
u32 divisor);
unsigned long parent_clk_frequency; unsigned long parent_clk_frequency;
u32 bus_frequency; u32 bus_frequency;
/* Transaction state. */ /* Transaction state. */
...@@ -710,16 +711,27 @@ static const struct i2c_algorithm aspeed_i2c_algo = { ...@@ -710,16 +711,27 @@ static const struct i2c_algorithm aspeed_i2c_algo = {
#endif /* CONFIG_I2C_SLAVE */ #endif /* CONFIG_I2C_SLAVE */
}; };
static u32 aspeed_i2c_get_clk_reg_val(u32 clk_high_low_max, u32 divisor) static u32 aspeed_i2c_get_clk_reg_val(struct device *dev,
u32 clk_high_low_mask,
u32 divisor)
{ {
u32 base_clk, clk_high, clk_low, tmp; u32 base_clk_divisor, clk_high_low_max, clk_high, clk_low, tmp;
/*
* SCL_high and SCL_low represent a value 1 greater than what is stored
* since a zero divider is meaningless. Thus, the max value each can
* store is every bit set + 1. Since SCL_high and SCL_low are added
* together (see below), the max value of both is the max value of one
* them times two.
*/
clk_high_low_max = (clk_high_low_mask + 1) * 2;
/* /*
* The actual clock frequency of SCL is: * The actual clock frequency of SCL is:
* SCL_freq = APB_freq / (base_freq * (SCL_high + SCL_low)) * SCL_freq = APB_freq / (base_freq * (SCL_high + SCL_low))
* = APB_freq / divisor * = APB_freq / divisor
* where base_freq is a programmable clock divider; its value is * where base_freq is a programmable clock divider; its value is
* base_freq = 1 << base_clk * base_freq = 1 << base_clk_divisor
* SCL_high is the number of base_freq clock cycles that SCL stays high * SCL_high is the number of base_freq clock cycles that SCL stays high
* and SCL_low is the number of base_freq clock cycles that SCL stays * and SCL_low is the number of base_freq clock cycles that SCL stays
* low for a period of SCL. * low for a period of SCL.
...@@ -729,47 +741,59 @@ static u32 aspeed_i2c_get_clk_reg_val(u32 clk_high_low_max, u32 divisor) ...@@ -729,47 +741,59 @@ static u32 aspeed_i2c_get_clk_reg_val(u32 clk_high_low_max, u32 divisor)
* SCL_low = clk_low + 1 * SCL_low = clk_low + 1
* Thus, * Thus,
* SCL_freq = APB_freq / * SCL_freq = APB_freq /
* ((1 << base_clk) * (clk_high + 1 + clk_low + 1)) * ((1 << base_clk_divisor) * (clk_high + 1 + clk_low + 1))
* The documentation recommends clk_high >= clk_high_max / 2 and * The documentation recommends clk_high >= clk_high_max / 2 and
* clk_low >= clk_low_max / 2 - 1 when possible; this last constraint * clk_low >= clk_low_max / 2 - 1 when possible; this last constraint
* gives us the following solution: * gives us the following solution:
*/ */
base_clk = divisor > clk_high_low_max ? base_clk_divisor = divisor > clk_high_low_max ?
ilog2((divisor - 1) / clk_high_low_max) + 1 : 0; ilog2((divisor - 1) / clk_high_low_max) + 1 : 0;
tmp = (divisor + (1 << base_clk) - 1) >> base_clk;
clk_low = tmp / 2;
clk_high = tmp - clk_low;
if (clk_high) if (base_clk_divisor > ASPEED_I2CD_TIME_BASE_DIVISOR_MASK) {
clk_high--; base_clk_divisor = ASPEED_I2CD_TIME_BASE_DIVISOR_MASK;
clk_low = clk_high_low_mask;
clk_high = clk_high_low_mask;
dev_err(dev,
"clamping clock divider: divider requested, %u, is greater than largest possible divider, %u.\n",
divisor, (1 << base_clk_divisor) * clk_high_low_max);
} else {
tmp = (divisor + (1 << base_clk_divisor) - 1)
>> base_clk_divisor;
clk_low = tmp / 2;
clk_high = tmp - clk_low;
if (clk_high)
clk_high--;
if (clk_low) if (clk_low)
clk_low--; clk_low--;
}
return ((clk_high << ASPEED_I2CD_TIME_SCL_HIGH_SHIFT) return ((clk_high << ASPEED_I2CD_TIME_SCL_HIGH_SHIFT)
& ASPEED_I2CD_TIME_SCL_HIGH_MASK) & ASPEED_I2CD_TIME_SCL_HIGH_MASK)
| ((clk_low << ASPEED_I2CD_TIME_SCL_LOW_SHIFT) | ((clk_low << ASPEED_I2CD_TIME_SCL_LOW_SHIFT)
& ASPEED_I2CD_TIME_SCL_LOW_MASK) & ASPEED_I2CD_TIME_SCL_LOW_MASK)
| (base_clk & ASPEED_I2CD_TIME_BASE_DIVISOR_MASK); | (base_clk_divisor
& ASPEED_I2CD_TIME_BASE_DIVISOR_MASK);
} }
static u32 aspeed_i2c_24xx_get_clk_reg_val(u32 divisor) static u32 aspeed_i2c_24xx_get_clk_reg_val(struct device *dev, u32 divisor)
{ {
/* /*
* clk_high and clk_low are each 3 bits wide, so each can hold a max * clk_high and clk_low are each 3 bits wide, so each can hold a max
* value of 8 giving a clk_high_low_max of 16. * value of 8 giving a clk_high_low_max of 16.
*/ */
return aspeed_i2c_get_clk_reg_val(16, divisor); return aspeed_i2c_get_clk_reg_val(dev, GENMASK(2, 0), divisor);
} }
static u32 aspeed_i2c_25xx_get_clk_reg_val(u32 divisor) static u32 aspeed_i2c_25xx_get_clk_reg_val(struct device *dev, u32 divisor)
{ {
/* /*
* clk_high and clk_low are each 4 bits wide, so each can hold a max * clk_high and clk_low are each 4 bits wide, so each can hold a max
* value of 16 giving a clk_high_low_max of 32. * value of 16 giving a clk_high_low_max of 32.
*/ */
return aspeed_i2c_get_clk_reg_val(32, divisor); return aspeed_i2c_get_clk_reg_val(dev, GENMASK(3, 0), divisor);
} }
/* precondition: bus.lock has been acquired. */ /* precondition: bus.lock has been acquired. */
...@@ -782,7 +806,7 @@ static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus) ...@@ -782,7 +806,7 @@ static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
clk_reg_val &= (ASPEED_I2CD_TIME_TBUF_MASK | clk_reg_val &= (ASPEED_I2CD_TIME_TBUF_MASK |
ASPEED_I2CD_TIME_THDSTA_MASK | ASPEED_I2CD_TIME_THDSTA_MASK |
ASPEED_I2CD_TIME_TACST_MASK); ASPEED_I2CD_TIME_TACST_MASK);
clk_reg_val |= bus->get_clk_reg_val(divisor); clk_reg_val |= bus->get_clk_reg_val(bus->dev, divisor);
writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1); writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1);
writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2); writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2);
...@@ -898,7 +922,8 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) ...@@ -898,7 +922,8 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev)
if (!match) if (!match)
bus->get_clk_reg_val = aspeed_i2c_24xx_get_clk_reg_val; bus->get_clk_reg_val = aspeed_i2c_24xx_get_clk_reg_val;
else else
bus->get_clk_reg_val = (u32 (*)(u32))match->data; bus->get_clk_reg_val = (u32 (*)(struct device *, u32))
match->data;
/* Initialize the I2C adapter */ /* Initialize the I2C adapter */
spin_lock_init(&bus->lock); spin_lock_init(&bus->lock);
......
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