mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
i2c: spacemit: configure ILCR/IWCR for accurate SCL frequency
The SpacemiT I2C controller's SCL (Serial Clock Line) frequency for master mode operations is determined by the ILCR (I2C Load Count Register). Previously, the driver relied on the hardware's reset default values for this register. The hardware's default ILCR values (SLV=0x156, FLV=0x5d) yield SCL frequencies lower than intended. For example, with the default 31.5 MHz input clock, these default settings result in an SCL frequency of approximately 93 kHz (standard mode) when targeting 100 kHz, and approximately 338 kHz (fast mode) when targeting 400 kHz. These frequencies are below the 100 kHz/400 kHz nominal speeds. This patch integrates the SCL frequency management into the Common Clock Framework (CCF). Specifically, the ILCR register, which acts as a frequency divider for the SCL clock, is now registered as a managed clock (scl_clk) within the CCF. The actual hardware timing formulas are: - standard mode: SCL = FCLK / (2 * SLV + 8) - fast mode: SCL = FCLK / (2 * FLV + 10) These formulas are only valid when the IWCR (Wait Count Register) is programmed to 0x142A, a value specified by the I2C IP designer. The driver now initializes IWCR to this value during controller init. Reviewed-by: Yixun Lan <dlan@gentoo.org> Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com> Reviewed-by: Alex Elder <elder@riscstar.com> Signed-off-by: Andi Shyti <andi.shyti@kernel.org> Link: https://lore.kernel.org/r/20260508-k1-i2c-ilcr-v7-1-8c2dde5c3ed5@linux.spacemit.com
This commit is contained in:
parent
dc59e4fea9
commit
0ed3fa2370
|
|
@ -793,7 +793,7 @@ config I2C_JZ4780
|
|||
config I2C_K1
|
||||
tristate "SpacemiT K1 I2C adapter"
|
||||
depends on ARCH_SPACEMIT || COMPILE_TEST
|
||||
depends on OF
|
||||
depends on OF && COMMON_CLK
|
||||
default ARCH_SPACEMIT
|
||||
help
|
||||
This option enables support for the I2C interface on the SpacemiT K1
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -17,6 +19,8 @@
|
|||
#define SPACEMIT_ISR 0x4 /* Status register */
|
||||
#define SPACEMIT_IDBR 0xc /* Data buffer register */
|
||||
#define SPACEMIT_IRCR 0x18 /* Reset cycle counter */
|
||||
#define SPACEMIT_ILCR 0x10 /* Load Count Register */
|
||||
#define SPACEMIT_IWCR 0x14 /* Wait Count Register */
|
||||
#define SPACEMIT_IBMR 0x1c /* Bus monitor register */
|
||||
|
||||
/* SPACEMIT_ICR register fields */
|
||||
|
|
@ -88,6 +92,19 @@
|
|||
#define SPACEMIT_BMR_SDA BIT(0) /* SDA line level */
|
||||
#define SPACEMIT_BMR_SCL BIT(1) /* SCL line level */
|
||||
|
||||
#define SPACEMIT_LCR_LV_STANDARD_MASK GENMASK(8, 0)
|
||||
#define SPACEMIT_LCR_LV_FAST_MASK GENMASK(17, 9)
|
||||
|
||||
/* SPACEMIT_IWCR register fields */
|
||||
#define SPACEMIT_WCR_COUNT GENMASK(4, 0)
|
||||
#define SPACEMIT_WCR_HS_COUNT1 GENMASK(9, 5)
|
||||
#define SPACEMIT_WCR_HS_COUNT2 GENMASK(14, 10)
|
||||
|
||||
/* Required by I2C IP for correct SCL timing */
|
||||
#define SPACEMIT_IWCR_INIT_VALUE (FIELD_PREP(SPACEMIT_WCR_COUNT, 10) | \
|
||||
FIELD_PREP(SPACEMIT_WCR_HS_COUNT1, 1) | \
|
||||
FIELD_PREP(SPACEMIT_WCR_HS_COUNT2, 5))
|
||||
|
||||
/* i2c bus recover timeout: us */
|
||||
#define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000
|
||||
|
||||
|
|
@ -109,11 +126,20 @@ enum spacemit_i2c_state {
|
|||
SPACEMIT_STATE_WRITE,
|
||||
};
|
||||
|
||||
enum spacemit_i2c_mode {
|
||||
SPACEMIT_MODE_STANDARD,
|
||||
SPACEMIT_MODE_FAST
|
||||
};
|
||||
|
||||
/* i2c-spacemit driver's main struct */
|
||||
struct spacemit_i2c_dev {
|
||||
struct device *dev;
|
||||
struct i2c_adapter adapt;
|
||||
|
||||
struct clk_hw scl_clk_hw;
|
||||
struct clk *scl_clk;
|
||||
enum spacemit_i2c_mode mode;
|
||||
|
||||
/* hardware resources */
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
|
|
@ -135,6 +161,85 @@ struct spacemit_i2c_dev {
|
|||
u32 status;
|
||||
};
|
||||
|
||||
static void spacemit_i2c_scl_clk_disable_unprepare(void *data)
|
||||
{
|
||||
clk_disable_unprepare(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the ILCR divider value (lv) from the target SCL rate.
|
||||
*
|
||||
* Hardware timing formulas:
|
||||
* - standard mode: SCL = FCLK / (2 * SLV + 8)
|
||||
* - fast mode: SCL = FCLK / (2 * FLV + 10)
|
||||
*/
|
||||
static u32 spacemit_i2c_calc_lv(struct spacemit_i2c_dev *i2c,
|
||||
unsigned long parent_rate,
|
||||
unsigned long target_rate)
|
||||
{
|
||||
u32 offset, denom;
|
||||
|
||||
offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10;
|
||||
denom = DIV_ROUND_CLOSEST(parent_rate, target_rate);
|
||||
|
||||
return (denom <= offset) ? 0 : DIV_ROUND_CLOSEST(denom - offset, 2);
|
||||
}
|
||||
|
||||
static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
|
||||
u32 lv, lcr, mask;
|
||||
|
||||
lv = spacemit_i2c_calc_lv(i2c, parent_rate, rate);
|
||||
|
||||
mask = (i2c->mode == SPACEMIT_MODE_STANDARD) ?
|
||||
SPACEMIT_LCR_LV_STANDARD_MASK : SPACEMIT_LCR_LV_FAST_MASK;
|
||||
|
||||
lcr = readl(i2c->base + SPACEMIT_ILCR);
|
||||
lcr &= ~mask;
|
||||
lcr |= field_prep(mask, lv);
|
||||
writel(lcr, i2c->base + SPACEMIT_ILCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spacemit_i2c_clk_determine_rate(struct clk_hw *hw,
|
||||
struct clk_rate_request *req)
|
||||
{
|
||||
struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
|
||||
u32 lv, offset;
|
||||
|
||||
lv = spacemit_i2c_calc_lv(i2c, req->best_parent_rate, req->rate);
|
||||
offset = (i2c->mode == SPACEMIT_MODE_STANDARD) ? 8 : 10;
|
||||
req->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long spacemit_i2c_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
|
||||
u32 lcr, lv = 0;
|
||||
|
||||
lcr = readl(i2c->base + SPACEMIT_ILCR);
|
||||
|
||||
if (i2c->mode == SPACEMIT_MODE_STANDARD) {
|
||||
lv = FIELD_GET(SPACEMIT_LCR_LV_STANDARD_MASK, lcr);
|
||||
return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 8);
|
||||
}
|
||||
|
||||
lv = FIELD_GET(SPACEMIT_LCR_LV_FAST_MASK, lcr);
|
||||
return DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 10);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_i2c_clk_ops = {
|
||||
.set_rate = spacemit_i2c_clk_set_rate,
|
||||
.determine_rate = spacemit_i2c_clk_determine_rate,
|
||||
.recalc_rate = spacemit_i2c_clk_recalc_rate,
|
||||
};
|
||||
|
||||
static void spacemit_i2c_enable(struct spacemit_i2c_dev *i2c)
|
||||
{
|
||||
u32 val;
|
||||
|
|
@ -153,6 +258,28 @@ static void spacemit_i2c_disable(struct spacemit_i2c_dev *i2c)
|
|||
writel(val, i2c->base + SPACEMIT_ICR);
|
||||
}
|
||||
|
||||
static int spacemit_i2c_register_scl_clk(struct spacemit_i2c_dev *i2c)
|
||||
{
|
||||
struct clk_init_data init = {};
|
||||
char name[64];
|
||||
int ret;
|
||||
|
||||
ret = snprintf(name, sizeof(name), "%s_scl_clk", dev_name(i2c->dev));
|
||||
if (ret >= ARRAY_SIZE(name))
|
||||
dev_warn(i2c->dev, "scl clock name truncated");
|
||||
|
||||
init.name = name;
|
||||
init.ops = &spacemit_i2c_clk_ops;
|
||||
init.parent_data = (struct clk_parent_data[]) {
|
||||
{ .fw_name = "func" },
|
||||
};
|
||||
init.num_parents = 1;
|
||||
|
||||
i2c->scl_clk_hw.init = &init;
|
||||
|
||||
return devm_clk_hw_register(i2c->dev, &i2c->scl_clk_hw);
|
||||
}
|
||||
|
||||
static void spacemit_i2c_reset(struct spacemit_i2c_dev *i2c)
|
||||
{
|
||||
writel(SPACEMIT_CR_UR, i2c->base + SPACEMIT_ICR);
|
||||
|
|
@ -286,7 +413,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)
|
|||
val |= SPACEMIT_CR_MSDIE;
|
||||
}
|
||||
|
||||
if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ)
|
||||
if (i2c->mode == SPACEMIT_MODE_FAST)
|
||||
val |= SPACEMIT_CR_MODE_FAST;
|
||||
|
||||
/* disable response to general call */
|
||||
|
|
@ -309,6 +436,14 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)
|
|||
writel(val, i2c->base + SPACEMIT_IRCR);
|
||||
|
||||
spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK);
|
||||
|
||||
/*
|
||||
* Initialize IWCR to the value specified by the I2C IP designer.
|
||||
* The SCL frequency formulas (SCL = FCLK / (2*SLV+8) for standard
|
||||
* mode, SCL = FCLK / (2*FLV+10) for fast mode) are only valid when
|
||||
* IWCR contains this specific value.
|
||||
*/
|
||||
writel(SPACEMIT_IWCR_INIT_VALUE, i2c->base + SPACEMIT_IWCR);
|
||||
}
|
||||
|
||||
static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c)
|
||||
|
|
@ -703,14 +838,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev)
|
|||
dev_warn(dev, "failed to read clock-frequency property: %d\n", ret);
|
||||
|
||||
/* For now, this driver doesn't support high-speed. */
|
||||
if (!i2c->clock_freq || i2c->clock_freq > SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {
|
||||
dev_warn(dev, "unsupported clock frequency %u; using %u\n",
|
||||
i2c->clock_freq, SPACEMIT_I2C_MAX_FAST_MODE_FREQ);
|
||||
if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ &&
|
||||
i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {
|
||||
i2c->mode = SPACEMIT_MODE_FAST;
|
||||
} else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {
|
||||
i2c->mode = SPACEMIT_MODE_STANDARD;
|
||||
} else {
|
||||
dev_warn(i2c->dev, "invalid clock-frequency, fallback to fast mode");
|
||||
i2c->mode = SPACEMIT_MODE_FAST;
|
||||
i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ;
|
||||
} else if (i2c->clock_freq < SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {
|
||||
dev_warn(dev, "unsupported clock frequency %u; using %u\n",
|
||||
i2c->clock_freq, SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ);
|
||||
i2c->clock_freq = SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ;
|
||||
}
|
||||
|
||||
i2c->dev = &pdev->dev;
|
||||
|
|
@ -732,6 +868,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk), "failed to enable func clock");
|
||||
|
||||
ret = spacemit_i2c_register_scl_clk(i2c);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to register scl clock\n");
|
||||
|
||||
i2c->scl_clk = devm_clk_hw_get_clk(dev, &i2c->scl_clk_hw, "scl");
|
||||
if (IS_ERR(i2c->scl_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(i2c->scl_clk),
|
||||
"failed to get scl clock\n");
|
||||
|
||||
clk = devm_clk_get_enabled(dev, "bus");
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock");
|
||||
|
|
@ -741,6 +886,19 @@ static int spacemit_i2c_probe(struct platform_device *pdev)
|
|||
return dev_err_probe(dev, PTR_ERR(rst),
|
||||
"failed to acquire deasserted reset\n");
|
||||
|
||||
ret = clk_set_rate(i2c->scl_clk, i2c->clock_freq);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to set rate for SCL clock");
|
||||
|
||||
ret = clk_prepare_enable(i2c->scl_clk);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to prepare and enable clock");
|
||||
|
||||
ret = devm_add_action_or_reset(dev, spacemit_i2c_scl_clk_disable_unprepare,
|
||||
i2c->scl_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spacemit_i2c_reset(i2c);
|
||||
|
||||
i2c_set_adapdata(&i2c->adapt, i2c);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user