icnss2: Add open loop CPR support

Open loop CPR (core power reduction) is a power save feature that
configs certain voltage for WLAN power rails at runtime. Firmware
will read fuse and calculate the proper voltage and then send it to
CNSS driver. CNSS driver then configs the voltage to WLAN PDC using
TCS command through RPMh. This is to make sure the proper voltage
can be updated during runtime instead just hardcode and vote fixed
value for WLAN power rails.

Change-Id: I631712d0f569a22a7ed7b3ede892f701152ce5f8
Signed-off-by: Naman Padhiar <npadhiar@codeaurora.org>
This commit is contained in:
Naman Padhiar 2020-07-20 15:02:14 +05:30
commit c8abb1291b
4 changed files with 147 additions and 0 deletions

View file

@ -3278,6 +3278,7 @@ static int icnss_probe(struct platform_device *pdev)
icnss_pr_err("ICNSS genl init failed %d\n", ret);
icnss_runtime_pm_init(priv);
icnss_get_cpr_info(priv);
}
INIT_LIST_HEAD(&priv->icnss_tcdev_list);

View file

@ -133,6 +133,15 @@ struct icnss_vreg_info {
u32 enabled;
};
struct icnss_cpr_info {
resource_size_t tcs_cmd_base_addr;
resource_size_t tcs_cmd_data_addr;
void __iomem *tcs_cmd_base_addr_io;
void __iomem *tcs_cmd_data_addr_io;
u32 cpr_pmic_addr;
u32 voltage;
};
enum icnss_vreg_type {
ICNSS_VREG_PRIM,
};
@ -300,6 +309,7 @@ struct icnss_priv {
struct ce_irq_list ce_irq_list[ICNSS_MAX_IRQ_REGISTRATIONS];
struct list_head vreg_list;
struct list_head clk_list;
struct icnss_cpr_info cpr_info;
unsigned long device_id;
struct icnss_msi_config *msi_config;
u32 msi_base_data;
@ -412,5 +422,7 @@ int icnss_soc_wake_event_post(struct icnss_priv *priv,
u32 flags, void *data);
int icnss_get_iova(struct icnss_priv *priv, u64 *addr, u64 *size);
int icnss_get_iova_ipa(struct icnss_priv *priv, u64 *addr, u64 *size);
int icnss_get_cpr_info(struct icnss_priv *priv);
int icnss_update_cpr_info(struct icnss_priv *priv);
#endif

View file

@ -8,6 +8,7 @@
#include <linux/pinctrl/consumer.h>
#include <linux/regulator/consumer.h>
#include <soc/qcom/cmd-db.h>
#include <linux/io.h>
#include "main.h"
#include "qmi.h"
#include "debug.h"
@ -45,6 +46,13 @@ static struct icnss_clk_cfg icnss_adrestea_clk_list[] = {
#define ICNSS_THRESHOLD_LOW 3450000
#define ICNSS_THRESHOLD_GUARD 20000
#define TCS_CMD_DATA_ADDR_OFFSET 0x4
#define TCS_OFFSET 0xC8
#define TCS_CMD_OFFSET 0x10
#define MAX_TCS_NUM 8
#define MAX_TCS_CMD_NUM 5
#define BT_CXMX_VOLTAGE_MV 950
static int icnss_get_vreg_single(struct icnss_priv *priv,
struct icnss_vreg_info *vreg)
{
@ -801,3 +809,121 @@ int icnss_init_vph_monitor(struct icnss_priv *priv)
out:
return ret;
}
int icnss_get_cpr_info(struct icnss_priv *priv)
{
struct platform_device *plat_dev = priv->pdev;
struct icnss_cpr_info *cpr_info = &priv->cpr_info;
struct resource *res;
resource_size_t addr_len;
void __iomem *tcs_cmd_base_addr;
const char *cmd_db_name;
u32 cpr_pmic_addr = 0;
int ret = 0;
res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "tcs_cmd");
if (!res) {
icnss_pr_dbg("TCS CMD address is not present for CPR\n");
goto out;
}
ret = of_property_read_string(plat_dev->dev.of_node,
"qcom,cmd_db_name", &cmd_db_name);
if (ret) {
icnss_pr_dbg("CommandDB name is not present for CPR\n");
goto out;
}
cpr_pmic_addr = cmd_db_read_addr(cmd_db_name);
if (cpr_pmic_addr > 0) {
cpr_info->cpr_pmic_addr = cpr_pmic_addr;
icnss_pr_dbg("Get CPR PMIC address 0x%x from %s\n",
cpr_info->cpr_pmic_addr, cmd_db_name);
} else {
icnss_pr_err("CPR PMIC address is not available for %s\n",
cmd_db_name);
ret = -EINVAL;
goto out;
}
cpr_info->tcs_cmd_base_addr = res->start;
addr_len = resource_size(res);
icnss_pr_dbg("TCS CMD base address is %pa with length %pa\n",
&cpr_info->tcs_cmd_base_addr, &addr_len);
tcs_cmd_base_addr = devm_ioremap_resource(&plat_dev->dev, res);
if (IS_ERR(tcs_cmd_base_addr)) {
ret = PTR_ERR(tcs_cmd_base_addr);
icnss_pr_err("Failed to map TCS CMD address, err = %d\n",
ret);
goto out;
}
cpr_info->tcs_cmd_base_addr_io = tcs_cmd_base_addr;
return 0;
out:
return ret;
}
int icnss_update_cpr_info(struct icnss_priv *priv)
{
struct icnss_cpr_info *cpr_info = &priv->cpr_info;
u32 pmic_addr, voltage = 0, voltage_tmp, offset;
void __iomem *tcs_cmd_addr, *tcs_cmd_data_addr;
int i, j;
if (cpr_info->tcs_cmd_base_addr == 0) {
icnss_pr_dbg("CPR is not enabled\n");
return 0;
}
if (cpr_info->voltage == 0 || cpr_info->cpr_pmic_addr == 0) {
icnss_pr_err("Voltage %dmV or PMIC address 0x%x is not valid\n",
cpr_info->voltage, cpr_info->cpr_pmic_addr);
return -EINVAL;
}
if (cpr_info->tcs_cmd_data_addr_io)
goto update_cpr;
for (i = 0; i < MAX_TCS_NUM; i++) {
for (j = 0; j < MAX_TCS_CMD_NUM; j++) {
offset = i * TCS_OFFSET + j * TCS_CMD_OFFSET;
tcs_cmd_addr = cpr_info->tcs_cmd_base_addr_io + offset;
pmic_addr = readl_relaxed(tcs_cmd_addr);
if (pmic_addr == cpr_info->cpr_pmic_addr) {
tcs_cmd_data_addr = tcs_cmd_addr +
TCS_CMD_DATA_ADDR_OFFSET;
voltage_tmp = readl_relaxed(tcs_cmd_data_addr);
icnss_pr_dbg("Got voltage %dmV from i: %d, j: %d\n",
voltage_tmp, i, j);
if (voltage_tmp > voltage) {
voltage = voltage_tmp;
cpr_info->tcs_cmd_data_addr =
cpr_info->tcs_cmd_base_addr +
offset +
TCS_CMD_DATA_ADDR_OFFSET;
cpr_info->tcs_cmd_data_addr_io =
tcs_cmd_data_addr;
}
}
}
}
if (!cpr_info->tcs_cmd_data_addr_io) {
icnss_pr_err("Failed to find proper TCS CMD data address\n");
return -EINVAL;
}
update_cpr:
cpr_info->voltage = cpr_info->voltage > BT_CXMX_VOLTAGE_MV ?
cpr_info->voltage : BT_CXMX_VOLTAGE_MV;
icnss_pr_dbg("Update TCS CMD data address %pa with voltage %dmV\n",
&cpr_info->tcs_cmd_data_addr, cpr_info->voltage);
writel_relaxed(cpr_info->voltage, cpr_info->tcs_cmd_data_addr_io);
return 0;
}

View file

@ -676,6 +676,14 @@ int wlfw_cap_send_sync_msg(struct icnss_priv *priv)
resp->fw_version_info.fw_build_timestamp,
WLFW_MAX_TIMESTAMP_LEN + 1);
}
if (resp->voltage_mv_valid) {
priv->cpr_info.voltage = resp->voltage_mv;
icnss_pr_dbg("Voltage for CPR: %dmV\n",
priv->cpr_info.voltage);
icnss_update_cpr_info(priv);
}
if (resp->fw_build_id_valid)
strlcpy(priv->fw_build_id, resp->fw_build_id,
QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1);