Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3cd0f884 authored by Anurag Chouhan's avatar Anurag Chouhan Committed by Sandeep Singh
Browse files

icnss: Add changes to enable VBATT feature



Power voting need to be controlled based on the battery voltage,
Register ADC and IIO channels to monitor the battery voltage,
Register threshold notification with the ADC, to get voltage
change notification based on the configuration, Register IIO
channel to read current battery voltage, if the voltage is
exceeding the predefined threshold, then update the voltage
to the firmware using QMI command.

Change-Id: I59cad3d3e301423c8a988ff34fb2db749c85c3fd
Signed-off-by: default avatarSandeep Singh <sandsing@codeaurora.org>
parent d02d62b8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ Optional properties:
			   uS.
  - qcom,icnss-vadc: VADC handle for vph_pwr read APIs.
  - qcom,icnss-adc_tm: VADC handle for vph_pwr notification APIs.
  - io-channels: IIO channel to monitor for vph_pwr power.
  - io-channel-names: IIO channel name as per the client name.
  - qcom,smmu-s1-bypass: Boolean context flag to set SMMU to S1 bypass
  - qcom,wlan-msa-fixed-region: phandle, specifier pairs to children of /reserved-memory
  - qcom,hyp_disabled: Boolean context flag to disable hyperviser
+186 −0
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@
#include <linux/ipc_logging.h>
#include <linux/thread_info.h>
#include <linux/uaccess.h>
#include <linux/adc-tm-clients.h>
#include <linux/iio/consumer.h>
#include <dt-bindings/iio/qcom,spmi-vadc.h>
#include <linux/etherdevice.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -790,6 +793,136 @@ int icnss_call_driver_uevent(struct icnss_priv *priv,
	return priv->ops->uevent(&priv->pdev->dev, &uevent_data);
}


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(penv->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;
	uint64_t vph_pwr = 0;
	uint64_t 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_tm5_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 = ADC_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_tm5_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;
}

static 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;
}

static int icnss_driver_event_server_arrive(void *data)
{
	int ret = 0;
@@ -859,6 +992,9 @@ static int icnss_driver_event_server_arrive(void *data)
	if (!penv->fw_early_crash_irq)
		register_early_crash_notifications(&penv->pdev->dev);

	if (penv->vbatt_supported)
		icnss_init_vph_monitor(penv);

	return ret;

err_setup_msa:
@@ -882,6 +1018,10 @@ static int icnss_driver_event_server_exit(void *data)

	icnss_clear_server(penv);

	if (penv->adc_tm_dev && penv->vbatt_supported)
		adc_tm5_disable_chan_meas(penv->adc_tm_dev,
					  &penv->vph_monitor_params);

	return 0;
}

@@ -3045,6 +3185,45 @@ static void icnss_debugfs_destroy(struct icnss_priv *priv)
	debugfs_remove_recursive(priv->root_dentry);
}


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_probe(struct platform_device *pdev)
{
	int ret = 0;
@@ -3074,6 +3253,13 @@ static int icnss_probe(struct platform_device *pdev)

	priv->vreg_info = icnss_vreg_info;

	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;
	}

	for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) {
		ret = icnss_get_vreg_info(dev, &priv->vreg_info[i]);

+11 −0
Original line number Diff line number Diff line
@@ -13,6 +13,9 @@
#ifndef __ICNSS_PRIVATE_H__
#define __ICNSS_PRIVATE_H__

#include <linux/adc-tm-clients.h>
#include <linux/iio/consumer.h>

#define icnss_ipc_log_string(_x...) do {				\
	if (icnss_ipc_log_context)					\
		ipc_log_string(icnss_ipc_log_context, _x);		\
@@ -239,6 +242,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;
};

#define WLFW_MAX_TIMESTAMP_LEN 32
@@ -356,6 +362,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];
};

+70 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <soc/qcom/service-notifier.h>
#include "wlan_firmware_service_v01.h"
#include "icnss_private.h"
#include "icnss_qmi.h"

#ifdef CONFIG_ICNSS_DEBUG
unsigned long qmi_timeout = 3000;
@@ -1324,4 +1325,73 @@ 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, WLFW_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;
}
+7 −1
Original line number Diff line number Diff line
/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
/* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -92,6 +92,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;
}

#else
int wlfw_ind_register_send_sync_msg(struct icnss_priv *priv);
@@ -121,6 +126,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);
#endif

#endif /* __ICNSS_QMI_H__*/