From 681c14539a16ee472293fc97e9a1ab2827d039b7 Mon Sep 17 00:00:00 2001 From: Kartikey Arora Date: Sun, 4 Aug 2024 22:38:01 +0530 Subject: [PATCH] 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 --- icnss2/main.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++++- icnss2/main.h | 50 ++++++++++++ inc/icnss2.h | 1 + 3 files changed, 269 insertions(+), 1 deletion(-) diff --git a/icnss2/main.c b/icnss2/main.c index 790cf48237..ddeb951546 100644 --- a/icnss2/main.c +++ b/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: diff --git a/icnss2/main.h b/icnss2/main.h index 0701183383..4513e5c685 100644 --- a/icnss2/main.h +++ b/icnss2/main.h @@ -20,6 +20,9 @@ #else #include #endif +#if IS_ENABLED(CONFIG_INTERCONNECT) +#include +#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; diff --git a/inc/icnss2.h b/inc/icnss2.h index e744cba692..04c1b3b3f2 100644 --- a/inc/icnss2.h +++ b/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 *),