Kaynağa Gözat

icnss2: Add changes to vote for bus bandwidth

Add changes to support voting for bus bandwidth through driver
for different interconnect paths based on throughput threshols
and DDR frequency.

CRs-Fixed: 3887876
Change-Id: I4cf759d46a9ef8f3f5688d22e7619d5a010293a3
Kartikey Arora 7 ay önce
ebeveyn
işleme
681c14539a
3 değiştirilmiş dosya ile 269 ekleme ve 1 silme
  1. 218 1
      icnss2/main.c
  2. 50 0
      icnss2/main.h
  3. 1 0
      inc/icnss2.h

+ 218 - 1
icnss2/main.c

@@ -413,6 +413,217 @@ bool icnss_is_fw_ready(void)
 }
 EXPORT_SYMBOL(icnss_is_fw_ready);
 
+#if IS_ENABLED(CONFIG_INTERCONNECT)
+/**
+ * icnss_register_bus_scale() - Setup interconnect voting data
+ * @plat_priv: Platform data structure
+ *
+ * For different interconnect path configured in device tree setup voting data
+ * for list of bandwidth requirements.
+ *
+ * Result: 0 for success. -EINVAL if not configured
+ */
+static int icnss_register_bus_scale(struct icnss_priv *plat_priv)
+{
+	int ret = -EINVAL;
+	u32 idx, i, j, cfg_arr_size, *cfg_arr = NULL;
+	struct icnss_bus_bw_info *bus_bw_info, *tmp;
+	struct device *dev = &plat_priv->pdev->dev;
+
+	INIT_LIST_HEAD(&plat_priv->icc.list_head);
+	ret = of_property_read_u32(dev->of_node,
+				   "qcom,icc-path-count",
+				   &plat_priv->icc.path_count);
+	if (ret) {
+		icnss_pr_dbg("Platform Bus Interconnect path not configured\n");
+		return 0;
+	}
+
+	ret = of_property_read_u32(plat_priv->pdev->dev.of_node,
+				   "qcom,bus-bw-cfg-count",
+				   &plat_priv->icc.bus_bw_cfg_count);
+	if (ret) {
+		icnss_pr_err("Failed to get Bus BW Config table size\n");
+		goto cleanup;
+	}
+
+	cfg_arr_size = plat_priv->icc.path_count *
+			plat_priv->icc.bus_bw_cfg_count * ICNSS_ICC_VOTE_MAX;
+	cfg_arr = kcalloc(cfg_arr_size, sizeof(*cfg_arr), GFP_KERNEL);
+	if (!cfg_arr) {
+		icnss_pr_err("Failed to alloc cfg table mem\n");
+		ret = -ENOMEM;
+		goto cleanup;
+	}
+
+	ret = of_property_read_u32_array(plat_priv->pdev->dev.of_node,
+					 "qcom,bus-bw-cfg", cfg_arr,
+					 cfg_arr_size);
+	if (ret) {
+		icnss_pr_err("Invalid Bus BW Config Table\n");
+		goto cleanup;
+	}
+
+	icnss_pr_dbg("ICC Path_Count: %d BW_CFG_Count: %d\n",
+		     plat_priv->icc.path_count,
+		     plat_priv->icc.bus_bw_cfg_count);
+
+	for (idx = 0; idx < plat_priv->icc.path_count; idx++) {
+		bus_bw_info = devm_kzalloc(dev, sizeof(*bus_bw_info),
+					   GFP_KERNEL);
+		if (!bus_bw_info) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ret = of_property_read_string_index(dev->of_node,
+						    "interconnect-names", idx,
+						    &bus_bw_info->icc_name);
+		if (ret)
+			goto out;
+
+		bus_bw_info->icc_path =
+			of_icc_get(&plat_priv->pdev->dev,
+				   bus_bw_info->icc_name);
+
+		if (IS_ERR(bus_bw_info->icc_path))  {
+			ret = PTR_ERR(bus_bw_info->icc_path);
+			if (ret != -EPROBE_DEFER) {
+				icnss_pr_err("Failed to get Interconnect path for %s. Err: %d\n",
+					     bus_bw_info->icc_name, ret);
+				goto out;
+			}
+		}
+
+		bus_bw_info->cfg_table =
+			devm_kcalloc(dev, plat_priv->icc.bus_bw_cfg_count,
+				     sizeof(*bus_bw_info->cfg_table),
+				     GFP_KERNEL);
+		if (!bus_bw_info->cfg_table) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		icnss_pr_dbg("ICC Vote CFG for path: %s\n",
+			     bus_bw_info->icc_name);
+
+		for (i = 0, j = (idx * plat_priv->icc.bus_bw_cfg_count *
+		     ICNSS_ICC_VOTE_MAX);
+		     i < plat_priv->icc.bus_bw_cfg_count;
+		     i++, j += 2) {
+			bus_bw_info->cfg_table[i].avg_bw = cfg_arr[j];
+			bus_bw_info->cfg_table[i].peak_bw = cfg_arr[j + 1];
+
+			icnss_pr_dbg("ICC Vote BW: %d avg: %d peak: %d\n",
+				     i, bus_bw_info->cfg_table[i].avg_bw,
+				     bus_bw_info->cfg_table[i].peak_bw);
+		}
+		list_add_tail(&bus_bw_info->list,
+			      &plat_priv->icc.list_head);
+	}
+	kfree(cfg_arr);
+	return 0;
+out:
+	list_for_each_entry_safe(bus_bw_info, tmp,
+				 &plat_priv->icc.list_head, list) {
+		list_del(&bus_bw_info->list);
+	}
+cleanup:
+	kfree(cfg_arr);
+	memset(&plat_priv->icc, 0, sizeof(plat_priv->icc));
+	return ret;
+}
+
+static void icnss_unregister_bus_scale(struct icnss_priv *plat_priv)
+{
+	struct icnss_bus_bw_info *bus_bw_info, *tmp;
+
+	list_for_each_entry_safe(bus_bw_info, tmp,
+				 &plat_priv->icc.list_head, list) {
+		list_del(&bus_bw_info->list);
+		if (bus_bw_info->icc_path)
+			icc_put(bus_bw_info->icc_path);
+	}
+	memset(&plat_priv->icc, 0, sizeof(plat_priv->icc));
+}
+
+/**
+ * icnss_setup_bus_bandwidth() - Setup interconnect vote for given bandwidth
+ * @plat_priv: Platform private data struct
+ * @bw: bandwidth
+ * @save: toggle flag to save bandwidth to current_bw_vote
+ *
+ * Setup bandwidth votes for configured interconnect paths
+ *
+ * Return: 0 for success
+ */
+static int icnss_setup_bus_bandwidth(struct icnss_priv *plat_priv,
+				     u32 bw, bool save)
+{
+	int ret = 0;
+	struct icnss_bus_bw_info *bus_bw_info;
+
+	if (!plat_priv->icc.path_count)
+		return -EOPNOTSUPP;
+
+	if (bw >= plat_priv->icc.bus_bw_cfg_count) {
+		icnss_pr_err("Invalid bus bandwidth Type: %d", bw);
+		return -EINVAL;
+	}
+
+	list_for_each_entry(bus_bw_info, &plat_priv->icc.list_head, list) {
+		ret = icc_set_bw(bus_bw_info->icc_path,
+				 bus_bw_info->cfg_table[bw].avg_bw,
+				 bus_bw_info->cfg_table[bw].peak_bw);
+		if (ret) {
+			icnss_pr_err("Could not set BW Cfg: %d, err = %d ICC Path: %s Val: %d %d\n",
+				     bw, ret, bus_bw_info->icc_name,
+				     bus_bw_info->cfg_table[bw].avg_bw,
+				     bus_bw_info->cfg_table[bw].peak_bw);
+			break;
+		}
+	}
+
+	if (ret == 0 && save)
+		plat_priv->icc.current_bw_vote = bw;
+
+	return ret;
+}
+
+int icnss_request_bus_bandwidth(struct device *dev, int bandwidth)
+{
+	struct icnss_priv *plat_priv = dev_get_drvdata(dev);
+
+	if (!plat_priv)
+		return -ENODEV;
+
+	if (bandwidth < 0)
+		return -EINVAL;
+
+	return icnss_setup_bus_bandwidth(plat_priv, (u32)bandwidth, true);
+}
+
+#else
+static int icnss_register_bus_scale(struct icnss_priv *plat_priv)
+{
+	return 0;
+}
+
+static void icnss_unregister_bus_scale(struct icnss_priv *plat_priv) {}
+
+static int icnss_setup_bus_bandwidth(struct icnss_priv *plat_priv,
+				     u32 bw, bool save)
+{
+	return 0;
+}
+
+int icnss_request_bus_bandwidth(struct device *dev, int bandwidth)
+{
+	return 0;
+}
+#endif /* CONFIG_INTERCONNECT */
+EXPORT_SYMBOL(icnss_request_bus_bandwidth);
+
 void icnss_block_shutdown(bool status)
 {
 	if (!penv)
@@ -4817,10 +5028,14 @@ static int icnss_probe(struct platform_device *pdev)
 	if (ret)
 		goto out_free_resources;
 
-	ret = icnss_smmu_dt_parse(priv);
+	ret = icnss_register_bus_scale(priv);
 	if (ret)
 		goto out_free_resources;
 
+	ret = icnss_smmu_dt_parse(priv);
+	if (ret)
+		goto unreg_bus_scale;
+
 	spin_lock_init(&priv->event_lock);
 	spin_lock_init(&priv->on_off_lock);
 	spin_lock_init(&priv->soc_wake_msg_lock);
@@ -4921,6 +5136,8 @@ out_destroy_wq:
 	destroy_workqueue(priv->event_wq);
 smmu_cleanup:
 	priv->iommu_domain = NULL;
+unreg_bus_scale:
+	icnss_unregister_bus_scale(priv);
 out_free_resources:
 	icnss_put_resources(priv);
 out_reset_drvdata:

+ 50 - 0
icnss2/main.h

@@ -20,6 +20,9 @@
 #else
 #include <soc/qcom/icnss2.h>
 #endif
+#if IS_ENABLED(CONFIG_INTERCONNECT)
+#include <linux/interconnect.h>
+#endif
 #include "wlan_firmware_service_v01.h"
 #include "cnss_prealloc.h"
 #include "cnss_common.h"
@@ -53,6 +56,50 @@ struct icnss_control_params {
 	unsigned int bdf_type;
 };
 
+#if IS_ENABLED(CONFIG_INTERCONNECT)
+/**
+ * struct icnss_bus_bw_cfg - Interconnect vote data
+ * @avg_bw: Vote for average bandwidth
+ * @peak_bw: Vote for peak bandwidth
+ */
+struct icnss_bus_bw_cfg {
+	u32 avg_bw;
+	u32 peak_bw;
+};
+
+/* Number of bw votes (avg, peak) entries that ICC requires */
+#define ICNSS_ICC_VOTE_MAX 2
+
+/**
+ * struct icnss_bus_bw_info - Bus bandwidth config for interconnect path
+ * @list: Kernel linked list
+ * @icc_name: Name of interconnect path as defined in Device tree
+ * @icc_path: Interconnect path data structure
+ * @cfg_table: Interconnect vote data for average and peak bandwidth
+ */
+struct icnss_bus_bw_info {
+	struct list_head list;
+	const char *icc_name;
+	struct icc_path *icc_path;
+	struct icnss_bus_bw_cfg *cfg_table;
+};
+
+/**
+ * struct icnss_interconnect_cfg - ICNSS platform interconnect config
+ * @list_head: List of interconnect path bandwidth configs
+ * @path_count: Count of interconnect path configured in device tree
+ * @current_bw_vote: WLAN driver provided bandwidth vote
+ * @bus_bw_cfg_count: Number of bandwidth configs for voting. It is the array
+ * size of struct icnss_bus_bw_info.cfg_table
+ */
+struct icnss_interconnect_cfg {
+	struct list_head list_head;
+	u32 path_count;
+	int current_bw_vote;
+	u32 bus_bw_cfg_count;
+};
+#endif
+
 enum icnss_driver_event_type {
 	ICNSS_DRIVER_EVENT_SERVER_ARRIVE,
 	ICNSS_DRIVER_EVENT_SERVER_EXIT,
@@ -411,6 +458,9 @@ struct icnss_priv {
 	struct list_head soc_wake_msg_list;
 	spinlock_t event_lock;
 	spinlock_t soc_wake_msg_lock;
+	#if IS_ENABLED(CONFIG_INTERCONNECT)
+	struct icnss_interconnect_cfg icc;
+	#endif
 	struct work_struct event_work;
 	struct work_struct fw_recv_msg_work;
 	struct work_struct soc_wake_msg_work;

+ 1 - 0
inc/icnss2.h

@@ -189,6 +189,7 @@ extern int icnss_wlan_disable(struct device *dev, enum icnss_driver_mode mode);
 extern void icnss_enable_irq(struct device *dev, unsigned int ce_id);
 extern void icnss_disable_irq(struct device *dev, unsigned int ce_id);
 extern int icnss_get_soc_info(struct device *dev, struct icnss_soc_info *info);
+extern int icnss_request_bus_bandwidth(struct device *dev, int bandwidth);
 extern int icnss_ce_free_irq(struct device *dev, unsigned int ce_id, void *ctx);
 extern int icnss_ce_request_irq(struct device *dev, unsigned int ce_id,
 	irqreturn_t (*handler)(int, void *),