From bbe3a66c3f5a65fb3d702351bac2a6033944d389 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:04 +0530 Subject: soc: qcom: rpmpd: Add a Power domain driver to model corners The Power domains 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 power domain data needed on msm8996 here. This driver can easily be extended by adding data for other qualcomm SoCs as well. Reviewed-by: Ulf Hansson Acked-by: Rob Herring Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmpd.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 drivers/soc/qcom/rpmpd.c (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index fcbf8a2e4080..df5cd9fa0d5e 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -98,6 +98,15 @@ config QCOM_RPMH of hardware components aggregate requests for these resources and help apply the aggregated state on the resource. +config QCOM_RPMPD + bool "Qualcomm RPM Power domain driver" + depends on MFD_QCOM_RPM && QCOM_SMD_RPM + help + QCOM RPM Power domain driver to support power-domains 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 || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f25b54cd6cf8..f1b25fdcf2ad 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.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..7715ba7c9157 --- /dev/null +++ b/drivers/soc/qcom/rpmpd.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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_CORNER_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_CORNER_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 int 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 Power domains */ +DEFINE_RPMPD_CORNER_SMPA(msm8996, vddcx, vddcx_ao, 1); +DEFINE_RPMPD_CORNER_SMPA(msm8996, vddmx, vddmx_ao, 2); +DEFINE_RPMPD_CORNER_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[] = { + [MSM8996_VDDCX] = &msm8996_vddcx, + [MSM8996_VDDCX_AO] = &msm8996_vddcx_ao, + [MSM8996_VDDCX_VFC] = &msm8996_vddcx_vfc, + [MSM8996_VDDMX] = &msm8996_vddmx, + [MSM8996_VDDMX_AO] = &msm8996_vddmx_ao, + [MSM8996_VDDSSCX] = &msm8996_vddsscx, + [MSM8996_VDDSSCX_VFC] = &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 }, + { } +}; + +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 int corner, + unsigned int *active, unsigned int *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 int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_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]) { + dev_warn(&pdev->dev, "rpmpds[] with empty entry at index=%d\n", + 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 struct platform_driver rpmpd_driver = { + .driver = { + .name = "qcom-rpmpd", + .of_match_table = rpmpd_match_table, + .suppress_bind_attrs = true, + }, + .probe = rpmpd_probe, +}; + +static int __init rpmpd_init(void) +{ + return platform_driver_register(&rpmpd_driver); +} +core_initcall(rpmpd_init); -- cgit From 075d3db8d10d82bb86777df3f81e1b83caeb630a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:05 +0530 Subject: soc: qcom: rpmpd: Add support for get/set performance state Add support for the .set_performace_state() and .opp_to_performance_state() callbacks in the rpmpd driver. Reviewed-by: Ulf Hansson Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmpd.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 7715ba7c9157..35a711017d6f 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,8 @@ #define KEY_ENABLE 0x6e657773 /* swen */ #define KEY_FLOOR_CORNER 0x636676 /* vfc */ +#define MAX_RPMPD_STATE 6 + #define DEFINE_RPMPD_CORNER_SMPA(_platform, _name, _active, r_id) \ static struct rpmpd _platform##_##_active; \ static struct rpmpd _platform##_##_name = { \ @@ -218,6 +221,36 @@ static int rpmpd_power_off(struct generic_pm_domain *domain) return ret; } +static int rpmpd_set_performance(struct generic_pm_domain *domain, + unsigned int state) +{ + int ret = 0; + struct rpmpd *pd = domain_to_rpmpd(domain); + + if (state > MAX_RPMPD_STATE) + goto out; + + mutex_lock(&rpmpd_lock); + + pd->corner = state; + + if (!pd->enabled && pd->key != KEY_FLOOR_CORNER) + goto out; + + ret = rpmpd_aggregate_corner(pd); + +out: + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static unsigned int rpmpd_get_performance(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + static int rpmpd_probe(struct platform_device *pdev) { int i; @@ -258,6 +291,8 @@ static int rpmpd_probe(struct platform_device *pdev) rpmpds[i]->rpm = rpm; rpmpds[i]->pd.power_off = rpmpd_power_off; rpmpds[i]->pd.power_on = rpmpd_power_on; + rpmpds[i]->pd.set_performance_state = rpmpd_set_performance; + rpmpds[i]->pd.opp_to_performance_state = rpmpd_get_performance; pm_genpd_init(&rpmpds[i]->pd, NULL, true); data->domains[i] = &rpmpds[i]->pd; -- cgit From 279b7e8a62cc4f524dac49ac3ab5bc401a965422 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:07 +0530 Subject: soc: qcom: rpmhpd: Add RPMh power domain driver The RPMh power domain driver aggregates the corner votes from various consumers for the ARC resources and communicates it to RPMh. With RPMh we use 2 different numbering space for corners, one used by the clients to express their performance needs, and another used to communicate to RPMh hardware. The clients express their performance requirements using a sparse numbering space which are mapped to meaningful levels like RET, SVS, NOMINAL, TURBO etc which then get mapped to another number space between 0 and 15 which is communicated to RPMh. The sparse number space, also referred to as vlvl is mapped to the continuous number space of 0 to 15, also referred to as hlvl, using command DB. Some power domain clients could request a performance state only while the CPU is active, while some others could request for a certain performance state all the time regardless of the state of the CPU. We handle this by internally aggregating the votes from both type of clients and then send the aggregated votes to RPMh. There are also 3 different types of votes that are comunicated to RPMh for every resource. 1. ACTIVE_ONLY: This specifies the requirement for the resource when the CPU is active 2. SLEEP: This specifies the requirement for the resource when the CPU is going to sleep 3. WAKE_ONLY: This specifies the requirement for the resource when the CPU is coming out of sleep to active state We add data for all power domains on sdm845 SoC as part of the patch. The driver can be extended to support other SoCs which support RPMh Reviewed-by: Ulf Hansson Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmhpd.c | 395 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 drivers/soc/qcom/rpmhpd.c (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index df5cd9fa0d5e..6241d3e3b115 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -98,6 +98,15 @@ config QCOM_RPMH of hardware components aggregate requests for these resources and help apply the aggregated state on the resource. +config QCOM_RPMHPD + bool "Qualcomm RPMh Power domain driver" + depends on QCOM_RPMH && QCOM_COMMAND_DB + help + QCOM RPMh Power domain driver to support power-domains with + performance states. The driver communicates a performance state + value to RPMh which then translates it into corresponding voltage + for the voltage rail. + config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" depends on MFD_QCOM_RPM && QCOM_SMD_RPM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f1b25fdcf2ad..ffe519b0cb66 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o +obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c new file mode 100644 index 000000000000..d3f819c1a90c --- /dev/null +++ b/drivers/soc/qcom/rpmhpd.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) + +#define RPMH_ARC_MAX_LEVELS 16 + +/** + * struct rpmhpd - top level RPMh power domain resource data structure + * @dev: rpmh power domain controller device + * @pd: generic_pm_domain corrresponding to the power domain + * @peer: A peer power domain in case Active only Voting is + * supported + * @active_only: True if it represents an Active only peer + * @level: An array of level (vlvl) to corner (hlvl) mappings + * derived from cmd-db + * @level_count: Number of levels supported by the power domain. max + * being 16 (0 - 15) + * @enabled: true if the power domain is enabled + * @res_name: Resource name used for cmd-db lookup + * @addr: Resource address as looped up using resource name from + * cmd-db + */ +struct rpmhpd { + struct device *dev; + struct generic_pm_domain pd; + struct generic_pm_domain *parent; + struct rpmhpd *peer; + const bool active_only; + unsigned int corner; + unsigned int active_corner; + u32 level[RPMH_ARC_MAX_LEVELS]; + size_t level_count; + bool enabled; + const char *res_name; + u32 addr; +}; + +struct rpmhpd_desc { + struct rpmhpd **rpmhpds; + size_t num_pds; +}; + +static DEFINE_MUTEX(rpmhpd_lock); + +/* SDM845 RPMH powerdomains */ + +static struct rpmhpd sdm845_ebi = { + .pd = { .name = "ebi", }, + .res_name = "ebi.lvl", +}; + +static struct rpmhpd sdm845_lmx = { + .pd = { .name = "lmx", }, + .res_name = "lmx.lvl", +}; + +static struct rpmhpd sdm845_lcx = { + .pd = { .name = "lcx", }, + .res_name = "lcx.lvl", +}; + +static struct rpmhpd sdm845_gfx = { + .pd = { .name = "gfx", }, + .res_name = "gfx.lvl", +}; + +static struct rpmhpd sdm845_mss = { + .pd = { .name = "mss", }, + .res_name = "mss.lvl", +}; + +static struct rpmhpd sdm845_mx_ao; +static struct rpmhpd sdm845_mx = { + .pd = { .name = "mx", }, + .peer = &sdm845_mx_ao, + .res_name = "mx.lvl", +}; + +static struct rpmhpd sdm845_mx_ao = { + .pd = { .name = "mx_ao", }, + .peer = &sdm845_mx, + .res_name = "mx.lvl", +}; + +static struct rpmhpd sdm845_cx_ao; +static struct rpmhpd sdm845_cx = { + .pd = { .name = "cx", }, + .peer = &sdm845_cx_ao, + .res_name = "cx.lvl", +}; + +static struct rpmhpd sdm845_cx_ao = { + .pd = { .name = "cx_ao", }, + .peer = &sdm845_cx, + .res_name = "cx.lvl", +}; + +static struct rpmhpd *sdm845_rpmhpds[] = { + [SDM845_EBI] = &sdm845_ebi, + [SDM845_MX] = &sdm845_mx, + [SDM845_MX_AO] = &sdm845_mx_ao, + [SDM845_CX] = &sdm845_cx, + [SDM845_CX_AO] = &sdm845_cx_ao, + [SDM845_LMX] = &sdm845_lmx, + [SDM845_LCX] = &sdm845_lcx, + [SDM845_GFX] = &sdm845_gfx, + [SDM845_MSS] = &sdm845_mss, +}; + +static const struct rpmhpd_desc sdm845_desc = { + .rpmhpds = sdm845_rpmhpds, + .num_pds = ARRAY_SIZE(sdm845_rpmhpds), +}; + +static const struct of_device_id rpmhpd_match_table[] = { + { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, + { } +}; + +static int rpmhpd_send_corner(struct rpmhpd *pd, int state, + unsigned int corner, bool sync) +{ + struct tcs_cmd cmd = { + .addr = pd->addr, + .data = corner, + }; + + /* + * Wait for an ack only when we are increasing the + * perf state of the power domain + */ + if (sync) + return rpmh_write(pd->dev, state, &cmd, 1); + else + return rpmh_write_async(pd->dev, state, &cmd, 1); +} + +static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, + unsigned int *active, unsigned int *sleep) +{ + *active = corner; + + if (pd->active_only) + *sleep = 0; + else + *sleep = *active; +} + +/* + * This function is used to aggregate the votes across the active only + * resources and its peers. The aggregated votes are sent to RPMh as + * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes + * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh + * on system sleep). + * We send ACTIVE_ONLY votes for resources without any peers. For others, + * which have an active only peer, all 3 votes are sent. + */ +static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) +{ + int ret; + struct rpmhpd *peer = pd->peer; + unsigned int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_corner); + + ret = rpmhpd_send_corner(pd, RPMH_ACTIVE_ONLY_STATE, active_corner, + active_corner > pd->active_corner); + if (ret) + return ret; + + pd->active_corner = active_corner; + + if (peer) { + peer->active_corner = active_corner; + + ret = rpmhpd_send_corner(pd, RPMH_WAKE_ONLY_STATE, + active_corner, false); + if (ret) + return ret; + + sleep_corner = max(this_sleep_corner, peer_sleep_corner); + + return rpmhpd_send_corner(pd, RPMH_SLEEP_STATE, sleep_corner, + false); + } + + return ret; +} + +static int rpmhpd_power_on(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + if (pd->corner) + ret = rpmhpd_aggregate_corner(pd, pd->corner); + + if (!ret) + pd->enabled = true; + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_power_off(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + ret = rpmhpd_aggregate_corner(pd, pd->level[0]); + + if (!ret) + pd->enabled = false; + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, + unsigned int level) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0, i; + + mutex_lock(&rpmhpd_lock); + + for (i = 0; i < pd->level_count; i++) + if (level <= pd->level[i]) + break; + + /* + * If the level requested is more than that supported by the + * max corner, just set it to max anyway. + */ + if (i == pd->level_count) + i--; + + if (pd->enabled) { + ret = rpmhpd_aggregate_corner(pd, i); + if (ret) + goto out; + } + + pd->corner = i; +out: + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static unsigned int rpmhpd_get_performance_state(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + +static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) +{ + int i; + const u16 *buf; + + buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* 2 bytes used for each command DB aux data entry */ + rpmhpd->level_count >>= 1; + + if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS) + return -EINVAL; + + for (i = 0; i < rpmhpd->level_count; i++) { + rpmhpd->level[i] = buf[i]; + + /* + * The AUX data may be zero padded. These 0 valued entries at + * the end of the map must be ignored. + */ + if (i > 0 && rpmhpd->level[i] == 0) { + rpmhpd->level_count = i; + break; + } + pr_debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, + rpmhpd->level[i]); + } + + return 0; +} + +static int rpmhpd_probe(struct platform_device *pdev) +{ + int i, ret; + size_t num_pds; + struct device *dev = &pdev->dev; + struct genpd_onecell_data *data; + struct rpmhpd **rpmhpds; + const struct rpmhpd_desc *desc; + + desc = of_device_get_match_data(dev); + if (!desc) + return -EINVAL; + + rpmhpds = desc->rpmhpds; + num_pds = desc->num_pds; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(dev, num_pds, sizeof(*data->domains), + GFP_KERNEL); + if (!data->domains) + return -ENOMEM; + + data->num_domains = num_pds; + + for (i = 0; i < num_pds; i++) { + if (!rpmhpds[i]) { + dev_warn(dev, "rpmhpds[%d] is empty\n", i); + continue; + } + + rpmhpds[i]->dev = dev; + rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); + if (!rpmhpds[i]->addr) { + dev_err(dev, "Could not find RPMh address for resource %s\n", + rpmhpds[i]->res_name); + return -ENODEV; + } + + ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); + if (ret != CMD_DB_HW_ARC) { + dev_err(dev, "RPMh slave ID mismatch\n"); + return -EINVAL; + } + + ret = rpmhpd_update_level_mapping(rpmhpds[i]); + if (ret) + return ret; + + rpmhpds[i]->pd.power_off = rpmhpd_power_off; + rpmhpds[i]->pd.power_on = rpmhpd_power_on; + rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state; + rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance_state; + pm_genpd_init(&rpmhpds[i]->pd, NULL, true); + + data->domains[i] = &rpmhpds[i]->pd; + } + + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); +} + +static struct platform_driver rpmhpd_driver = { + .driver = { + .name = "qcom-rpmhpd", + .of_match_table = rpmhpd_match_table, + .suppress_bind_attrs = true, + }, + .probe = rpmhpd_probe, +}; + +static int __init rpmhpd_init(void) +{ + return platform_driver_register(&rpmhpd_driver); +} +core_initcall(rpmhpd_init); -- cgit From 0503aec22c1483d02b2d8bbd8594c5b9713294b8 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:09 +0530 Subject: soc: qcom: rpmhpd: Mark mx as a parent for cx Specify the active + sleep and active-only MX power domains as the parents of the corresponding CX power domains. This will ensure that performance state requests on CX automatically generate equivalent requests on MX power domains. This is used to enforce a requirement that exists for various hardware blocks on SDM845 that MX performance state >= CX performance state for a given operating frequency. Acked-by: Viresh Kumar Reviewed-by: Stephen Boyd Reviewed-by: Ulf Hansson Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmhpd.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c index d3f819c1a90c..5741ec3fa814 100644 --- a/drivers/soc/qcom/rpmhpd.c +++ b/drivers/soc/qcom/rpmhpd.c @@ -101,12 +101,14 @@ static struct rpmhpd sdm845_cx_ao; static struct rpmhpd sdm845_cx = { .pd = { .name = "cx", }, .peer = &sdm845_cx_ao, + .parent = &sdm845_mx.pd, .res_name = "cx.lvl", }; static struct rpmhpd sdm845_cx_ao = { .pd = { .name = "cx_ao", }, .peer = &sdm845_cx, + .parent = &sdm845_mx_ao.pd, .res_name = "cx.lvl", }; @@ -376,6 +378,15 @@ static int rpmhpd_probe(struct platform_device *pdev) data->domains[i] = &rpmhpds[i]->pd; } + /* Add subdomains */ + for (i = 0; i < num_pds; i++) { + if (!rpmhpds[i]) + continue; + if (rpmhpds[i]->parent) + pm_genpd_add_subdomain(rpmhpds[i]->parent, + &rpmhpds[i]->pd); + } + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); } -- cgit From 41c5bb767ecbc986c93df312769f3f49189f8ee0 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 16 Jan 2019 21:11:20 -0800 Subject: soc: qcom: rpmpd: Drop family A RPM dependency The MFD_QCOM_RPM is the RPM in family A, but the rpmpd driver only implements support for SMD based devices. Drop the dependency and remove includes of the family A headers. No functional change. Signed-off-by: Bjorn Andersson Reviewed-by: Rajendra Nayak Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 2 +- drivers/soc/qcom/rpmpd.c | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 6241d3e3b115..a5d5167c3f16 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -109,7 +109,7 @@ config QCOM_RPMHPD config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" - depends on MFD_QCOM_RPM && QCOM_SMD_RPM + depends on QCOM_SMD_RPM help QCOM RPM Power domain driver to support power-domains with performance states. The driver communicates a performance state diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 35a711017d6f..005326050c23 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -6,14 +6,12 @@ #include #include #include -#include #include #include #include #include #include -#include #include #define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd) @@ -131,8 +129,8 @@ static int rpmpd_send_enable(struct rpmpd *pd, bool enable) .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)); + return qcom_rpm_smd_write(pd->rpm, QCOM_SMD_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) @@ -174,13 +172,13 @@ static int rpmpd_aggregate_corner(struct rpmpd *pd) active_corner = max(this_active_corner, peer_active_corner); - ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner); + ret = rpmpd_send_corner(pd, QCOM_SMD_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); + return rpmpd_send_corner(pd, QCOM_SMD_RPM_SLEEP_STATE, sleep_corner); } static int rpmpd_power_on(struct generic_pm_domain *domain) -- cgit From 4eccc154b3866e59358f1102045d23a301dffe0a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 18 Jan 2019 10:18:01 +0530 Subject: soc: qcom: update config dependencies for QCOM_RPMPD Since QCOM_RPMPD is bool and it depends on QCOM_SMD_RPM which is tristate, configurations such as arm64:allmodconfig result in CONFIG_QCOM_RPMPD=y CONFIG_QCOM_SMD_RPM=m This in turn results in drivers/soc/qcom/rpmpd.o: In function `rpmpd_send_corner': rpmpd.c:(.text+0x10c): undefined reference to `qcom_rpm_smd_write' drivers/soc/qcom/rpmpd.o: In function `rpmpd_power_on': rpmpd.c:(.text+0x3b4): undefined reference to `qcom_rpm_smd_write' drivers/soc/qcom/rpmpd.o: In function `rpmpd_power_off': rpmpd.c:(.text+0x520): undefined reference to `qcom_rpm_smd_write' make: *** [vmlinux] Error 1 Fix it by making QCOM_RPMPD depend on QCOM_SMD_RPM=y Reported-by: Guenter Roeck Signed-off-by: Rajendra Nayak Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index a5d5167c3f16..1ee298f6bf17 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -109,7 +109,7 @@ config QCOM_RPMHPD config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" - depends on QCOM_SMD_RPM + depends on QCOM_SMD_RPM=y help QCOM RPM Power domain driver to support power-domains with performance states. The driver communicates a performance state -- cgit From b4aa93bca91318a8cf832b9fd34a830e44660d50 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:12 -0800 Subject: soc: qcom: rmtfs-mem: Add class to enable uevents Currently the qcom_rmtfs_memN devices are entirely invisible to the udev world. Add a class to the rmtfs device so that uevents fire when the device is added. Reviewed-by: Brian Norris Reviewed-by: Bjorn Andersson Signed-off-by: Evan Green Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rmtfs_mem.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index 97bb5989aa21..b2fa8fc8016c 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -132,6 +132,11 @@ static int qcom_rmtfs_mem_release(struct inode *inode, struct file *filp) return 0; } +static struct class rmtfs_class = { + .owner = THIS_MODULE, + .name = "rmtfs", +}; + static const struct file_operations qcom_rmtfs_mem_fops = { .owner = THIS_MODULE, .open = qcom_rmtfs_mem_open, @@ -199,6 +204,7 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev) dev_set_name(&rmtfs_mem->dev, "qcom_rmtfs_mem%d", client_id); rmtfs_mem->dev.id = client_id; + rmtfs_mem->dev.class = &rmtfs_class; rmtfs_mem->dev.devt = MKDEV(MAJOR(qcom_rmtfs_mem_major), client_id); ret = cdev_device_add(&rmtfs_mem->cdev, &rmtfs_mem->dev); @@ -277,32 +283,42 @@ static struct platform_driver qcom_rmtfs_mem_driver = { }, }; -static int qcom_rmtfs_mem_init(void) +static int __init qcom_rmtfs_mem_init(void) { int ret; + ret = class_register(&rmtfs_class); + if (ret) + return ret; + ret = alloc_chrdev_region(&qcom_rmtfs_mem_major, 0, QCOM_RMTFS_MEM_DEV_MAX, "qcom_rmtfs_mem"); if (ret < 0) { pr_err("qcom_rmtfs_mem: failed to allocate char dev region\n"); - return ret; + goto unregister_class; } ret = platform_driver_register(&qcom_rmtfs_mem_driver); if (ret < 0) { pr_err("qcom_rmtfs_mem: failed to register rmtfs_mem driver\n"); - unregister_chrdev_region(qcom_rmtfs_mem_major, - QCOM_RMTFS_MEM_DEV_MAX); + goto unregister_chrdev; } + return 0; + +unregister_chrdev: + unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); +unregister_class: + class_unregister(&rmtfs_class); return ret; } module_init(qcom_rmtfs_mem_init); -static void qcom_rmtfs_mem_exit(void) +static void __exit qcom_rmtfs_mem_exit(void) { platform_driver_unregister(&qcom_rmtfs_mem_driver); unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); + class_unregister(&rmtfs_class); } module_exit(qcom_rmtfs_mem_exit); -- cgit From f58b0f9d9dd6d033396561517bb191d904df9e54 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:13 -0800 Subject: soc: qcom: rmtfs-mem: Make sysfs attributes world-readable In order to run an rmtfs daemon as an unprivileged user, that user would need access to the phys_addr and size sysfs attributes. Sharing these attributes with unprivileged users doesn't really leak anything sensitive, since if you have access to physical memory, the jig is up anyway. Make those attributes readable by all. Reviewed-by: Brian Norris Reviewed-by: Bjorn Andersson Signed-off-by: Evan Green Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rmtfs_mem.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index b2fa8fc8016c..7200d762a951 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -45,9 +45,9 @@ static ssize_t qcom_rmtfs_mem_show(struct device *dev, struct device_attribute *attr, char *buf); -static DEVICE_ATTR(phys_addr, 0400, qcom_rmtfs_mem_show, NULL); -static DEVICE_ATTR(size, 0400, qcom_rmtfs_mem_show, NULL); -static DEVICE_ATTR(client_id, 0400, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(phys_addr, 0444, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(size, 0444, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(client_id, 0444, qcom_rmtfs_mem_show, NULL); static ssize_t qcom_rmtfs_mem_show(struct device *dev, struct device_attribute *attr, -- cgit From 7300241926e8cf5b5f01059b0c07896fb1c74d9e Mon Sep 17 00:00:00 2001 From: "Raju P.L.S.S.S.N" Date: Tue, 23 Oct 2018 22:59:08 +0530 Subject: drivers: qcom: rpmh: avoid sending sleep/wake sets immediately Fix the redundant call being made to send the sleep and wake requests immediately to the controller. As per the patch [1], the sleep and wake request votes are cached in rpmh controller and sent during rpmh_flush(). These requests needs to be sent only during entry of deeper system low power modes or suspend. [1] https://patchwork.kernel.org/patch/10477533/ Reviewed-by: Lina Iyer Reviewed-by: Evan Green Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index c7beb6841289..9fb25e627698 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -192,9 +192,8 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, WARN_ON(irqs_disabled()); ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); } else { - ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), - &rpm_msg->msg); /* Clean up our call by spoofing tx_done */ + ret = 0; rpmh_tx_done(&rpm_msg->msg, ret); } -- cgit From baef1c90aac7e5bf13f0360a3b334825a23d31a1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 15 Jan 2019 14:54:47 -0800 Subject: soc: qcom: rpmh: Avoid accessing freed memory from batch API Using the batch API from the interconnect driver sometimes leads to a KASAN error due to an access to freed memory. This is easier to trigger with threadirqs on the kernel commandline. BUG: KASAN: use-after-free in rpmh_tx_done+0x114/0x12c Read of size 1 at addr fffffff51414ad84 by task irq/110-apps_rs/57 CPU: 0 PID: 57 Comm: irq/110-apps_rs Tainted: G W 4.19.10 #72 Call trace: dump_backtrace+0x0/0x2f8 show_stack+0x20/0x2c __dump_stack+0x20/0x28 dump_stack+0xcc/0x10c print_address_description+0x74/0x240 kasan_report+0x250/0x26c __asan_report_load1_noabort+0x20/0x2c rpmh_tx_done+0x114/0x12c tcs_tx_done+0x450/0x768 irq_forced_thread_fn+0x58/0x9c irq_thread+0x120/0x1dc kthread+0x248/0x260 ret_from_fork+0x10/0x18 Allocated by task 385: kasan_kmalloc+0xac/0x148 __kmalloc+0x170/0x1e4 rpmh_write_batch+0x174/0x540 qcom_icc_set+0x8dc/0x9ac icc_set+0x288/0x2e8 a6xx_gmu_stop+0x320/0x3c0 a6xx_pm_suspend+0x108/0x124 adreno_suspend+0x50/0x60 pm_generic_runtime_suspend+0x60/0x78 __rpm_callback+0x214/0x32c rpm_callback+0x54/0x184 rpm_suspend+0x3f8/0xa90 pm_runtime_work+0xb4/0x178 process_one_work+0x544/0xbc0 worker_thread+0x514/0x7d0 kthread+0x248/0x260 ret_from_fork+0x10/0x18 Freed by task 385: __kasan_slab_free+0x12c/0x1e0 kasan_slab_free+0x10/0x1c kfree+0x134/0x588 rpmh_write_batch+0x49c/0x540 qcom_icc_set+0x8dc/0x9ac icc_set+0x288/0x2e8 a6xx_gmu_stop+0x320/0x3c0 a6xx_pm_suspend+0x108/0x124 adreno_suspend+0x50/0x60 cr50_spi spi5.0: SPI transfer timed out pm_generic_runtime_suspend+0x60/0x78 __rpm_callback+0x214/0x32c rpm_callback+0x54/0x184 rpm_suspend+0x3f8/0xa90 pm_runtime_work+0xb4/0x178 process_one_work+0x544/0xbc0 worker_thread+0x514/0x7d0 kthread+0x248/0x260 ret_from_fork+0x10/0x18 The buggy address belongs to the object at fffffff51414ac80 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 260 bytes inside of 512-byte region [fffffff51414ac80, fffffff51414ae80) The buggy address belongs to the page: page:ffffffbfd4505200 count:1 mapcount:0 mapping:fffffff51e00c680 index:0x0 compound_mapcount: 0 flags: 0x4000000000008100(slab|head) raw: 4000000000008100 ffffffbfd4529008 ffffffbfd44f9208 fffffff51e00c680 raw: 0000000000000000 0000000000200020 00000001ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: fffffff51414ac80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fffffff51414ad00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >fffffff51414ad80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ fffffff51414ae00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fffffff51414ae80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc The batch API sets the same completion for each rpmh message that's sent and then loops through all the messages and waits for that single completion declared on the stack to be completed before returning from the function and freeing the message structures. Unfortunately, some messages may still be in process and 'stuck' in the TCS. At some later point, the tcs_tx_done() interrupt will run and try to process messages that have already been freed at the end of rpmh_write_batch(). This will in turn access the 'needs_free' member of the rpmh_request structure and cause KASAN to complain. Furthermore, if there's a message that's completed in rpmh_tx_done() and freed immediately after the complete() call is made we'll be racing with potentially freed memory when accessing the 'needs_free' member: CPU0 CPU1 ---- ---- rpmh_tx_done() complete(&compl) wait_for_completion(&compl) kfree(rpm_msg) if (rpm_msg->needs_free) Let's fix this by allocating a chunk of completions for each message and waiting for all of them to be completed before returning from the batch API. Alternatively, we could wait for the last message in the batch, but that may be a more complicated change because it looks like tcs_tx_done() just iterates through the indices of the queue and completes each message instead of tracking the last inserted message and completing that first. Fixes: c8790cb6da58 ("drivers: qcom: rpmh: add support for batch RPMH request") Cc: Lina Iyer Cc: "Raju P.L.S.S.S.N" Cc: Matthias Kaehlcke Cc: Evan Green Cc: stable@vger.kernel.org Reviewed-by: Lina Iyer Reviewed-by: Evan Green Signed-off-by: Stephen Boyd Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 9fb25e627698..035091fd44b8 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -80,6 +80,7 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request, msg); struct completion *compl = rpm_msg->completion; + bool free = rpm_msg->needs_free; rpm_msg->err = r; @@ -94,7 +95,7 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) complete(compl); exit: - if (rpm_msg->needs_free) + if (free) kfree(rpm_msg); } @@ -347,11 +348,12 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, { struct batch_cache_req *req; struct rpmh_request *rpm_msgs; - DECLARE_COMPLETION_ONSTACK(compl); + struct completion *compls; struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); unsigned long time_left; int count = 0; - int ret, i, j; + int ret, i; + void *ptr; if (!cmd || !n) return -EINVAL; @@ -361,10 +363,15 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, if (!count) return -EINVAL; - req = kzalloc(sizeof(*req) + count * sizeof(req->rpm_msgs[0]), + ptr = kzalloc(sizeof(*req) + + count * (sizeof(req->rpm_msgs[0]) + sizeof(*compls)), GFP_ATOMIC); - if (!req) + if (!ptr) return -ENOMEM; + + req = ptr; + compls = ptr + sizeof(*req) + count * sizeof(*rpm_msgs); + req->count = count; rpm_msgs = req->rpm_msgs; @@ -379,25 +386,26 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, } for (i = 0; i < count; i++) { - rpm_msgs[i].completion = &compl; + struct completion *compl = &compls[i]; + + init_completion(compl); + rpm_msgs[i].completion = compl; ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msgs[i].msg); if (ret) { pr_err("Error(%d) sending RPMH message addr=%#x\n", ret, rpm_msgs[i].msg.cmds[0].addr); - for (j = i; j < count; j++) - rpmh_tx_done(&rpm_msgs[j].msg, ret); break; } } time_left = RPMH_TIMEOUT_MS; - for (i = 0; i < count; i++) { - time_left = wait_for_completion_timeout(&compl, time_left); + while (i--) { + time_left = wait_for_completion_timeout(&compls[i], time_left); if (!time_left) { /* * Better hope they never finish because they'll signal - * the completion on our stack and that's bad once - * we've returned from the function. + * the completion that we're going to free once + * we've returned from this function. */ WARN_ON(1); ret = -ETIMEDOUT; @@ -406,7 +414,7 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, } exit: - kfree(req); + kfree(ptr); return ret; } -- cgit From 8cd09a3dd3e176c62da67efcd477a44a8d87185e Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Sat, 8 Dec 2018 01:57:04 +0300 Subject: soc: qcom: gsbi: Fix error handling in gsbi_probe() If of_platform_populate() fails in gsbi_probe(), gsbi->hclk is left undisabled. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Alexey Khoroshilov Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/qcom_gsbi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/qcom_gsbi.c b/drivers/soc/qcom/qcom_gsbi.c index 09c669e70d63..038abc377fdb 100644 --- a/drivers/soc/qcom/qcom_gsbi.c +++ b/drivers/soc/qcom/qcom_gsbi.c @@ -138,7 +138,7 @@ static int gsbi_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; struct gsbi_info *gsbi; - int i; + int i, ret; u32 mask, gsbi_num; const struct crci_config *config = NULL; @@ -221,7 +221,10 @@ static int gsbi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gsbi); - return of_platform_populate(node, NULL, NULL, &pdev->dev); + ret = of_platform_populate(node, NULL, NULL, &pdev->dev); + if (ret) + clk_disable_unprepare(gsbi->hclk); + return ret; } static int gsbi_remove(struct platform_device *pdev) -- cgit From 4e2256d31f0f24107c36dae35a1d84dff0fced30 Mon Sep 17 00:00:00 2001 From: Craig Tatlor Date: Sat, 11 Aug 2018 17:24:17 +0100 Subject: soc: qcom: smd-rpm: Add sdm660 compatible Add the compatible for the RPM in SDM660, so that rpm resources can be made available. Signed-off-by: Craig Tatlor Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index b8e63724a49d..9956bb2c63f2 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -227,6 +227,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-msm8974" }, { .compatible = "qcom,rpm-msm8996" }, { .compatible = "qcom,rpm-msm8998" }, + { .compatible = "qcom,rpm-sdm660" }, { .compatible = "qcom,rpm-qcs404" }, {} }; -- cgit