|
@@ -413,6 +413,217 @@ bool icnss_is_fw_ready(void)
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(icnss_is_fw_ready);
|
|
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)
|
|
void icnss_block_shutdown(bool status)
|
|
{
|
|
{
|
|
if (!penv)
|
|
if (!penv)
|
|
@@ -4817,10 +5028,14 @@ static int icnss_probe(struct platform_device *pdev)
|
|
if (ret)
|
|
if (ret)
|
|
goto out_free_resources;
|
|
goto out_free_resources;
|
|
|
|
|
|
- ret = icnss_smmu_dt_parse(priv);
|
|
|
|
|
|
+ ret = icnss_register_bus_scale(priv);
|
|
if (ret)
|
|
if (ret)
|
|
goto out_free_resources;
|
|
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->event_lock);
|
|
spin_lock_init(&priv->on_off_lock);
|
|
spin_lock_init(&priv->on_off_lock);
|
|
spin_lock_init(&priv->soc_wake_msg_lock);
|
|
spin_lock_init(&priv->soc_wake_msg_lock);
|
|
@@ -4921,6 +5136,8 @@ out_destroy_wq:
|
|
destroy_workqueue(priv->event_wq);
|
|
destroy_workqueue(priv->event_wq);
|
|
smmu_cleanup:
|
|
smmu_cleanup:
|
|
priv->iommu_domain = NULL;
|
|
priv->iommu_domain = NULL;
|
|
|
|
+unreg_bus_scale:
|
|
|
|
+ icnss_unregister_bus_scale(priv);
|
|
out_free_resources:
|
|
out_free_resources:
|
|
icnss_put_resources(priv);
|
|
icnss_put_resources(priv);
|
|
out_reset_drvdata:
|
|
out_reset_drvdata:
|