diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index 7642f3601dbf..998ede7e87ef 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c @@ -26,6 +26,8 @@ static DEFINE_IDR(icc_idr); static LIST_HEAD(icc_providers); +static int providers_count; +static bool synced_state; static DEFINE_MUTEX(icc_lock); static struct dentry *icc_debugfs_dir; @@ -267,6 +269,12 @@ static int aggregate_requests(struct icc_node *node) } p->aggregate(node, r->tag, avg_bw, peak_bw, &node->avg_bw, &node->peak_bw); + + /* during boot use the initial bandwidth as a floor value */ + if (!synced_state) { + node->avg_bw = max(node->avg_bw, node->init_avg); + node->peak_bw = max(node->peak_bw, node->init_peak); + } } return 0; @@ -958,6 +966,19 @@ void icc_node_add(struct icc_node *node, struct icc_provider *provider) node->provider = provider; list_add_tail(&node->node_list, &provider->nodes); + /* get the initial bandwidth values and sync them with hardware */ + if (provider->get_bw) { + provider->get_bw(node, &node->init_avg, &node->init_peak); + } else { + node->init_avg = INT_MAX; + node->init_peak = INT_MAX; + } + node->avg_bw = node->init_avg; + node->peak_bw = node->init_peak; + provider->set(node, node); + node->avg_bw = 0; + node->peak_bw = 0; + mutex_unlock(&icc_lock); } EXPORT_SYMBOL_GPL(icc_node_add); @@ -1053,8 +1074,54 @@ int icc_provider_del(struct icc_provider *provider) } EXPORT_SYMBOL_GPL(icc_provider_del); +static int of_count_icc_providers(struct device_node *np) +{ + struct device_node *child; + int count = 0; + + for_each_available_child_of_node(np, child) { + if (of_property_read_bool(child, "#interconnect-cells")) + count++; + count += of_count_icc_providers(child); + } + of_node_put(np); + + return count; +} + +void icc_sync_state(struct device *dev) +{ + struct icc_provider *p; + struct icc_node *n; + static int count; + + count++; + + if (count < providers_count) + return; + + mutex_lock(&icc_lock); + synced_state = true; + list_for_each_entry(p, &icc_providers, provider_list) { + dev_dbg(p->dev, "interconnect provider is in synced state\n"); + list_for_each_entry(n, &p->nodes, node_list) { + if (n->init_avg || n->init_peak) { + aggregate_requests(n); + p->set(n, n); + } + } + } + mutex_unlock(&icc_lock); +} +EXPORT_SYMBOL_GPL(icc_sync_state); + static int __init icc_init(void) { + struct device_node *root = of_find_node_by_path("/"); + + providers_count = of_count_icc_providers(root); + of_node_put(root); + icc_debugfs_dir = debugfs_create_dir("interconnect", NULL); debugfs_create_file("interconnect_summary", 0444, icc_debugfs_dir, NULL, &icc_summary_fops); diff --git a/drivers/interconnect/qcom/osm-l3.c b/drivers/interconnect/qcom/osm-l3.c index eb0158af346f..695f28789e98 100644 --- a/drivers/interconnect/qcom/osm-l3.c +++ b/drivers/interconnect/qcom/osm-l3.c @@ -322,6 +322,7 @@ static struct platform_driver osm_l3_driver = { .driver = { .name = "osm-l3", .of_match_table = osm_l3_of_match, + .sync_state = icc_sync_state, }, }; module_platform_driver(osm_l3_driver); diff --git a/drivers/interconnect/qcom/sc7180.c b/drivers/interconnect/qcom/sc7180.c index a6f119f363b1..bf11b82ed55c 100644 --- a/drivers/interconnect/qcom/sc7180.c +++ b/drivers/interconnect/qcom/sc7180.c @@ -633,6 +633,7 @@ static struct platform_driver qnoc_driver = { .driver = { .name = "qnoc-sc7180", .of_match_table = qnoc_of_match, + .sync_state = icc_sync_state, }, }; module_platform_driver(qnoc_driver); diff --git a/drivers/interconnect/qcom/sdm845.c b/drivers/interconnect/qcom/sdm845.c index 3b81dbb71b0b..d79e3163e2c3 100644 --- a/drivers/interconnect/qcom/sdm845.c +++ b/drivers/interconnect/qcom/sdm845.c @@ -559,6 +559,7 @@ static struct platform_driver qnoc_driver = { .driver = { .name = "qnoc-sdm845", .of_match_table = qnoc_of_match, + .sync_state = icc_sync_state, }, }; module_platform_driver(qnoc_driver); diff --git a/include/linux/interconnect-provider.h b/include/linux/interconnect-provider.h index 4d535fddd5d3..6bd01f7159c6 100644 --- a/include/linux/interconnect-provider.h +++ b/include/linux/interconnect-provider.h @@ -49,6 +49,7 @@ struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec, * @aggregate: pointer to device specific aggregate operation function * @pre_aggregate: pointer to device specific function that is called * before the aggregation begins (optional) + * @get_bw: pointer to device specific function to get current bandwidth * @xlate: provider-specific callback for mapping nodes from phandle arguments * @xlate_extended: vendor-specific callback for mapping node data from phandle arguments * @dev: the device this interconnect provider belongs to @@ -63,6 +64,7 @@ struct icc_provider { int (*aggregate)(struct icc_node *node, u32 tag, u32 avg_bw, u32 peak_bw, u32 *agg_avg, u32 *agg_peak); void (*pre_aggregate)(struct icc_node *node); + int (*get_bw)(struct icc_node *node, u32 *avg, u32 *peak); struct icc_node* (*xlate)(struct of_phandle_args *spec, void *data); struct icc_node_data* (*xlate_extended)(struct of_phandle_args *spec, void *data); struct device *dev; @@ -86,6 +88,8 @@ struct icc_provider { * @req_list: a list of QoS constraint requests associated with this node * @avg_bw: aggregated value of average bandwidth requests from all consumers * @peak_bw: aggregated value of peak bandwidth requests from all consumers + * @init_avg: average bandwidth value that is read from the hardware during init + * @init_peak: peak bandwidth value that is read from the hardware during init * @data: pointer to private data */ struct icc_node { @@ -102,6 +106,8 @@ struct icc_node { struct hlist_head req_list; u32 avg_bw; u32 peak_bw; + u32 init_avg; + u32 init_peak; void *data; }; @@ -119,6 +125,7 @@ int icc_nodes_remove(struct icc_provider *provider); int icc_provider_add(struct icc_provider *provider); int icc_provider_del(struct icc_provider *provider); struct icc_node_data *of_icc_get_from_provider(struct of_phandle_args *spec); +void icc_sync_state(struct device *dev); #else