* Re: [PATCH v6 1/2] i2c: spacemit: configure ILCR/IWCR for accurate SCL frequency
[not found] ` <01278362-ae7c-424a-838b-4add1718e7f6@riscstar.com>
@ 2026-05-08 6:37 ` Troy Mitchell
0 siblings, 0 replies; only message in thread
From: Troy Mitchell @ 2026-05-08 6:37 UTC (permalink / raw)
To: Alex Elder, Troy Mitchell, Andi Shyti, Yixun Lan
Cc: linux-i2c, linux-kernel, linux-riscv, spacemit
On Wed Apr 29, 2026 at 8:40 PM CST, Alex Elder wrote:
> On 4/29/26 2:48 AM, Troy Mitchell wrote:
[...]
>> #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,12 @@
>> #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)
>> +
>> +/* Required by I2C IP for correct SCL timing */
>> +#define SPACEMIT_IWCR_INIT_VALUE 0x142A
>
> Even if it's just used to "do the right thing", I still think it's
> useful to break this numeric value into what it *represents*. The
> register is defined this way:
> COUNT GENMASK(4:0)
> HS_COUNT1 GENMASK(9:5)
> HS_COUNT2 GENMASK(14:10)
> (the rest is reserved)
>
> So 0x142a represents:
> COUNT 10 33 MHz I2C functional clock (versus 66 MHz)
> HS_COUNT1 2 Count defining start bit setup/hold times
> HS_COUNT2 3 Count defining stop bit setup/hold times
>
> The first one in particular *could* be managed in your clock
> to support even more accurate clock rates. But even if you
> don't change its value I think it's meaningful to know what
> the value represents.
>
> That said--what I suggest won't change what the code does,
> it just helps the reader understand what it's doing.
Thanks for the suggestion.
I've broken SPACEMIT_IWCR_INIT_VALUE into its constituent fields
using FIELD_PREP. One small correction: 0x142A actually decodes
to COUNT=10, HS_COUNT1=1, HS_COUNT2=5 (not 2 and 3).
>
>> +
>> /* i2c bus recover timeout: us */
>> #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000
>>
>>
[...]
>> +
>> +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, denom;
>
> Can you have clk_set_rate() call clk_determine_rate(), or at
> least have them both share a helper that does these (duplicated)
> calculations?
Done. Extracted spacemit_i2c_calc_lv() as a shared helper used
by both set_rate and determine_rate.
>
>> +
>> + denom = DIV_ROUND_CLOSEST(req->best_parent_rate, req->rate);
>> +
>> + if (i2c->mode == SPACEMIT_MODE_STANDARD) {
>> + lv = (denom <= 8) ? 0 : DIV_ROUND_CLOSEST(denom - 8, 2);
>> + req->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + 8);
>> + } else {
>> + lv = (denom <= 10) ? 0 : DIV_ROUND_CLOSEST(denom - 10, 2);
>> + req->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + 10);
>> + }
>> +
>> + return 0;
>> +}
>> +
[...]
>>
>> +static struct clk *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_register(i2c->dev, &i2c->scl_clk_hw);
>
> The comments above clk_register() indicate that this is a deprecated
> interface, and you should use a form of clk_hw_register() instead.
> devm_clk_hw_register() exists and takes the same arguments, but
> returns an int rather than the clock pointer. But I think then you
> can call devm_clk_get_enabled() in the caller (spacemit_i2c_probe()).
> That would allow you to get rid of your clk_disable_unprepare()
> wrapper too.
I've switched to devm_clk_hw_register() as suggested. However,
devm_clk_get_enabled() isn't applicable here because the SCL clock is
registered internally by the driver rather than being described in the
Device Tree. Since devm_clk_get_enabled() relies on clk_get(),
it would fail to find the clock without a DT entry or a clkdev lookup.
Instead, I'm using devm_clk_hw_get_clk() to retrieve the struct clk
directly from the clk_hw, followed by a manual clk_prepare_enable()
with a devm action for automated cleanup.
- Troy
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-05-08 6:38 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260429-k1-i2c-ilcr-v6-0-1c7a5a5a8b24@linux.spacemit.com>
[not found] ` <20260429-k1-i2c-ilcr-v6-1-1c7a5a5a8b24@linux.spacemit.com>
[not found] ` <01278362-ae7c-424a-838b-4add1718e7f6@riscstar.com>
2026-05-08 6:37 ` [PATCH v6 1/2] i2c: spacemit: configure ILCR/IWCR for accurate SCL frequency Troy Mitchell
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox