[PATCH v1 2/3] interconnect: qcom: Add CAMNOC interconnect provider driver
Atanas Filipov <[email protected]>
| Newsgroups | org.kernel.vger.linux-pm,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The Camera Network-on-Chip (CAMNOC) is the internal AXI interconnect within the camera subsystem. Multiple camera sub-devices share the CAMNOC AXI clock. When each sub-device calls clk_set_rate() directly on this shared clock, a last-writer-wins race condition arises: the final caller sets the rate regardless of what other active consumers require. Introduce a lightweight ICC provider for CAMNOC that solves this by leveraging the ICC aggregation framework. Each consumer obtains an ICC path to SLAVE_CAMNOC_AXI and votes for bandwidth via icc_set_bw(). The ICC core aggregates all votes (max of peak_bw across consumers) and the provider's set() callback translates the result into a single clk_set_rate() call on the CAMNOC AXI clock. Consumers encode the required clock frequency directly as peak_bw in kBps (e.g. 400000 for 400 MHz). The conversion is: rate_hz = peak_bw_kBps * 1000 Implement get_bw() returning 0 for both avg and peak to prevent the ICC core from defaulting to INT_MAX before any consumer has voted. Signed-off-by: Atanas Filipov <[email protected]> --- drivers/interconnect/qcom/Kconfig | 10 ++ drivers/interconnect/qcom/Makefile | 2 + drivers/interconnect/qcom/qcom-camnoc.c | 184 ++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 drivers/interconnect/qcom/qcom-camnoc.c diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index 5b8a094ca4ed..9d9d238019a0 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -135,6 +135,16 @@ config INTERCONNECT_QCOM_NORD the RPMh hardware and communicates via Bus Clock Manager (BCM) through the Resource State Coordinator (RSC). +config INTERCONNECT_QCOM_CAMNOC + tristate "Qualcomm CAMNOC interconnect driver" + depends on INTERCONNECT_QCOM || COMPILE_TEST + depends on ARM64 || COMPILE_TEST + help + Say y here to support the Camera Network-on-Chip (CAMNOC) + interconnect driver for Qualcomm SoCs. It aggregates bandwidth + requests from camera sub-devices (IFE, JPEG, BPS, etc.) and + scales CAM_CC_CAMNOC_AXI_CLK accordingly. + config INTERCONNECT_QCOM_OSM_L3 tristate "Qualcomm OSM L3 interconnect driver" depends on INTERCONNECT_QCOM || COMPILE_TEST diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index 1c7d410b40cc..52a5998ae6a8 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -18,6 +18,7 @@ qnoc-msm8974-objs := msm8974.o qnoc-msm8976-objs := msm8976.o qnoc-msm8996-objs := msm8996.o qnoc-nord-objs := nord.o +icc-camnoc-objs := qcom-camnoc.o icc-osm-l3-objs := osm-l3.o qnoc-qcm2290-objs := qcm2290.o qnoc-qcs404-objs := qcs404.o @@ -66,6 +67,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8976) += qnoc-msm8976.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8996) += qnoc-msm8996.o obj-$(CONFIG_INTERCONNECT_QCOM_NORD) += qnoc-nord.o +obj-$(CONFIG_INTERCONNECT_QCOM_CAMNOC) += icc-camnoc.o obj-$(CONFIG_INTERCONNECT_QCOM_OSM_L3) += icc-osm-l3.o obj-$(CONFIG_INTERCONNECT_QCOM_QCM2290) += qnoc-qcm2290.o obj-$(CONFIG_INTERCONNECT_QCOM_QCS404) += qnoc-qcs404.o diff --git a/drivers/interconnect/qcom/qcom-camnoc.c b/drivers/interconnect/qcom/qcom-camnoc.c new file mode 100644 index 000000000000..a5a484491515 --- /dev/null +++ b/drivers/interconnect/qcom/qcom-camnoc.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * Qualcomm Camera NOC (CAMNOC) interconnect provider. + * + * CAMNOC is the internal AXI interconnect within the Qualcomm camera + * subsystem. Multiple camera sub-devices (IFE, JPEG, BPS, etc.) share + * CAM_CC_CAMNOC_AXI_CLK. This driver acts as an ICC provider so that + * each sub-device can independently vote for bandwidth; the ICC core + * aggregates the votes (max of peak_bw across all consumers) and this + * driver translates the result into a clk_set_rate() call, avoiding + * the last-writer-wins race that occurs with direct clk_set_rate(). + * + * Consumers express their required clock rate directly as peak_bw in + * kBps (e.g. 400000 for 400 MHz). The driver converts kBps → Hz: + * rate_hz = peak_bw_kBps * 1000 + */ + +#include <linux/clk.h> +#include <linux/interconnect-provider.h> +#include <linux/interconnect.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <dt-bindings/interconnect/qcom,camnoc.h> + +#define to_camnoc_provider(_p) \ + container_of(_p, struct qcom_camnoc_icc_provider, provider) + +struct qcom_camnoc_icc_provider { + struct clk *clk; + struct icc_provider provider; +}; + +struct qcom_camnoc_node { + const char *name; + u16 buswidth; +}; + +static const struct qcom_camnoc_node camnoc_master_jpeg = { + .name = "master-camnoc-jpeg", + .buswidth = 16, +}; + +static const struct qcom_camnoc_node camnoc_slave_axi = { + .name = "slave-camnoc-axi", + .buswidth = 16, +}; + +static const struct qcom_camnoc_node * const camnoc_nodes[] = { + [MASTER_CAMNOC_JPEG] = &camnoc_master_jpeg, + [SLAVE_CAMNOC_AXI] = &camnoc_slave_axi, +}; + +#define CAMNOC_NUM_NODES ARRAY_SIZE(camnoc_nodes) + +static int qcom_camnoc_get_bw(struct icc_node *node, u32 *avg, u32 *peak) +{ + *avg = 0; + *peak = 0; + + return 0; +} + +static int qcom_camnoc_set(struct icc_node *src, struct icc_node *dst) +{ + struct qcom_camnoc_icc_provider *cp = + to_camnoc_provider(src->provider); + unsigned long rate; + + /* + * peak_bw is the aggregated max across all consumers (kBps). + * Consumers encode the required clock frequency directly as kBps, + * so the conversion is simply: rate_hz = peak_bw * 1000. + * A vote of 0 means no requirement; leave the clock at its minimum. + */ + rate = icc_units_to_bps(dst->peak_bw); + + dev_dbg(src->provider->dev, + "CAMNOC set: aggregated peak_bw=%u kBps -> rate=%lu Hz\n", + dst->peak_bw, rate); + + return clk_set_rate(cp->clk, rate); +} + +static void qcom_camnoc_remove(struct platform_device *pdev) +{ + struct qcom_camnoc_icc_provider *cp = platform_get_drvdata(pdev); + + icc_provider_deregister(&cp->provider); + icc_nodes_remove(&cp->provider); +} + +static int qcom_camnoc_probe(struct platform_device *pdev) +{ + struct qcom_camnoc_icc_provider *cp; + struct icc_onecell_data *data; + struct icc_provider *provider; + struct icc_node *node; + unsigned int i; + int ret; + + cp = devm_kzalloc(&pdev->dev, sizeof(*cp), GFP_KERNEL); + if (!cp) + return -ENOMEM; + + cp->clk = devm_clk_get(&pdev->dev, "camnoc_axi"); + if (IS_ERR(cp->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(cp->clk), + "failed to get camnoc_axi clock\n"); + + data = devm_kzalloc(&pdev->dev, + struct_size(data, nodes, CAMNOC_NUM_NODES), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->num_nodes = CAMNOC_NUM_NODES; + + provider = &cp->provider; + provider->dev = &pdev->dev; + provider->get_bw = qcom_camnoc_get_bw; + provider->set = qcom_camnoc_set; + provider->aggregate = icc_std_aggregate; + provider->xlate = of_icc_xlate_onecell; + provider->data = data; + + icc_provider_init(provider); + + for (i = 0; i < CAMNOC_NUM_NODES; i++) { + node = icc_node_create_dyn(); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto err_remove; + } + + ret = icc_node_set_name(node, provider, camnoc_nodes[i]->name); + if (ret) { + icc_node_destroy(node->id); + goto err_remove; + } + + node->data = (void *)camnoc_nodes[i]; + icc_node_add(node, provider); + data->nodes[i] = node; + } + + icc_link_nodes(data->nodes[MASTER_CAMNOC_JPEG], + &data->nodes[SLAVE_CAMNOC_AXI]); + + ret = icc_provider_register(provider); + if (ret) + goto err_remove; + + platform_set_drvdata(pdev, cp); + + return 0; + +err_remove: + icc_nodes_remove(provider); + return ret; +} + +static const struct of_device_id qcom_camnoc_of_match[] = { + { .compatible = "qcom,sm8250-cam-virt" }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_camnoc_of_match); + +static struct platform_driver qcom_camnoc_driver = { + .probe = qcom_camnoc_probe, + .remove = qcom_camnoc_remove, + .driver = { + .name = "qcom-camnoc-icc", + .of_match_table = qcom_camnoc_of_match, + .sync_state = icc_sync_state, + }, +}; +module_platform_driver(qcom_camnoc_driver); + +MODULE_DESCRIPTION("Qualcomm CAMNOC interconnect driver"); +MODULE_LICENSE("GPL"); -- 2.34.1