[PATCH RESEND 5/5] clk: ccf: call clock provided ops directly for endisable()
Yang Xiwen
forbidden405 at outlook.com
Wed Nov 1 21:33:07 CET 2023
On 11/2/2023 2:50 AM, Yang Xiwen wrote:
> On 11/2/2023 2:19 AM, Sean Anderson wrote:
>> On 8/17/23 13:04, Yang Xiwen via B4 Relay wrote:
>>> From: Yang Xiwen <forbidden405 at outlook.com>
>>>
>>> Calling into CCF framework will cause a clock being enabled twice
>>> instead of once (clk->enable_count becomes 2 rather than 1), thus making
>>> it hard to disable (needs to call clk_disable() twice).
>>> Fix that by calling clock provided ops directly.
>>
>> Can you describe this scenario more? From what I can tell, clk_enable
>> doesn't
>> increment enable_count for CCF clocks.
>>
> Well, it's hard to describe clearly. But I can only tell this patch
> fixed the issue when i was trying to write an Ethernet driver[1] which
> calls clk_disable() and expects the clock to be disabled after that.
> Also I found that CCF driver does not have a corresponding test file. I
> will try to write a test for that in next release.
Okay, fine. I read the source again and let me try to explain the whole
thing to you briefly. Let's see what happens when we are calling
clk_enable(gate).
The source of clk.c is listed below and labeled for clarity:
```
1 if (CONFIG_IS_ENABLED(CLK_CCF)) {
2 /* Take id 0 as a non-valid clk, such as dummy */
3 if (clk->id && !clk_get_by_id(clk->id, &clkp)) {
4 if (clkp->enable_count) {
5 clkp->enable_count++;
6 return 0;
7 }
8 if (clkp->dev->parent &&
9 device_get_uclass_id(clkp->dev->parent) == UCLASS_CLK) {
10 ret = clk_enable(dev_get_clk_ptr(clkp->dev->parent));
11 if (ret) {
12 printf("Enable %s failed\n",
13 clkp->dev->parent->name);
14 return ret;
15 }
16 }
17 }
18
19 if (ops->enable) {
20 ret = ops->enable(clk);
21 if (ret) {
22 printf("Enable %s failed\n", clk->dev->name);
23 return ret;
24 }
25 }
26 if (clkp)
27 clkp->enable_count++;
28 } else {
29 if (!ops->enable)
30 return -ENOSYS;
31 return ops->enable(clk);
```
The following steps are executed:
1. Actually, a "fake" clk is passed to clk_enable() and only clk->id is
valid. The actual clk is "clkp";
2. Initially, we runs till `ret = ops->enable(clk)`(line 20), i.e.
ccf_clk_enable(clk);
3. Thankfully, ccf_clk_enable() calls clk_get_by_id() to get the real
clk and call clk_enable(clkp) again so we won't have an endless loop here.
4. So ops->enable(clk) actually equals to clk_enable(clkp). It's obvious
that there is a `clkp->enable_count++` inside the nested function call
since it's still 0. Now it becomes 1;
5. The nested clk_enable(clkp) now returns to the outer clk_enable(clk);
6. Unfortunately, there is a `if (clkp) clkp->enable_count++;`(line 26)
afterwards. Now it becomes 2.
7. Finally, we got a clk being enabled twice. "clkp->enable_count" is 2 now.
Obviously it's not the intended behavior. We can either fix clk_enable()
or ccf_clk_endisable() to resolve this. But I choose to touch
ccf_clk_endisable() since it's less commonly used.
>> --Sean
>>
>>> Signed-off-by: Yang Xiwen <forbidden405 at outlook.com>
>>> ---
>>> drivers/clk/clk.c | 12 +++++++++++-
>>> 1 file changed, 11 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
>>> index a38daaac0c..00d082c46f 100644
>>> --- a/drivers/clk/clk.c
>>> +++ b/drivers/clk/clk.c
>>> @@ -14,6 +14,7 @@
>>> #include <dm/uclass.h>
>>> #include <dm/lists.h>
>>> #include <dm/device-internal.h>
>>> +#include <linux/clk-provider.h>
>>> int clk_register(struct clk *clk, const char *drv_name,
>>> const char *name, const char *parent_name)
>>> @@ -115,11 +116,20 @@ int ccf_clk_set_parent(struct clk *clk, struct
>>> clk *parent)
>>> static int ccf_clk_endisable(struct clk *clk, bool enable)
>>> {
>>> struct clk *c;
>>> + const struct clk_ops *ops;
>>> int err = clk_get_by_id(clk->id, &c);
>>> if (err)
>>> return err;
>>> - return enable ? clk_enable(c) : clk_disable(c);
>>> + else
>>> + ops = clk_dev_ops(c->dev);
>>> +
>>> + if (enable && ops->enable)
>>> + return ops->enable(c);
>>> + else if (!enable && ops->disable)
>>> + return ops->disable(c);
>>> +
>>> + return -ENOSYS;
>>> }
>>> int ccf_clk_enable(struct clk *clk)
>>>
>>
> [1]
> https://lore.kernel.org/all/20230814-wip-hisi_femac-trunk-v2-0-1e29f400585d@outlook.com/
>
--
Best regards,
Yang Xiwen
More information about the U-Boot
mailing list