Re: [PATCH v1 3/4] soc: starfive: Add StarFive JH71XX pmu driver
From: Walker Chen
Date: Wed Nov 30 2022 - 22:56:42 EST
On 2022/11/25 19:17, Conor Dooley wrote:
> On Fri, Nov 25, 2022 at 06:04:59PM +0800, Walker Chen wrote:
>> On 2022/11/19 8:24, Conor Dooley wrote:
>> > On Fri, Nov 18, 2022 at 09:32:15PM +0800, Walker Chen wrote:
>
>> >> +void starfive_pmu_hw_event_turn_off(u32 mask)
>> >> +{
>> >> + pmu_writel(mask, HW_EVENT_TURN_OFF_MASK);
>> >> +}
>> >> +EXPORT_SYMBOL(starfive_pmu_hw_event_turn_off);
>> >
>> > Where are the users for these exports? Also, should they be exported as
>> > GPL?
>> >
>> > Either way, what is the point of the extra layer of abstraction here
>> > around the writel()?
>>
>> The two export functions are only prepared for GPU module. But accordint to
>> the latest information, it seems that there is no open source plan for GPU.
>> So the two functions will be drop in next version of patch.
>
> That's a shame!
Need to comply with certain commercial terms.
>
>> >> +static int starfive_pmu_get_state(struct starfive_power_dev *pmd, bool *is_on)
>> >> +{
>> >> + struct starfive_pmu *pmu = pmd->power;
>> >> +
>> >> + if (!pmd->mask) {
>> >> + *is_on = false;
>> >> + return -EINVAL;
>> >> + }
>> >> +
>> >> + *is_on = __raw_readl(pmu->base + CURR_POWER_MODE) & pmd->mask;
>> >
>> > Is there a specific reason that you are using the __raw variants here
>> > (and elsewhere) in the driver?
>>
>> Will use unified function '__raw_readl' and '__raw_writel'
>
> No no, I want to know *why* you are using the __raw accessors here. My
> understanding was that __raw variants are unbarriered & unordered with
> respect to other io accesses.
>
> I do notice that the bcm driver you mentioned uses the __raw variants,
> but only __raw variants - whereas you use readl() which is ordered and
> barriered & __raw_readl().
>
> Is there a reason why you would not use readl() or readl_relaxed()?
Your question led me to deeply understand the usage of these io accessors.
__raw_readl / __raw_writel denotes native byte order, no memory barrier.
readl / writel do guarantee the byte order with barrier, ensure that previous writes are done.
Seem that non-raw accessors are more safe.
>
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static int starfive_pmu_set_state(struct starfive_power_dev *pmd, bool on)
>> >> +{
>> >> + struct starfive_pmu *pmu = pmd->power;
>> >> + unsigned long flags;
>> >> + uint32_t val;
>> >> + uint32_t mode;
>> >> + uint32_t encourage_lo;
>> >> + uint32_t encourage_hi;
>> >> + bool is_on;
>> >> + int ret;
>> >> +
>> >> + if (!pmd->mask)
>> >> + return -EINVAL;
>> >> +
>> >> + if (is_on == on) {
>> >> + dev_info(pmu->pdev, "pm domain [%s] is already %sable status.\n",
>> >> + pmd->genpd.name, on ? "en" : "dis");
>> >
>> > Am I missing something here: you've just declared is_on, so it must be
>> > false & therefore this check is all a little pointless? The check just
>> > becomes if (false == on) which I don't think is what you're going for
>> > here? Or did I miss something obvious?
>>
>> Sorry, indeed I missed several lines of code. It should be witten like this:
>> ret = jh71xx_pmu_get_state(pmd, &is_on);
>> if (ret) {
>> dev_dbg(pmu->pdev, "unable to get current state for %s\n",
>> pmd->genpd.name);
>> return ret;
>> }
>
> Heh, this looks more sane :)
>
>>
>> if (is_on == on) {
>> dev_dbg(pmu->pdev, "pm domain [%s] is already %sable status.\n",
>> pmd->genpd.name, on ? "en" : "dis");
>> return 0;
>> }
>>
>> >
>> >> + return 0;
>> >> + }
>> >> +
>> >> + spin_lock_irqsave(&pmu->lock, flags);
>> >> +
>> >> + if (on) {
>> >> + mode = SW_TURN_ON_POWER_MODE;
>> >> + encourage_lo = SW_MODE_ENCOURAGE_EN_LO;
>> >> + encourage_hi = SW_MODE_ENCOURAGE_EN_HI;
>> >> + } else {
>> >> + mode = SW_TURN_OFF_POWER_MODE;
>> >> + encourage_lo = SW_MODE_ENCOURAGE_DIS_LO;
>> >> + encourage_hi = SW_MODE_ENCOURAGE_DIS_HI;
>> >> + }
>> >> +
>> >> + __raw_writel(pmd->mask, pmu->base + mode);
>> >> +
>> >> + /* write SW_ENCOURAGE to make the configuration take effect */
>> >> + __raw_writel(SW_MODE_ENCOURAGE_ON, pmu->base + SW_ENCOURAGE);
>> >
>> > Is register "sticky"? IOW, could you set it in probe and leave this mode
>> > always on? Or does it need to be set every time you want to use this
>> > feature?
>>
>> These power domain registers need to be set by other module according to the
>> specific situation.
>> For example some power domains should be turned off via System PM mechanism
>> when system goes to sleep,
>> and then they are turned on when system resume.
>
> I was just wondering if SW_MODE_ENCOURAGE_ON would retain it's value
> during operation or if it had to be written every time. It's fine if
> that's not the case.
Writing SW_MODE_ENCOURAGE_ON (0xFF) to SW_ENCOURAGE register, the purpose is to reset the
state machine which is going to parse instruction sequence. It has to be written every time.
>
>> >> + __raw_writel(encourage_lo, pmu->base + SW_ENCOURAGE);
>> >> + __raw_writel(encourage_hi, pmu->base + SW_ENCOURAGE);
>> >> +
>> >> + spin_unlock_irqrestore(&pmu->lock, flags);
>> >> +
>> >> + if (on) {
>> >> + ret = readl_poll_timeout_atomic(pmu->base + CURR_POWER_MODE, val,
>> >> + val & pmd->mask, DELAY_US,
>> >> + TIMEOUT_US);
>> >> + if (ret) {
>> >> + dev_err(pmu->pdev, "%s: failed to power on\n", pmd->genpd.name);
>> >> + return -ETIMEDOUT;
>> >> + }
>> >> + } else {
>> >> + ret = readl_poll_timeout_atomic(pmu->base + CURR_POWER_MODE, val,
>> >> + !(val & pmd->mask), DELAY_US,
>> >> + TIMEOUT_US);
>> >
>> > Could you not just decide the 3rd arg outside of the readl_poll..() and
>> > save on duplicating the wait logic here?
>>
>> Seems that the readl_poll..() can only be called in two cases
>> because the CURR_POWER_MODE register is read to val and then operation with mask every time.
>>
>
> I'm sorry, I completely forgot that read*_poll..() actually not actually
> a function. Please ignore this comment!
>
>> >> +static int starfive_pmu_probe(struct platform_device *pdev)
>> >> +{
>> >> + struct device *dev = &pdev->dev;
>> >> + struct device_node *np = dev->of_node;
>> >> + const struct starfive_pmu_data *entry, *table;
>> >> + struct starfive_pmu *pmu;
>> >> + unsigned int i;
>> >> + uint8_t max_bit = 0;
>> >> + int ret;
>> >> +
>> >> + pmu = devm_kzalloc(dev, sizeof(*pmu), GFP_KERNEL);
>> >> + if (!pmu)
>> >> + return -ENOMEM;
>> >> +
>> >> + pmu_base = pmu->base = devm_platform_ioremap_resource(pdev, 0);
>> >> + if (IS_ERR(pmu->base))
>> >> + return PTR_ERR(pmu->base);
>> >> +
>> >> + /* initialize pmu interrupt */
>> >> + pmu->irq = platform_get_irq(pdev, 0);
>> >> + if (pmu->irq < 0)
>> >> + return pmu->irq;
>> >> +
>> >> + ret = devm_request_irq(dev, pmu->irq, starfive_pmu_interrupt,
>> >> + 0, pdev->name, pmu);
>> >> + if (ret)
>> >> + dev_err(dev, "request irq failed.\n");
>> >> +
>> >> + table = of_device_get_match_data(dev);
>> >> + if (!table)
>> >> + return -EINVAL;
>> >> +
>> >> + pmu->pdev = dev;
>> >> + pmu->genpd_data.num_domains = 0;
>> >> + i = 0;
>> >> + for (entry = table; entry->name; entry++) {
>> >> + max_bit = max(max_bit, entry->bit);
>> >> + i++;
>> >> + }
>> >
>> > This looks like something that could be replaced by the functions in
>> > linux/list.h, no? Same below.
>>
>> Nowadays other platforms on linux mainline mostly write in this way or write like this:
>> for (i = 0; i < num; i++) {
>> ...
>> pm_genpd_init(&pmd[i]->genpd, NULL, !is_on);
>> }
>
> That's not what this specific bit of code is doing though, right? You're
> walking jh7110_power_domains to find the highest bit. I was looking at what
> some other drivers do, and took a look at drivers/soc/qcom/rpmhpd.c
> where they know the size of this struct at compile time & so can do
> store the number of power domains in the match data. If you did that,
> you could use a loop like the one other platforms use.
>
>> >> + if (!pmu->genpd)
>> >> + return -ENOMEM;
>> >> +
>> >> + pmu->genpd_data.domains = pmu->genpd;
>> >> +
>> >> + i = 0;
>> >> + for (entry = table; entry->name; entry++) {
>
> And it's the same here. By now, you should know how many power domains
> you have, no?
>
> Anyways, as I said before I don't know much about this power domain
> stuff, it's just that these two loops seem odd.
About the usage of loop, I took a look at other platforms like drivers/soc/qcom/rpmhpd.c,
maybe that way is simpler and easier to understand. I will try to change to that looping in next version.
>
>> >> + struct starfive_power_dev *pmd = &pmu->dev[i];
>> >> + bool is_on;
>> >> +
>> >> + pmd->power = pmu;
>> >> + pmd->mask = BIT(entry->bit);
>> >> + pmd->genpd.name = entry->name;
>> >> + pmd->genpd.flags = entry->flags;
>> >> +
>> >> + ret = starfive_pmu_get_state(pmd, &is_on);
>> >> + if (ret)
>> >> + dev_warn(dev, "unable to get current state for %s\n",
>> >> + pmd->genpd.name);
>
>> >> +static const struct starfive_pmu_data jh7110_power_domains[] = {
>> >
>> > Is this driver jh7110 only or actually jh71XX? Have you just started out
>> > by implementing one SoC both intend to support both?
>>
>> JH7110 is our first SoC, probably the next generation of SoC jh7120 still
>> use this controller driver.
>> Maybe now the naming for JH71XX is not very suitable.
>
> Right. My question was more about if this supported the JH7100 too, but
> I saw from your answer to Emil that it won't. I don't have a preference
> as to whether you call it jh71xx or jh7110, I'll leave that up to
> yourselves and Emil. This particular struct should still be called
> `jh7110_power_domains` though since it is particular to this SoC, no
> matter what you end up calling the file etc.
Emil has gave me a good suggestion.
>
>> > I don't know /jack/ about power domain stuff, so I can barely review
>> > this at all sadly.
>
>> Thank you for taking the time to review the code, you helped me a lot.
>> Thank you so much.
>
> No worries, looking forward to getting my board :)
>
Have you purchased a VisionFive 2 board online? Do you need the shopping link ?
https://forum.rvspace.org/t/how-to-purchase-visionfive-2/665
Best Regards,
Walker Chen