diff mbox

[v2,1/6] soc: qcom: rpmpd: Add a powerdomain driver to model corners

Message ID 20180525100121.28214-2-rnayak@codeaurora.org (mailing list archive)
State Superseded, archived
Delegated to: Andy Gross
Headers show

Commit Message

Rajendra Nayak May 25, 2018, 10:01 a.m. UTC
The powerdomains for corners just pass the performance state set by the
consumers to the RPM (Remote Power manager) which then takes care
of setting the appropriate voltage on the corresponding rails to
meet the performance needs.

We add all powerdomain data needed on msm8996 here. This driver can easily
be extended by adding data for other qualcomm SoCs as well.

Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
---
 .../devicetree/bindings/power/qcom,rpmpd.txt  |  55 ++++
 drivers/soc/qcom/Kconfig                      |   9 +
 drivers/soc/qcom/Makefile                     |   1 +
 drivers/soc/qcom/rpmpd.c                      | 299 ++++++++++++++++++
 4 files changed, 364 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt
 create mode 100644 drivers/soc/qcom/rpmpd.c

Comments

Ulf Hansson May 30, 2018, 9:17 a.m. UTC | #1
On 25 May 2018 at 12:01, Rajendra Nayak <rnayak@codeaurora.org> wrote:
> The powerdomains for corners just pass the performance state set by the
> consumers to the RPM (Remote Power manager) which then takes care
> of setting the appropriate voltage on the corresponding rails to
> meet the performance needs.
>
> We add all powerdomain data needed on msm8996 here. This driver can easily
> be extended by adding data for other qualcomm SoCs as well.
>
> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
> ---
>  .../devicetree/bindings/power/qcom,rpmpd.txt  |  55 ++++

Please split DT doc changes into separate patches, to simplify review.

>  drivers/soc/qcom/Kconfig                      |   9 +
>  drivers/soc/qcom/Makefile                     |   1 +
>  drivers/soc/qcom/rpmpd.c                      | 299 ++++++++++++++++++
>  4 files changed, 364 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>  create mode 100644 drivers/soc/qcom/rpmpd.c
>

[...]

> +++ b/drivers/soc/qcom/rpmpd.c
> @@ -0,0 +1,299 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_domain.h>
> +#include <linux/mfd/qcom_rpm.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/soc/qcom/smd-rpm.h>
> +
> +#include <dt-bindings/mfd/qcom-rpm.h>
> +
> +#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd)
> +
> +/* Resource types */
> +#define RPMPD_SMPA 0x61706d73
> +#define RPMPD_LDOA 0x616f646c
> +
> +/* Operation Keys */
> +#define KEY_CORNER             0x6e726f63 /* corn */
> +#define KEY_ENABLE             0x6e657773 /* swen */
> +#define KEY_FLOOR_CORNER       0x636676   /* vfc */
> +
> +#define DEFINE_RPMPD_CORN_SMPA(_platform, _name, _active, r_id)                \
> +       static struct rpmpd _platform##_##_active;                      \
> +       static struct rpmpd _platform##_##_name = {                     \
> +               .pd = { .name = #_name, },                              \
> +               .peer = &_platform##_##_active,                         \
> +               .res_type = RPMPD_SMPA,                                 \
> +               .res_id = r_id,                                         \
> +               .key = KEY_CORNER,                                      \
> +       };                                                              \
> +       static struct rpmpd _platform##_##_active = {                   \
> +               .pd = { .name = #_active, },                            \
> +               .peer = &_platform##_##_name,                           \
> +               .active_only = true,                                    \
> +               .res_type = RPMPD_SMPA,                                 \
> +               .res_id = r_id,                                         \
> +               .key = KEY_CORNER,                                      \
> +       }
> +
> +#define DEFINE_RPMPD_CORN_LDOA(_platform, _name, r_id)                 \
> +       static struct rpmpd _platform##_##_name = {                     \
> +               .pd = { .name = #_name, },                              \
> +               .res_type = RPMPD_LDOA,                                 \
> +               .res_id = r_id,                                         \
> +               .key = KEY_CORNER,                                      \
> +       }
> +
> +#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type)               \
> +       static struct rpmpd _platform##_##_name = {                     \
> +               .pd = { .name = #_name, },                              \
> +               .res_type = r_type,                                     \
> +               .res_id = r_id,                                         \
> +               .key = KEY_FLOOR_CORNER,                                \
> +       }
> +
> +#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id)                  \
> +       DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA)
> +
> +#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id)                  \
> +       DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA)
> +
> +struct rpmpd_req {
> +       __le32 key;
> +       __le32 nbytes;
> +       __le32 value;
> +};
> +
> +struct rpmpd {
> +       struct generic_pm_domain pd;
> +       struct rpmpd *peer;
> +       const bool active_only;
> +       unsigned long corner;
> +       bool enabled;
> +       const char *res_name;
> +       const int res_type;
> +       const int res_id;
> +       struct qcom_smd_rpm *rpm;
> +       __le32 key;
> +};
> +
> +struct rpmpd_desc {
> +       struct rpmpd **rpmpds;
> +       size_t num_pds;
> +};
> +
> +static DEFINE_MUTEX(rpmpd_lock);
> +
> +/* msm8996 RPM powerdomains */
> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
> +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
> +
> +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
> +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
> +
> +static struct rpmpd *msm8996_rpmpds[] = {
> +       [0] = &msm8996_vddcx,
> +       [1] = &msm8996_vddcx_ao,
> +       [2] = &msm8996_vddcx_vfc,
> +       [3] = &msm8996_vddmx,
> +       [4] = &msm8996_vddmx_ao,
> +       [5] = &msm8996_vddsscx,
> +       [6] = &msm8996_vddsscx_vfc,
> +};

It's not my call, but honestly the above all macros makes the code
less readable.

Anyway, I think you should convert to allocate these structs
dynamically from the heap (kzalloc/kcalloc), instead of statically as
above.

> +
> +static const struct rpmpd_desc msm8996_desc = {
> +       .rpmpds = msm8996_rpmpds,
> +       .num_pds = ARRAY_SIZE(msm8996_rpmpds),
> +};
> +
> +static const struct of_device_id rpmpd_match_table[] = {
> +       { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, rpmpd_match_table);

[...]

> +static int rpmpd_aggregate_corner(struct rpmpd *pd)
> +{

Isn't the aggregation of the performance states in genpd sufficient
for your case?

I guess this is SoC specific and needed anyways, but then could you
perhaps add a few comments about what goes on here?

> +       int ret;
> +       struct rpmpd *peer = pd->peer;
> +       unsigned long active_corner, sleep_corner;
> +       unsigned long this_corner = 0, this_sleep_corner = 0;
> +       unsigned long peer_corner = 0, peer_sleep_corner = 0;
> +
> +       to_active_sleep(pd, pd->corner, &this_corner, &this_sleep_corner);
> +
> +       if (peer && peer->enabled)
> +               to_active_sleep(peer, peer->corner, &peer_corner,
> +                               &peer_sleep_corner);
> +
> +       active_corner = max(this_corner, peer_corner);
> +
> +       ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner);
> +       if (ret)
> +               return ret;
> +
> +       sleep_corner = max(this_sleep_corner, peer_sleep_corner);
> +
> +       return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner);
> +}

[...]

> +static int rpmpd_probe(struct platform_device *pdev)
> +{
> +       int i;
> +       size_t num;
> +       struct genpd_onecell_data *data;
> +       struct qcom_smd_rpm *rpm;
> +       struct rpmpd **rpmpds;
> +       const struct rpmpd_desc *desc;
> +
> +       rpm = dev_get_drvdata(pdev->dev.parent);
> +       if (!rpm) {
> +               dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n");
> +               return -ENODEV;
> +       }
> +
> +       desc = of_device_get_match_data(&pdev->dev);
> +       if (!desc)
> +               return -EINVAL;
> +
> +       rpmpds = desc->rpmpds;
> +       num = desc->num_pds;
> +
> +       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
> +
> +       data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains),
> +                                    GFP_KERNEL);
> +       data->num_domains = num;
> +
> +       for (i = 0; i < num; i++) {
> +               if (!rpmpds[i])
> +                       continue;
> +
> +               rpmpds[i]->rpm = rpm;
> +               rpmpds[i]->pd.power_off = rpmpd_power_off;
> +               rpmpds[i]->pd.power_on = rpmpd_power_on;
> +               pm_genpd_init(&rpmpds[i]->pd, NULL, true);

Question: Is there no hierarchical topology of the PM domains. No
genpd subdomains?

> +
> +               data->domains[i] = &rpmpds[i]->pd;
> +       }
> +
> +       return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
> +}
> +
> +static int rpmpd_remove(struct platform_device *pdev)
> +{
> +       of_genpd_del_provider(pdev->dev.of_node);
> +       return 0;
> +}
> +
> +static struct platform_driver rpmpd_driver = {
> +       .driver = {
> +               .name = "qcom-rpmpd",
> +               .of_match_table = rpmpd_match_table,
> +       },
> +       .probe = rpmpd_probe,
> +       .remove = rpmpd_remove,
> +};
> +
> +static int __init rpmpd_init(void)
> +{
> +       return platform_driver_register(&rpmpd_driver);
> +}
> +core_initcall(rpmpd_init);
> +
> +static void __exit rpmpd_exit(void)
> +{
> +       platform_driver_unregister(&rpmpd_driver);
> +}
> +module_exit(rpmpd_exit);
> +
> +MODULE_DESCRIPTION("Qualcomm RPM Power Domain Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:qcom-rpmpd");
> --
> QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
>

Besides the minor things above, this looks good to me.

Kind regards
Uffe
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rajendra Nayak May 30, 2018, 10:14 a.m. UTC | #2
On 05/30/2018 02:47 PM, Ulf Hansson wrote:
> On 25 May 2018 at 12:01, Rajendra Nayak <rnayak@codeaurora.org> wrote:
>> The powerdomains for corners just pass the performance state set by the
>> consumers to the RPM (Remote Power manager) which then takes care
>> of setting the appropriate voltage on the corresponding rails to
>> meet the performance needs.
>>
>> We add all powerdomain data needed on msm8996 here. This driver can easily
>> be extended by adding data for other qualcomm SoCs as well.
>>
>> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
>> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
>> ---
>>  .../devicetree/bindings/power/qcom,rpmpd.txt  |  55 ++++
> 
> Please split DT doc changes into separate patches, to simplify review.

yes, i will split it when I resend the series.

> 
>>  drivers/soc/qcom/Kconfig                      |   9 +
>>  drivers/soc/qcom/Makefile                     |   1 +
>>  drivers/soc/qcom/rpmpd.c                      | 299 ++++++++++++++++++
>>  4 files changed, 364 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>>  create mode 100644 drivers/soc/qcom/rpmpd.c
>>
> 
> [...]
> 
>> +++ b/drivers/soc/qcom/rpmpd.c
>> @@ -0,0 +1,299 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/init.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/pm_domain.h>
>> +#include <linux/mfd/qcom_rpm.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/soc/qcom/smd-rpm.h>
>> +
>> +#include <dt-bindings/mfd/qcom-rpm.h>
>> +
>> +#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd)
>> +
>> +/* Resource types */
>> +#define RPMPD_SMPA 0x61706d73
>> +#define RPMPD_LDOA 0x616f646c
>> +
>> +/* Operation Keys */
>> +#define KEY_CORNER             0x6e726f63 /* corn */
>> +#define KEY_ENABLE             0x6e657773 /* swen */
>> +#define KEY_FLOOR_CORNER       0x636676   /* vfc */
>> +
>> +#define DEFINE_RPMPD_CORN_SMPA(_platform, _name, _active, r_id)                \
>> +       static struct rpmpd _platform##_##_active;                      \
>> +       static struct rpmpd _platform##_##_name = {                     \
>> +               .pd = { .name = #_name, },                              \
>> +               .peer = &_platform##_##_active,                         \
>> +               .res_type = RPMPD_SMPA,                                 \
>> +               .res_id = r_id,                                         \
>> +               .key = KEY_CORNER,                                      \
>> +       };                                                              \
>> +       static struct rpmpd _platform##_##_active = {                   \
>> +               .pd = { .name = #_active, },                            \
>> +               .peer = &_platform##_##_name,                           \
>> +               .active_only = true,                                    \
>> +               .res_type = RPMPD_SMPA,                                 \
>> +               .res_id = r_id,                                         \
>> +               .key = KEY_CORNER,                                      \
>> +       }
>> +
>> +#define DEFINE_RPMPD_CORN_LDOA(_platform, _name, r_id)                 \
>> +       static struct rpmpd _platform##_##_name = {                     \
>> +               .pd = { .name = #_name, },                              \
>> +               .res_type = RPMPD_LDOA,                                 \
>> +               .res_id = r_id,                                         \
>> +               .key = KEY_CORNER,                                      \
>> +       }
>> +
>> +#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type)               \
>> +       static struct rpmpd _platform##_##_name = {                     \
>> +               .pd = { .name = #_name, },                              \
>> +               .res_type = r_type,                                     \
>> +               .res_id = r_id,                                         \
>> +               .key = KEY_FLOOR_CORNER,                                \
>> +       }
>> +
>> +#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id)                  \
>> +       DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA)
>> +
>> +#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id)                  \
>> +       DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA)
>> +
>> +struct rpmpd_req {
>> +       __le32 key;
>> +       __le32 nbytes;
>> +       __le32 value;
>> +};
>> +
>> +struct rpmpd {
>> +       struct generic_pm_domain pd;
>> +       struct rpmpd *peer;
>> +       const bool active_only;
>> +       unsigned long corner;
>> +       bool enabled;
>> +       const char *res_name;
>> +       const int res_type;
>> +       const int res_id;
>> +       struct qcom_smd_rpm *rpm;
>> +       __le32 key;
>> +};
>> +
>> +struct rpmpd_desc {
>> +       struct rpmpd **rpmpds;
>> +       size_t num_pds;
>> +};
>> +
>> +static DEFINE_MUTEX(rpmpd_lock);
>> +
>> +/* msm8996 RPM powerdomains */
>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
>> +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
>> +
>> +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
>> +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
>> +
>> +static struct rpmpd *msm8996_rpmpds[] = {
>> +       [0] = &msm8996_vddcx,
>> +       [1] = &msm8996_vddcx_ao,
>> +       [2] = &msm8996_vddcx_vfc,
>> +       [3] = &msm8996_vddmx,
>> +       [4] = &msm8996_vddmx_ao,
>> +       [5] = &msm8996_vddsscx,
>> +       [6] = &msm8996_vddsscx_vfc,
>> +};
> 
> It's not my call, but honestly the above all macros makes the code
> less readable.

This is all static data per SoC. The macros will keep the new additions
needed for every new SoC to a minimal. Currently this supports only
msm8996.

> 
> Anyway, I think you should convert to allocate these structs
> dynamically from the heap (kzalloc/kcalloc), instead of statically as
> above.
> 
>> +
>> +static const struct rpmpd_desc msm8996_desc = {
>> +       .rpmpds = msm8996_rpmpds,
>> +       .num_pds = ARRAY_SIZE(msm8996_rpmpds),
>> +};
>> +
>> +static const struct of_device_id rpmpd_match_table[] = {
>> +       { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, rpmpd_match_table);
> 
> [...]
> 
>> +static int rpmpd_aggregate_corner(struct rpmpd *pd)
>> +{
> 
> Isn't the aggregation of the performance states in genpd sufficient
> for your case?
> 
> I guess this is SoC specific and needed anyways, but then could you
> perhaps add a few comments about what goes on here?

Yes, this is SoC specific aggregation for active and sleep votes.
i will add comments to clarify this is different from the aggregation
done at the framework level.

> 
>> +       int ret;
>> +       struct rpmpd *peer = pd->peer;
>> +       unsigned long active_corner, sleep_corner;
>> +       unsigned long this_corner = 0, this_sleep_corner = 0;
>> +       unsigned long peer_corner = 0, peer_sleep_corner = 0;
>> +
>> +       to_active_sleep(pd, pd->corner, &this_corner, &this_sleep_corner);
>> +
>> +       if (peer && peer->enabled)
>> +               to_active_sleep(peer, peer->corner, &peer_corner,
>> +                               &peer_sleep_corner);
>> +
>> +       active_corner = max(this_corner, peer_corner);
>> +
>> +       ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner);
>> +       if (ret)
>> +               return ret;
>> +
>> +       sleep_corner = max(this_sleep_corner, peer_sleep_corner);
>> +
>> +       return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner);
>> +}
> 
> [...]
> 
>> +static int rpmpd_probe(struct platform_device *pdev)
>> +{
>> +       int i;
>> +       size_t num;
>> +       struct genpd_onecell_data *data;
>> +       struct qcom_smd_rpm *rpm;
>> +       struct rpmpd **rpmpds;
>> +       const struct rpmpd_desc *desc;
>> +
>> +       rpm = dev_get_drvdata(pdev->dev.parent);
>> +       if (!rpm) {
>> +               dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       desc = of_device_get_match_data(&pdev->dev);
>> +       if (!desc)
>> +               return -EINVAL;
>> +
>> +       rpmpds = desc->rpmpds;
>> +       num = desc->num_pds;
>> +
>> +       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
>> +       if (!data)
>> +               return -ENOMEM;
>> +
>> +       data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains),
>> +                                    GFP_KERNEL);
>> +       data->num_domains = num;
>> +
>> +       for (i = 0; i < num; i++) {
>> +               if (!rpmpds[i])
>> +                       continue;
>> +
>> +               rpmpds[i]->rpm = rpm;
>> +               rpmpds[i]->pd.power_off = rpmpd_power_off;
>> +               rpmpds[i]->pd.power_on = rpmpd_power_on;
>> +               pm_genpd_init(&rpmpds[i]->pd, NULL, true);
> 
> Question: Is there no hierarchical topology of the PM domains. No
> genpd subdomains?

The hierarchy if any is all handled by the remote core (RPM in this case).
For Linux its just a flat view.

> 
>> +
>> +               data->domains[i] = &rpmpds[i]->pd;
>> +       }
>> +
>> +       return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
>> +}
>> +
>> +static int rpmpd_remove(struct platform_device *pdev)
>> +{
>> +       of_genpd_del_provider(pdev->dev.of_node);
>> +       return 0;
>> +}
>> +
>> +static struct platform_driver rpmpd_driver = {
>> +       .driver = {
>> +               .name = "qcom-rpmpd",
>> +               .of_match_table = rpmpd_match_table,
>> +       },
>> +       .probe = rpmpd_probe,
>> +       .remove = rpmpd_remove,
>> +};
>> +
>> +static int __init rpmpd_init(void)
>> +{
>> +       return platform_driver_register(&rpmpd_driver);
>> +}
>> +core_initcall(rpmpd_init);
>> +
>> +static void __exit rpmpd_exit(void)
>> +{
>> +       platform_driver_unregister(&rpmpd_driver);
>> +}
>> +module_exit(rpmpd_exit);
>> +
>> +MODULE_DESCRIPTION("Qualcomm RPM Power Domain Driver");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_ALIAS("platform:qcom-rpmpd");
>> --
>> QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
>> of Code Aurora Forum, hosted by The Linux Foundation
>>
> 
> Besides the minor things above, this looks good to me.

thanks,
Rajendra
Ulf Hansson May 30, 2018, 12:44 p.m. UTC | #3
[...]

>>> +
>>> +static DEFINE_MUTEX(rpmpd_lock);
>>> +
>>> +/* msm8996 RPM powerdomains */
>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
>>> +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
>>> +
>>> +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
>>> +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
>>> +
>>> +static struct rpmpd *msm8996_rpmpds[] = {
>>> +       [0] = &msm8996_vddcx,
>>> +       [1] = &msm8996_vddcx_ao,
>>> +       [2] = &msm8996_vddcx_vfc,
>>> +       [3] = &msm8996_vddmx,
>>> +       [4] = &msm8996_vddmx_ao,
>>> +       [5] = &msm8996_vddsscx,
>>> +       [6] = &msm8996_vddsscx_vfc,
>>> +};
>>
>> It's not my call, but honestly the above all macros makes the code
>> less readable.
>
> This is all static data per SoC. The macros will keep the new additions
> needed for every new SoC to a minimal. Currently this supports only
> msm8996.

Right, that's fine then.

>
>>
>> Anyway, I think you should convert to allocate these structs
>> dynamically from the heap (kzalloc/kcalloc), instead of statically as
>> above.

However, I assume this is still doable!?

[...]

>>> +       for (i = 0; i < num; i++) {
>>> +               if (!rpmpds[i])
>>> +                       continue;
>>> +
>>> +               rpmpds[i]->rpm = rpm;
>>> +               rpmpds[i]->pd.power_off = rpmpd_power_off;
>>> +               rpmpds[i]->pd.power_on = rpmpd_power_on;
>>> +               pm_genpd_init(&rpmpds[i]->pd, NULL, true);
>>
>> Question: Is there no hierarchical topology of the PM domains. No
>> genpd subdomains?
>
> The hierarchy if any is all handled by the remote core (RPM in this case).
> For Linux its just a flat view.

Okay, thanks for clarifying!

Kind regards
Uffe
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Collins May 30, 2018, 6:27 p.m. UTC | #4
Hello Rajendra,

On 05/30/2018 03:14 AM, Rajendra Nayak wrote:
> On 05/30/2018 02:47 PM, Ulf Hansson wrote:
>> On 25 May 2018 at 12:01, Rajendra Nayak <rnayak@codeaurora.org> wrote:
...
>>> +               pm_genpd_init(&rpmpds[i]->pd, NULL, true);
>>
>> Question: Is there no hierarchical topology of the PM domains. No
>> genpd subdomains?
> 
> The hierarchy if any is all handled by the remote core (RPM in this case).
> For Linux its just a flat view.

There is one special case that we'll need to handle somehow.  The APPS
vlvl request for VDD_MX needs to be greater than or equal to the vlvl
request for VDD_CX.  Can you please add the necessary code to achieve
this?  RPMh hardware doesn't handle this hardware requirement due to
concerns about modem use case latency.

Please note that this is handled in a somewhat hacky manner [1] with the
downstream rpmh-regulator driver by specifying VDD_MX as the parent of
VDD_CX and VDD_MX_AO as the parent of VDD_CX_AO with a dropout voltage of
-1.  That way, enabling CX causes MX to be enabled and voltage level
requests are propagated from CX to MX (the -1 is ignored because it is
rounded up within the sparse vlvl numbering space).

Thanks,
David

[1]:
https://source.codeaurora.org/quic/la/kernel/msm-4.9/tree/arch/arm64/boot/dts/qcom/sdm845-regulator.dtsi?h=msm-4.9#n135
Rob Herring (Arm) May 31, 2018, 3:27 a.m. UTC | #5
On Fri, May 25, 2018 at 03:31:16PM +0530, Rajendra Nayak wrote:
> The powerdomains for corners just pass the performance state set by the
> consumers to the RPM (Remote Power manager) which then takes care
> of setting the appropriate voltage on the corresponding rails to
> meet the performance needs.
> 
> We add all powerdomain data needed on msm8996 here. This driver can easily
> be extended by adding data for other qualcomm SoCs as well.
> 
> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
> ---
>  .../devicetree/bindings/power/qcom,rpmpd.txt  |  55 ++++
>  drivers/soc/qcom/Kconfig                      |   9 +
>  drivers/soc/qcom/Makefile                     |   1 +
>  drivers/soc/qcom/rpmpd.c                      | 299 ++++++++++++++++++
>  4 files changed, 364 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>  create mode 100644 drivers/soc/qcom/rpmpd.c
> 
> diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
> new file mode 100644
> index 000000000000..68f620a2af0d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
> @@ -0,0 +1,55 @@
> +Qualcomm RPM Powerdomains
> +
> +* For RPM powerdomains, we communicate a performance state to RPM
> +which then translates it into a corresponding voltage on a rail
> +
> +Required Properties:
> + - compatible: Should be one of the following
> +	* qcom,msm8996-rpmpd: RPM Powerdomain for the msm8996 family of SoC
> + - power-domain-cells: number of cells in power domain specifier
> +	must be 1.
> + - operating-points-v2: Phandle to the OPP table for the power-domain.
> +	Refer to Documentation/devicetree/bindings/power/power_domain.txt
> +	and Documentation/devicetree/bindings/opp/qcom-opp.txt for more details
> +
> +Example:
> +
> +	rpmpd: power-controller {
> +		compatible = "qcom,msm8996-rpmpd";
> +		#power-domain-cells = <1>;
> +		operating-points-v2 = <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>,
> +				      <&rpmpd_opp_table>;
> +	};
> +
> +	rpmpd_opp_table: opp-table {
> +		compatible = "operating-points-v2-qcom-level", "operating-points-v2";
> +
> +		rpmpd_opp1: opp@1 {

unit-address without reg property is not valid.

> +			qcom,level = <1>;

Is this the only property? If so, I don't see the point in trying to use 
operating-points-v2 here.

Rob
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rajendra Nayak May 31, 2018, 3:53 a.m. UTC | #6
Hi David,

On 05/30/2018 11:57 PM, David Collins wrote:
> Hello Rajendra,
> 
> On 05/30/2018 03:14 AM, Rajendra Nayak wrote:
>> On 05/30/2018 02:47 PM, Ulf Hansson wrote:
>>> On 25 May 2018 at 12:01, Rajendra Nayak <rnayak@codeaurora.org> wrote:
> ...
>>>> +               pm_genpd_init(&rpmpds[i]->pd, NULL, true);
>>>
>>> Question: Is there no hierarchical topology of the PM domains. No
>>> genpd subdomains?
>>
>> The hierarchy if any is all handled by the remote core (RPM in this case).
>> For Linux its just a flat view.
> 
> There is one special case that we'll need to handle somehow.  The APPS
> vlvl request for VDD_MX needs to be greater than or equal to the vlvl
> request for VDD_CX.  Can you please add the necessary code to achieve
> this?  RPMh hardware doesn't handle this hardware requirement due to
> concerns about modem use case latency.

Sure, I'll take a look at it.

> 
> Please note that this is handled in a somewhat hacky manner [1] with the
> downstream rpmh-regulator driver by specifying VDD_MX as the parent of
> VDD_CX and VDD_MX_AO as the parent of VDD_CX_AO with a dropout voltage of
> -1.  That way, enabling CX causes MX to be enabled and voltage level
> requests are propagated from CX to MX (the -1 is ignored because it is
> rounded up within the sparse vlvl numbering space).

I can't see how else to handle this but with a fake parent/child relation,
which also means we might need support to propagate performance states for
power domains up the parents, which I think was initially supported but
later dropped since we thought this wasn't needed for now.
We might need to take a re-look at it to support this usecase.

thanks,
Rajendra
Rajendra Nayak May 31, 2018, 4:14 a.m. UTC | #7
On 05/31/2018 08:57 AM, Rob Herring wrote:
> On Fri, May 25, 2018 at 03:31:16PM +0530, Rajendra Nayak wrote:
>> The powerdomains for corners just pass the performance state set by the
>> consumers to the RPM (Remote Power manager) which then takes care
>> of setting the appropriate voltage on the corresponding rails to
>> meet the performance needs.
>>
>> We add all powerdomain data needed on msm8996 here. This driver can easily
>> be extended by adding data for other qualcomm SoCs as well.
>>
>> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
>> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
>> ---
>>  .../devicetree/bindings/power/qcom,rpmpd.txt  |  55 ++++
>>  drivers/soc/qcom/Kconfig                      |   9 +
>>  drivers/soc/qcom/Makefile                     |   1 +
>>  drivers/soc/qcom/rpmpd.c                      | 299 ++++++++++++++++++
>>  4 files changed, 364 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>>  create mode 100644 drivers/soc/qcom/rpmpd.c
>>
>> diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>> new file mode 100644
>> index 000000000000..68f620a2af0d
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
>> @@ -0,0 +1,55 @@
>> +Qualcomm RPM Powerdomains
>> +
>> +* For RPM powerdomains, we communicate a performance state to RPM
>> +which then translates it into a corresponding voltage on a rail
>> +
>> +Required Properties:
>> + - compatible: Should be one of the following
>> +	* qcom,msm8996-rpmpd: RPM Powerdomain for the msm8996 family of SoC
>> + - power-domain-cells: number of cells in power domain specifier
>> +	must be 1.
>> + - operating-points-v2: Phandle to the OPP table for the power-domain.
>> +	Refer to Documentation/devicetree/bindings/power/power_domain.txt
>> +	and Documentation/devicetree/bindings/opp/qcom-opp.txt for more details
>> +
>> +Example:
>> +
>> +	rpmpd: power-controller {
>> +		compatible = "qcom,msm8996-rpmpd";
>> +		#power-domain-cells = <1>;
>> +		operating-points-v2 = <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>,
>> +				      <&rpmpd_opp_table>;
>> +	};
>> +
>> +	rpmpd_opp_table: opp-table {
>> +		compatible = "operating-points-v2-qcom-level", "operating-points-v2";
>> +
>> +		rpmpd_opp1: opp@1 {
> 
> unit-address without reg property is not valid.
> 
>> +			qcom,level = <1>;
> 
> Is this the only property? If so, I don't see the point in trying to use 
> operating-points-v2 here.

Thanks, I'll drop the unit address above and the compatible here.
Rajendra Nayak May 31, 2018, 4:20 a.m. UTC | #8
On 05/30/2018 06:14 PM, Ulf Hansson wrote:
> [...]
> 
>>>> +
>>>> +static DEFINE_MUTEX(rpmpd_lock);
>>>> +
>>>> +/* msm8996 RPM powerdomains */
>>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
>>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
>>>> +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
>>>> +
>>>> +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
>>>> +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
>>>> +
>>>> +static struct rpmpd *msm8996_rpmpds[] = {
>>>> +       [0] = &msm8996_vddcx,
>>>> +       [1] = &msm8996_vddcx_ao,
>>>> +       [2] = &msm8996_vddcx_vfc,
>>>> +       [3] = &msm8996_vddmx,
>>>> +       [4] = &msm8996_vddmx_ao,
>>>> +       [5] = &msm8996_vddsscx,
>>>> +       [6] = &msm8996_vddsscx_vfc,
>>>> +};
>>>
>>> It's not my call, but honestly the above all macros makes the code
>>> less readable.
>>
>> This is all static data per SoC. The macros will keep the new additions
>> needed for every new SoC to a minimal. Currently this supports only
>> msm8996.
> 
> Right, that's fine then.
> 
>>
>>>
>>> Anyway, I think you should convert to allocate these structs
>>> dynamically from the heap (kzalloc/kcalloc), instead of statically as
>>> above.
> 
> However, I assume this is still doable!?

Perhaps it is, but is there any specific advantage of constructing these structures
dynamically vs statically, given they are static data?
Most other powerdomain/clock/regulator drivers I see do it statically, and thats
what I followed.
Ulf Hansson May 31, 2018, 11:09 a.m. UTC | #9
On 31 May 2018 at 06:20, Rajendra Nayak <rnayak@codeaurora.org> wrote:
>
>
> On 05/30/2018 06:14 PM, Ulf Hansson wrote:
>> [...]
>>
>>>>> +
>>>>> +static DEFINE_MUTEX(rpmpd_lock);
>>>>> +
>>>>> +/* msm8996 RPM powerdomains */
>>>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
>>>>> +DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
>>>>> +DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
>>>>> +
>>>>> +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
>>>>> +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
>>>>> +
>>>>> +static struct rpmpd *msm8996_rpmpds[] = {
>>>>> +       [0] = &msm8996_vddcx,
>>>>> +       [1] = &msm8996_vddcx_ao,
>>>>> +       [2] = &msm8996_vddcx_vfc,
>>>>> +       [3] = &msm8996_vddmx,
>>>>> +       [4] = &msm8996_vddmx_ao,
>>>>> +       [5] = &msm8996_vddsscx,
>>>>> +       [6] = &msm8996_vddsscx_vfc,
>>>>> +};
>>>>
>>>> It's not my call, but honestly the above all macros makes the code
>>>> less readable.
>>>
>>> This is all static data per SoC. The macros will keep the new additions
>>> needed for every new SoC to a minimal. Currently this supports only
>>> msm8996.
>>
>> Right, that's fine then.
>>
>>>
>>>>
>>>> Anyway, I think you should convert to allocate these structs
>>>> dynamically from the heap (kzalloc/kcalloc), instead of statically as
>>>> above.
>>
>> However, I assume this is still doable!?
>
> Perhaps it is, but is there any specific advantage of constructing these structures
> dynamically vs statically, given they are static data?

Well, I was just thinking that the genpd struct has grown quite big.

> Most other powerdomain/clock/regulator drivers I see do it statically, and thats
> what I followed.

Right, so forget it and keep it as is.

Kind regards
Uffe
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
new file mode 100644
index 000000000000..68f620a2af0d
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt
@@ -0,0 +1,55 @@ 
+Qualcomm RPM Powerdomains
+
+* For RPM powerdomains, we communicate a performance state to RPM
+which then translates it into a corresponding voltage on a rail
+
+Required Properties:
+ - compatible: Should be one of the following
+	* qcom,msm8996-rpmpd: RPM Powerdomain for the msm8996 family of SoC
+ - power-domain-cells: number of cells in power domain specifier
+	must be 1.
+ - operating-points-v2: Phandle to the OPP table for the power-domain.
+	Refer to Documentation/devicetree/bindings/power/power_domain.txt
+	and Documentation/devicetree/bindings/opp/qcom-opp.txt for more details
+
+Example:
+
+	rpmpd: power-controller {
+		compatible = "qcom,msm8996-rpmpd";
+		#power-domain-cells = <1>;
+		operating-points-v2 = <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>,
+				      <&rpmpd_opp_table>;
+	};
+
+	rpmpd_opp_table: opp-table {
+		compatible = "operating-points-v2-qcom-level", "operating-points-v2";
+
+		rpmpd_opp1: opp@1 {
+			qcom,level = <1>;
+		};
+
+		rpmpd_opp2: opp@2 {
+			qcom,level = <2>;
+		};
+
+		rpmpd_opp3: opp@3 {
+			qcom,level = <3>;
+		};
+
+		rpmpd_opp4: opp@4 {
+			qcom,level = <4>;
+		};
+
+		rpmpd_opp5: opp@5 {
+			qcom,level = <5>;
+		};
+
+		rpmpd_opp6: opp@6 {
+			qcom,level = <6>;
+		};
+	};
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 9dc02f390ba3..a7a405178967 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -74,6 +74,15 @@  config QCOM_RMTFS_MEM
 
 	  Say y here if you intend to boot the modem remoteproc.
 
+config QCOM_RPMPD
+	tristate "Qualcomm RPM Powerdomain driver"
+	depends on MFD_QCOM_RPM && QCOM_SMD_RPM
+	help
+	  QCOM RPM powerdomain driver to support powerdomain with
+	  performance states. The driver communicates a performance state
+	  value to RPM which then translates it into corresponding voltage
+	  for the voltage rail.
+
 config QCOM_SMEM
 	tristate "Qualcomm Shared Memory Manager (SMEM)"
 	depends on ARCH_QCOM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 19dcf957cb3a..9550c170de93 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -15,3 +15,4 @@  obj-$(CONFIG_QCOM_SMP2P)	+= smp2p.o
 obj-$(CONFIG_QCOM_SMSM)	+= smsm.o
 obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
 obj-$(CONFIG_QCOM_APR) += apr.o
+obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o
diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c
new file mode 100644
index 000000000000..df6427d43414
--- /dev/null
+++ b/drivers/soc/qcom/rpmpd.c
@@ -0,0 +1,299 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_domain.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/soc/qcom/smd-rpm.h>
+
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd)
+
+/* Resource types */
+#define RPMPD_SMPA 0x61706d73
+#define RPMPD_LDOA 0x616f646c
+
+/* Operation Keys */
+#define KEY_CORNER		0x6e726f63 /* corn */
+#define KEY_ENABLE		0x6e657773 /* swen */
+#define KEY_FLOOR_CORNER	0x636676   /* vfc */
+
+#define DEFINE_RPMPD_CORN_SMPA(_platform, _name, _active, r_id)		\
+	static struct rpmpd _platform##_##_active;			\
+	static struct rpmpd _platform##_##_name = {			\
+		.pd = {	.name = #_name,	},				\
+		.peer = &_platform##_##_active,				\
+		.res_type = RPMPD_SMPA,					\
+		.res_id = r_id,						\
+		.key = KEY_CORNER,					\
+	};								\
+	static struct rpmpd _platform##_##_active = {			\
+		.pd = { .name = #_active, },				\
+		.peer = &_platform##_##_name,				\
+		.active_only = true,					\
+		.res_type = RPMPD_SMPA,					\
+		.res_id = r_id,						\
+		.key = KEY_CORNER,					\
+	}
+
+#define DEFINE_RPMPD_CORN_LDOA(_platform, _name, r_id)			\
+	static struct rpmpd _platform##_##_name = {			\
+		.pd = { .name = #_name, },				\
+		.res_type = RPMPD_LDOA,					\
+		.res_id = r_id,						\
+		.key = KEY_CORNER,					\
+	}
+
+#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type)		\
+	static struct rpmpd _platform##_##_name = {			\
+		.pd = { .name = #_name, },				\
+		.res_type = r_type,					\
+		.res_id = r_id,						\
+		.key = KEY_FLOOR_CORNER,				\
+	}
+
+#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id)			\
+	DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA)
+
+#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id)			\
+	DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA)
+
+struct rpmpd_req {
+	__le32 key;
+	__le32 nbytes;
+	__le32 value;
+};
+
+struct rpmpd {
+	struct generic_pm_domain pd;
+	struct rpmpd *peer;
+	const bool active_only;
+	unsigned long corner;
+	bool enabled;
+	const char *res_name;
+	const int res_type;
+	const int res_id;
+	struct qcom_smd_rpm *rpm;
+	__le32 key;
+};
+
+struct rpmpd_desc {
+	struct rpmpd **rpmpds;
+	size_t num_pds;
+};
+
+static DEFINE_MUTEX(rpmpd_lock);
+
+/* msm8996 RPM powerdomains */
+DEFINE_RPMPD_CORN_SMPA(msm8996, vddcx, vddcx_ao, 1);
+DEFINE_RPMPD_CORN_SMPA(msm8996, vddmx, vddmx_ao, 2);
+DEFINE_RPMPD_CORN_LDOA(msm8996, vddsscx, 26);
+
+DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
+DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
+
+static struct rpmpd *msm8996_rpmpds[] = {
+	[0] = &msm8996_vddcx,
+	[1] = &msm8996_vddcx_ao,
+	[2] = &msm8996_vddcx_vfc,
+	[3] = &msm8996_vddmx,
+	[4] = &msm8996_vddmx_ao,
+	[5] = &msm8996_vddsscx,
+	[6] = &msm8996_vddsscx_vfc,
+};
+
+static const struct rpmpd_desc msm8996_desc = {
+	.rpmpds = msm8996_rpmpds,
+	.num_pds = ARRAY_SIZE(msm8996_rpmpds),
+};
+
+static const struct of_device_id rpmpd_match_table[] = {
+	{ .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rpmpd_match_table);
+
+static int rpmpd_send_enable(struct rpmpd *pd, bool enable)
+{
+	struct rpmpd_req req = {
+		.key = KEY_ENABLE,
+		.nbytes = cpu_to_le32(sizeof(u32)),
+		.value = cpu_to_le32(enable),
+	};
+
+	return qcom_rpm_smd_write(pd->rpm, QCOM_RPM_ACTIVE_STATE, pd->res_type,
+				  pd->res_id, &req, sizeof(req));
+}
+
+static int rpmpd_send_corner(struct rpmpd *pd, int state, unsigned int corner)
+{
+	struct rpmpd_req req = {
+		.key = pd->key,
+		.nbytes = cpu_to_le32(sizeof(u32)),
+		.value = cpu_to_le32(corner),
+	};
+
+	return qcom_rpm_smd_write(pd->rpm, state, pd->res_type, pd->res_id,
+				  &req, sizeof(req));
+};
+
+static void to_active_sleep(struct rpmpd *pd, unsigned long corner,
+			    unsigned long *active, unsigned long *sleep)
+{
+	*active = corner;
+
+	if (pd->active_only)
+		*sleep = 0;
+	else
+		*sleep = *active;
+}
+
+static int rpmpd_aggregate_corner(struct rpmpd *pd)
+{
+	int ret;
+	struct rpmpd *peer = pd->peer;
+	unsigned long active_corner, sleep_corner;
+	unsigned long this_corner = 0, this_sleep_corner = 0;
+	unsigned long peer_corner = 0, peer_sleep_corner = 0;
+
+	to_active_sleep(pd, pd->corner, &this_corner, &this_sleep_corner);
+
+	if (peer && peer->enabled)
+		to_active_sleep(peer, peer->corner, &peer_corner,
+				&peer_sleep_corner);
+
+	active_corner = max(this_corner, peer_corner);
+
+	ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner);
+	if (ret)
+		return ret;
+
+	sleep_corner = max(this_sleep_corner, peer_sleep_corner);
+
+	return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner);
+}
+
+static int rpmpd_power_on(struct generic_pm_domain *domain)
+{
+	int ret;
+	struct rpmpd *pd = domain_to_rpmpd(domain);
+
+	mutex_lock(&rpmpd_lock);
+
+	ret = rpmpd_send_enable(pd, true);
+	if (ret)
+		goto out;
+
+	pd->enabled = true;
+
+	if (pd->corner)
+		ret = rpmpd_aggregate_corner(pd);
+
+out:
+	mutex_unlock(&rpmpd_lock);
+
+	return ret;
+}
+
+static int rpmpd_power_off(struct generic_pm_domain *domain)
+{
+	int ret;
+	struct rpmpd *pd = domain_to_rpmpd(domain);
+
+	mutex_lock(&rpmpd_lock);
+
+	ret = rpmpd_send_enable(pd, false);
+	if (!ret)
+		pd->enabled = false;
+
+	mutex_unlock(&rpmpd_lock);
+
+	return ret;
+}
+
+static int rpmpd_probe(struct platform_device *pdev)
+{
+	int i;
+	size_t num;
+	struct genpd_onecell_data *data;
+	struct qcom_smd_rpm *rpm;
+	struct rpmpd **rpmpds;
+	const struct rpmpd_desc *desc;
+
+	rpm = dev_get_drvdata(pdev->dev.parent);
+	if (!rpm) {
+		dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n");
+		return -ENODEV;
+	}
+
+	desc = of_device_get_match_data(&pdev->dev);
+	if (!desc)
+		return -EINVAL;
+
+	rpmpds = desc->rpmpds;
+	num = desc->num_pds;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains),
+				     GFP_KERNEL);
+	data->num_domains = num;
+
+	for (i = 0; i < num; i++) {
+		if (!rpmpds[i])
+			continue;
+
+		rpmpds[i]->rpm = rpm;
+		rpmpds[i]->pd.power_off = rpmpd_power_off;
+		rpmpds[i]->pd.power_on = rpmpd_power_on;
+		pm_genpd_init(&rpmpds[i]->pd, NULL, true);
+
+		data->domains[i] = &rpmpds[i]->pd;
+	}
+
+	return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
+}
+
+static int rpmpd_remove(struct platform_device *pdev)
+{
+	of_genpd_del_provider(pdev->dev.of_node);
+	return 0;
+}
+
+static struct platform_driver rpmpd_driver = {
+	.driver = {
+		.name = "qcom-rpmpd",
+		.of_match_table = rpmpd_match_table,
+	},
+	.probe = rpmpd_probe,
+	.remove = rpmpd_remove,
+};
+
+static int __init rpmpd_init(void)
+{
+	return platform_driver_register(&rpmpd_driver);
+}
+core_initcall(rpmpd_init);
+
+static void __exit rpmpd_exit(void)
+{
+	platform_driver_unregister(&rpmpd_driver);
+}
+module_exit(rpmpd_exit);
+
+MODULE_DESCRIPTION("Qualcomm RPM Power Domain Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qcom-rpmpd");