diff --git a/drivers/soc/qcom/icnss2/main.c b/drivers/soc/qcom/icnss2/main.c index d46cb1b46c3a..a58e2cfc532d 100644 --- a/drivers/soc/qcom/icnss2/main.c +++ b/drivers/soc/qcom/icnss2/main.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -632,6 +634,9 @@ static int icnss_driver_event_server_arrive(struct icnss_priv *priv, if (!priv->fw_early_crash_irq) register_early_crash_notifications(&priv->pdev->dev); + if (priv->vbatt_supported) + icnss_init_vph_monitor(priv); + return ret; err_power_on: @@ -653,6 +658,10 @@ static int icnss_driver_event_server_exit(struct icnss_priv *priv) icnss_clear_server(priv); + if (priv->adc_tm_dev && priv->vbatt_supported) + adc_tm_disable_chan_meas(priv->adc_tm_dev, + &priv->vph_monitor_params); + return 0; } @@ -2771,6 +2780,44 @@ static void icnss_sysfs_destroy(struct icnss_priv *priv) kobject_put(icnss_kobject); } +static int icnss_get_vbatt_info(struct icnss_priv *priv) +{ + struct adc_tm_chip *adc_tm_dev = NULL; + struct iio_channel *channel = NULL; + int ret = 0; + + adc_tm_dev = get_adc_tm(&priv->pdev->dev, "icnss"); + if (PTR_ERR(adc_tm_dev) == -EPROBE_DEFER) { + icnss_pr_err("adc_tm_dev probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(adc_tm_dev)) { + ret = PTR_ERR(adc_tm_dev); + icnss_pr_err("Not able to get ADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + channel = iio_channel_get(&priv->pdev->dev, "icnss"); + if (PTR_ERR(channel) == -EPROBE_DEFER) { + icnss_pr_err("channel probe defer\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(channel)) { + ret = PTR_ERR(channel); + icnss_pr_err("Not able to get VADC dev, VBATT monitoring is disabled: %d\n", + ret); + return ret; + } + + priv->adc_tm_dev = adc_tm_dev; + priv->channel = channel; + + return 0; +} + static int icnss_resource_parse(struct icnss_priv *priv) { int ret = 0, i = 0; @@ -2779,6 +2826,13 @@ static int icnss_resource_parse(struct icnss_priv *priv) struct resource *res; u32 int_prop; + if (of_property_read_bool(pdev->dev.of_node, "qcom,icnss-adc_tm")) { + ret = icnss_get_vbatt_info(priv); + if (ret == -EPROBE_DEFER) + goto out; + priv->vbatt_supported = true; + } + ret = icnss_get_vreg(priv); if (ret) { icnss_pr_err("Failed to get vreg, err = %d\n", ret); diff --git a/drivers/soc/qcom/icnss2/main.h b/drivers/soc/qcom/icnss2/main.h index bbfd9bd05d0d..a273a25f83a4 100644 --- a/drivers/soc/qcom/icnss2/main.h +++ b/drivers/soc/qcom/icnss2/main.h @@ -6,10 +6,13 @@ #ifndef __MAIN_H__ #define __MAIN_H__ +#include +#include #include #include #include #include +#include #include #include #include @@ -218,6 +221,9 @@ struct icnss_stats { uint32_t rejuvenate_ack_req; uint32_t rejuvenate_ack_resp; uint32_t rejuvenate_ack_err; + uint32_t vbatt_req; + uint32_t vbatt_resp; + uint32_t vbatt_req_err; uint32_t device_info_req; uint32_t device_info_resp; uint32_t device_info_err; @@ -363,6 +369,11 @@ struct icnss_priv { uint32_t fw_error_fatal_irq; uint32_t fw_early_crash_irq; struct completion unblock_shutdown; + struct adc_tm_param vph_monitor_params; + struct adc_tm_chip *adc_tm_dev; + struct iio_channel *channel; + uint64_t vph_pwr; + bool vbatt_supported; char function_name[WLFW_FUNCTION_NAME_LEN + 1]; bool is_ssr; bool smmu_s1_enable; diff --git a/drivers/soc/qcom/icnss2/power.c b/drivers/soc/qcom/icnss2/power.c index 745906609dc9..a1e238d94176 100644 --- a/drivers/soc/qcom/icnss2/power.c +++ b/drivers/soc/qcom/icnss2/power.c @@ -11,6 +11,7 @@ #include "main.h" #include "qmi.h" #include "debug.h" +#include "power.h" static struct icnss_vreg_cfg icnss_wcn6750_vreg_list[] = { {"vdd-cx-mx", 824000, 952000, 0, 0, 0, false}, @@ -40,6 +41,9 @@ static struct icnss_clk_cfg icnss_adrestea_clk_list[] = { #define ICNSS_CLK_ADRESTEA_LIST_SIZE ARRAY_SIZE(icnss_adrestea_clk_list) #define MAX_PROP_SIZE 32 +#define ICNSS_THRESHOLD_HIGH 3600000 +#define ICNSS_THRESHOLD_LOW 3450000 +#define ICNSS_THRESHOLD_GUARD 20000 static int icnss_get_vreg_single(struct icnss_priv *priv, struct icnss_vreg_info *vreg) @@ -669,4 +673,131 @@ void icnss_put_resources(struct icnss_priv *priv) icnss_put_vreg(priv); } +static int icnss_get_phone_power(struct icnss_priv *priv, uint64_t *result_uv) +{ + int ret = 0; + int result; + if (!priv->channel) { + icnss_pr_err("Channel doesn't exists\n"); + ret = -EINVAL; + goto out; + } + + ret = iio_read_channel_processed(priv->channel, &result); + if (ret < 0) { + icnss_pr_err("Error reading channel, ret = %d\n", ret); + goto out; + } + + *result_uv = (uint64_t)result; +out: + return ret; +} + +static void icnss_vph_notify(enum adc_tm_state state, void *ctx) +{ + struct icnss_priv *priv = ctx; + u64 vph_pwr = 0; + u64 vph_pwr_prev; + int ret = 0; + bool update = true; + + if (!priv) { + icnss_pr_err("Priv pointer is NULL\n"); + return; + } + + vph_pwr_prev = priv->vph_pwr; + + ret = icnss_get_phone_power(priv, &vph_pwr); + if (ret < 0) + return; + + if (vph_pwr < ICNSS_THRESHOLD_LOW) { + if (vph_pwr_prev < ICNSS_THRESHOLD_LOW) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_LOW + + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.low_thr = 0; + } else if (vph_pwr > ICNSS_THRESHOLD_HIGH) { + if (vph_pwr_prev > ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_HIGH - + ICNSS_THRESHOLD_GUARD; + priv->vph_monitor_params.high_thr = 0; + } else { + if (vph_pwr_prev > ICNSS_THRESHOLD_LOW && + vph_pwr_prev < ICNSS_THRESHOLD_HIGH) + update = false; + priv->vph_monitor_params.state_request = + ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + } + + priv->vph_pwr = vph_pwr; + + if (update) { + icnss_send_vbatt_update(priv, vph_pwr); + icnss_pr_dbg("set low threshold to %d, high threshold to %d Phone power=%llu\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr, vph_pwr); + } + + ret = adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +} + +static int icnss_setup_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + if (!priv->adc_tm_dev) { + icnss_pr_err("ADC TM handler is NULL\n"); + ret = -EINVAL; + goto out; + } + + priv->vph_monitor_params.low_thr = ICNSS_THRESHOLD_LOW; + priv->vph_monitor_params.high_thr = ICNSS_THRESHOLD_HIGH; + priv->vph_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + priv->vph_monitor_params.channel = ADC5_VBAT_SNS; + priv->vph_monitor_params.btm_ctx = priv; + priv->vph_monitor_params.threshold_notification = &icnss_vph_notify; + icnss_pr_dbg("Set low threshold to %d, high threshold to %d\n", + priv->vph_monitor_params.low_thr, + priv->vph_monitor_params.high_thr); + + ret = adc_tm_channel_measure(priv->adc_tm_dev, + &priv->vph_monitor_params); + if (ret) + icnss_pr_err("TM channel setup failed %d\n", ret); +out: + return ret; +} + +int icnss_init_vph_monitor(struct icnss_priv *priv) +{ + int ret = 0; + + ret = icnss_get_phone_power(priv, &priv->vph_pwr); + if (ret < 0) + goto out; + + icnss_pr_dbg("Phone power=%llu\n", priv->vph_pwr); + + icnss_send_vbatt_update(priv, priv->vph_pwr); + + ret = icnss_setup_vph_monitor(priv); + if (ret) + goto out; +out: + return ret; +} diff --git a/drivers/soc/qcom/icnss2/power.h b/drivers/soc/qcom/icnss2/power.h index cac55578fcef..003694135435 100644 --- a/drivers/soc/qcom/icnss2/power.h +++ b/drivers/soc/qcom/icnss2/power.h @@ -10,6 +10,7 @@ int icnss_hw_power_on(struct icnss_priv *priv); int icnss_hw_power_off(struct icnss_priv *priv); int icnss_get_clk(struct icnss_priv *priv); int icnss_get_vreg(struct icnss_priv *priv); +int icnss_init_vph_monitor(struct icnss_priv *priv); void icnss_put_resources(struct icnss_priv *priv); void icnss_put_vreg(struct icnss_priv *priv); void icnss_put_clk(struct icnss_priv *priv); diff --git a/drivers/soc/qcom/icnss2/qmi.c b/drivers/soc/qcom/icnss2/qmi.c index 7a76d3afc4f0..38d934fe57e2 100644 --- a/drivers/soc/qcom/icnss2/qmi.c +++ b/drivers/soc/qcom/icnss2/qmi.c @@ -2194,6 +2194,77 @@ int icnss_send_wlan_disable_to_fw(struct icnss_priv *priv) return wlfw_wlan_mode_send_sync_msg(priv, mode); } +int icnss_send_vbatt_update(struct icnss_priv *priv, uint64_t voltage_uv) +{ + int ret; + struct wlfw_vbatt_req_msg_v01 *req; + struct wlfw_vbatt_resp_msg_v01 *resp; + struct qmi_txn txn; + + if (!priv) + return -ENODEV; + + if (test_bit(ICNSS_FW_DOWN, &priv->state)) + return -EINVAL; + + icnss_pr_dbg("Sending Vbatt message, state: 0x%lx\n", priv->state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + priv->stats.vbatt_req++; + + req->voltage_uv = voltage_uv; + + ret = qmi_txn_init(&priv->qmi, &txn, wlfw_vbatt_resp_msg_v01_ei, resp); + if (ret < 0) { + icnss_pr_err("Fail to init txn for Vbatt message resp %d\n", + ret); + goto out; + } + + ret = qmi_send_request(&priv->qmi, NULL, &txn, + QMI_WLFW_VBATT_REQ_V01, + WLFW_VBATT_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_vbatt_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + icnss_pr_err("Fail to send Vbatt message req %d\n", ret); + goto out; + } + + ret = qmi_txn_wait(&txn, priv->ctrl_params.qmi_timeout); + if (ret < 0) { + icnss_pr_err("VBATT message resp wait failed with ret %d\n", + ret); + goto out; + } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + icnss_pr_err("QMI Vbatt message request rejected, result:%d error:%d\n", + resp->resp.result, resp->resp.error); + ret = -resp->resp.result; + goto out; + } + + priv->stats.vbatt_resp++; + + kfree(resp); + kfree(req); + return 0; + +out: + kfree(resp); + kfree(req); + priv->stats.vbatt_req_err++; + return ret; +} + #ifdef CONFIG_ICNSS2_DEBUG static inline u32 icnss_get_host_build_type(void) { diff --git a/drivers/soc/qcom/icnss2/qmi.h b/drivers/soc/qcom/icnss2/qmi.h index bf850a4fcb5d..f4c1d2b263f1 100644 --- a/drivers/soc/qcom/icnss2/qmi.h +++ b/drivers/soc/qcom/icnss2/qmi.h @@ -104,6 +104,11 @@ static inline int icnss_register_fw_service(struct icnss_priv *priv) return 0; } static inline void icnss_unregister_fw_service(struct icnss_priv *priv) {} +static inline int icnss_send_vbatt_update(struct icnss_priv *priv, + uint64_t voltage_uv) +{ + return 0; +} static inline int wlfw_device_info_send_msg(struct icnss_priv *priv) { @@ -168,6 +173,7 @@ int icnss_send_wlan_enable_to_fw(struct icnss_priv *priv, int icnss_send_wlan_disable_to_fw(struct icnss_priv *priv); int icnss_register_fw_service(struct icnss_priv *priv); void icnss_unregister_fw_service(struct icnss_priv *priv); +int icnss_send_vbatt_update(struct icnss_priv *priv, uint64_t voltage_uv); int wlfw_host_cap_send_sync(struct icnss_priv *priv); int wlfw_device_info_send_msg(struct icnss_priv *priv); int wlfw_wlan_mode_send_sync_msg(struct icnss_priv *priv,