[PATCH v13 03/10] qcom: spm: Add Subsystem Power Manager driver

Daniel Lezcano daniel.lezcano at linaro.org
Tue Dec 2 01:53:30 PST 2014


On 12/01/2014 07:50 PM, Lina Iyer wrote:
> On Thu, Nov 27 2014 at 01:52 -0700, Daniel Lezcano wrote:
>> On 11/27/2014 06:24 AM, Lina Iyer wrote:
>
>> +    static bool cpuidle_drv_init;
>>
>>        ^^^^^^^^^
>>
>> As already said in a previous comment, please find a way to remove that.
>>
> I will look into it. Stephen and I wanted the cpuidle driver to be
> probed only after any of the SPMs are ready. And possibly, only for that
> cpu, for which the SPM has been probed.
>
> To achieve the SPM -> CPUIDLE Device relation, I havent found a good way
> to do that. Without using CPUIDLE_MULTIPLE_DRIVERS, initializing each
> cpuidle device, separate from the cpuidle driver, requires that I update
> the cpuidle_driver->cpumask after probing each SPM device, to allow for
> only one driver and cpuidle devices only for the probed cpus. Using the
> cpuidle_register_driver(), resets the drv->refcnt in
> __cpuidle_driver_init.
>
> I may need something like this in the else clause of
> CPUIDLE_MULTIPLE_DRIVERS -
>
> static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
> {
>         struct cpuidle_driver *drv = cpuidle_curr_driver;
>
>         if (drv && !cpumask_test_cpu(cpu, drv->cpumask))
>                  drv = NULL;
>
>         return drv;
> }

You don't have to deal with the drv->cpumask and 
CPUIDLE_MULTIPLE_DRIVERS. This field gives the list of the cpus the 
driver will have to potentially handle.

Just register the driver at the first place with the platform device by 
using cpuidle_register_driver(drv), I suggest somewhere else than the 
spm code.

The underlying code will do:

         if (!drv->cpumask)
                 drv->cpumask = (struct cpumask *)cpu_possible_mask;

Until the cpuidle device is not initialized for a specific cpu, the 
cpuidle code will have no effect for this cpu. If you register the 
driver but without registering the cpuidle devices that will result in 
nothing more than a structure initialized but inoperative.

For each SPM being probed:

struct cpuidle_device *dev = &per_cpu(cpuidle_dev, cpu);
dev->cpu = cpu;
cpuidle_register_device(dev);

Note cpuidle_dev is exported via cpuidle.h, so you don't have to 
redeclare a per cpu cpuidle device by your own.

So rephrasing all that:

(1) in cpuidle-qcom register the driver
(2) in the spm code register the device for each spm probe

(1) being called before (2).

If after that in the code you still have a "static bool 
cpuidle_drv_init", then I guess we have a problem in the init sequence 
somewhere else.

> void cpuidle_update_cpumask(struct cpumask *mask) {
>         struct cpuidle_driver *drv;
>
>         spin_lock(&cpuidle_driver_lock);
>         drv = cpuidle_get_driver();
>      if (drv)
>          drv->cpumask = mask ?: cpu_possible_mask;
>         spin_unlock(&cpuidle_driver_lock);
> }
>
> With that, I could register cpuidle driver the first time when the mask
> changes
> from empty and consequent updates would just update the cpumask. (I am not
> sure, if I missed anything in this change). It just seemed far too
> invasive at
> this time, in lieu of the static bool.
>
>>> +    const struct platform_device_info qcom_cpuidle_info = {
>>> +        .name    = "qcom_cpuidle",
>>> +        .id    = -1,
>>> +        .data = &lpm_ops,
>>> +        .size_data = sizeof(lpm_ops),
>>> +    };
>>> +
>>> +    drv = spm_get_drv(pdev, &cpu);
>>> +    if (!drv || cpu < 0)
>>> +        return -EINVAL;
>>
>> As already said in a previous comment, it is not possible to have "cpu
>> < 0" with "drv != NULL", so except I am missing something the test
>> should be:
>>
>>     if (!drv)
>>         return -EINVAL;
>>
> Sorry, done.
>
>> There is something wrong with the init sequence. Don't you find weird
>> you have to backward search for the cpu belonging to the pdev each
>> time the probe function is called ?
>>
>>
> Well, it was that or the SPM device node pointing to the CPU that it
> references. It seems more common to have an iterator than the doubly
> linked device nodes. I dont have a strong preference either way, just
> chose the way that made device nodes easier.
>
>>> +
>>> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> +    drv->reg_base = devm_ioremap_resource(&pdev->dev, res);
>>> +    if (IS_ERR(drv->reg_base))
>>> +        return PTR_ERR(drv->reg_base);
>>> +
>>> +    match_id = of_match_node(spm_match_table, pdev->dev.of_node);
>>> +    if (!match_id)
>>> +        return -ENODEV;
>>> +
>>> +    drv->reg_data = match_id->data;
>>> +
>>> +    /* Write the SPM sequences first.. */
>>> +    addr = drv->reg_base +
>>> drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY];
>>> +    __iowrite32_copy(addr, drv->reg_data->seq,
>>> +            ARRAY_SIZE(drv->reg_data->seq) / 4);
>>> +
>>> +    /*
>>> +     * ..and then the control registers.
>>> +     * On some SoC's if the control registers are written first and
>>> if the
>>> +     * CPU was held in reset, the reset signal could trigger the SPM
>>> state
>>> +     * machine, before the sequences are completely written.
>>> +     */
>>> +    spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
>>> +    spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
>>> +    spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
>>> +
>>> +    spm_register_write(drv, SPM_REG_PMIC_DATA_0,
>>> +                drv->reg_data->pmic_data[0]);
>>> +    spm_register_write(drv, SPM_REG_PMIC_DATA_1,
>>> +                drv->reg_data->pmic_data[1]);
>>> +
>>> +    /*
>>> +     * Ensure all observers see the above register writes before the
>>> +     * cpuidle driver is allowed to use the SPM.
>>> +     */
>>> +    wmb();
>>> +    per_cpu(cpu_spm_drv, cpu) = drv;
>>> +
>>> +    if (!cpuidle_drv_init) {
>>> +        platform_device_register_full(&qcom_cpuidle_info);
>>> +        cpuidle_drv_init = true;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static struct platform_driver spm_driver = {
>>> +    .probe = spm_dev_probe,
>>> +    .driver = {
>>> +        .name = "saw",
>>> +        .of_match_table = spm_match_table,
>>> +    },
>>> +};
>>> +
>>> +module_platform_driver(spm_driver);
>>> +
>>> +MODULE_LICENSE("GPL v2");
>>> +MODULE_DESCRIPTION("SAW power controller driver");
>>> +MODULE_ALIAS("platform:saw");
>>> diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h
>>> new file mode 100644
>>> index 0000000..d9a56d7
>>> --- /dev/null
>>> +++ b/include/soc/qcom/pm.h
>>> @@ -0,0 +1,31 @@
>>> +/*
>>> + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved.
>>> + *
>>> + * This software is licensed under the terms of the GNU General Public
>>> + * License version 2, as published by the Free Software Foundation, and
>>> + * may be copied, distributed, and modified under those terms.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + */
>>> +
>>> +#ifndef __QCOM_PM_H
>>> +#define __QCOM_PM_H
>>> +
>>> +enum pm_sleep_mode {
>>> +    PM_SLEEP_MODE_STBY,
>>> +    PM_SLEEP_MODE_RET,
>>> +    PM_SLEEP_MODE_SPC,
>>> +    PM_SLEEP_MODE_PC,
>>> +    PM_SLEEP_MODE_NR,
>>> +};
>>> +
>>> +struct qcom_cpu_pm_ops {
>>> +    int (*standby)(void *data);
>>> +    int (*spc)(void *data);
>>> +};
>>> +
>>> +#endif  /* __QCOM_PM_H */
>>>
>>
>>
>> --
>> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>>
>> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
>> <http://twitter.com/#!/linaroorg> Twitter |
>> <http://www.linaro.org/linaro-blog/> Blog
>>


-- 
  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog




More information about the linux-arm-kernel mailing list