* [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support
@ 2026-04-27 12:43 Loic Poulain
2026-04-27 12:43 ` [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices Loic Poulain
` (13 more replies)
0 siblings, 14 replies; 33+ messages in thread
From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw)
To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain,
Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva,
Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Konrad Dybcio
Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening,
devicetree, laurent.pinchart, kieran.bingham
This series introduces support for the Qualcomm CAMSS Offline Processing
Engine (OPE), as found on Agatti-based platforms. Boards such as Arduino
UNO-Q use this SoC family and will benefit from hardware-assisted image
processing enabled by this work.
This represents the first step toward enabling image processing beyond
raw capture on Qualcomm platforms by using hardware blocks for
operations such as debayering, color correction, and scaling.
The OPE sits outside the live capture pipeline. It operates on frames
fetched from system memory and writes processed results back to memory.
Because of this design, the OPE is not tied to any specific capture
interface: frames may come from CAMSS RDI or PIX paths, or from any
other producer capable of providing memory-backed buffers.
The hardware can sustain up to 580 megapixels per second, which is
sufficient to process a 9MPix (4K) stream at 60 fps or to handle
four parallel 2MPix (HD) streams at 60 fps.
In order to enable reuse across future CAMSS ISP implementations,
a new camss-isp-common module is introduced, providing common
helpers for:
- buffer queue management
- job scheduling
- pipeline construction
- parameter parsing
While currently tailored for CAMSS ISP drivers, these helpers are
designed in a way that allows further generalization and extension
into the V4L2 ISP framework.
The driver is designed to support multiple processing contexts. At
present, however, only a single context is instantiated, which is
created on the first media‑pipeline open.
This keeps the implementation simple while ensuring the design is
ready for forthcoming multi‑context support at the V4L2/media
framework level.
Since the OPE is a child node of the CAMSS device, the CAMSS driver
is reworked to properly share and manage power‑related resources,
including power domains and common clocks.
Graph:
frame-input --+
+--> proc --> frame-output
params -------+
Compliance: v4l2-compliance has been executed without any error on
the media and video devices.
Testing: A camss-isp-m2m tool is available at
https://github.com/loicpoulain/camss-isp-m2m-test
The tool can be used to exercise and stress‑test the OPE engine,
as well as to observe performance and latency behavior under
various operating conditions (resolution, format, buf count, etc).
./camss-isp-m2m -s 3840x2160 -n 5
Name Device Dir Type
--------------------------------------------------------------
frame-input /dev/video10 output video
frame-output /dev/video11 capture video
params /dev/video12 output meta
Test: 3840x2160 RGGB -> 3840x2160 NV12 [5 frames]
Input /dev/video10 3840x2160 RGGB bpl=3840 size=8294400
Output /dev/video11 3840x2160 NV12 bpl=5760 size=12441600
Buffers:
/dev/video10 buf[0] offset=0x00000000 length=8294400 VA=0xffffbcb87000
/dev/video11 buf[0] offset=0x00000000 length=12441600 VA=0xffffbbfa9000
Streaming 5 frames...
seq=0 buf: in=0 out=0 14.833 ms (67.4 fps)
seq=1 buf: in=0 out=0 14.874 ms (67.2 fps)
seq=2 buf: in=0 out=0 14.902 ms (67.1 fps)
seq=3 buf: in=0 out=0 14.960 ms (66.8 fps)
seq=4 buf: in=0 out=0 14.896 ms (67.1 fps)
Changes in V2:
- Not an RFC anymore
- Move from v4l2-m2m to multi-devices design
- Support for parameters
- Helpers for pipeline, job scheduling, buf-queues
- OPE as a child node of CAMSS
---
Bryan O'Donoghue (1):
media: qcom: camss: Add support to populate sub-devices
Loic Poulain (13):
media: qcom: camss: Add PM clock support and integrate with runtime PM
media: qcom: camss: Add PM clock definitions for QCM2290
media: qcom: camss: Drop top_ahb/axi from QCM2290 subdevice clocks
media: qcom: camss: Add camss-isp-bufq helper
media: qcom: camss: Add camss-isp-sched helper
media: qcom: camss: Add camss-isp-pipeline helper
media: qcom-camss: Add image formats for Qualcomm CAMSS parameters buffer
media: qcom: camss: Add camss-isp-params helper
dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE)
dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode
media: uapi: Add CAMSS ISP configuration definition
media: qcom: camss: Add CAMSS Offline Processing Engine driver
arm64: dts: qcom: agatti: Add OPE node
.../bindings/media/qcom,qcm2290-camss-ope.yaml | 131 ++
.../bindings/media/qcom,qcm2290-camss.yaml | 13 +
arch/arm64/boot/dts/qcom/agatti.dtsi | 71 +
drivers/media/platform/qcom/camss/Kconfig | 32 +
drivers/media/platform/qcom/camss/Makefile | 11 +
drivers/media/platform/qcom/camss/camss-isp-bufq.c | 101 +
drivers/media/platform/qcom/camss/camss-isp-bufq.h | 85 +
drivers/media/platform/qcom/camss/camss-isp-ope.c | 2466 ++++++++++++++++++++
.../media/platform/qcom/camss/camss-isp-params.c | 67 +
.../media/platform/qcom/camss/camss-isp-params.h | 62 +
.../media/platform/qcom/camss/camss-isp-pipeline.c | 361 +++
.../media/platform/qcom/camss/camss-isp-pipeline.h | 228 ++
.../media/platform/qcom/camss/camss-isp-sched.c | 223 ++
.../media/platform/qcom/camss/camss-isp-sched.h | 174 ++
drivers/media/platform/qcom/camss/camss.c | 80 +-
drivers/media/platform/qcom/camss/camss.h | 6 +
drivers/media/v4l2-core/v4l2-ioctl.c | 1 +
include/uapi/linux/camss-config.h | 118 +
include/uapi/linux/videodev2.h | 3 +
19 files changed, 4217 insertions(+), 16 deletions(-)
---
base-commit: 143efe4d18f3e221bf6e9c6476296b3dd53de032
change-id: 20260427-camss-isp-ope-4b575ffec2ed
Best regards,
--
Loic Poulain <loic.poulain@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread* [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-28 6:43 ` Krzysztof Kozlowski 2026-04-27 12:43 ` [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM Loic Poulain ` (12 subsequent siblings) 13 siblings, 1 reply; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham From: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Use devm_of_platform_populate() to populate subs in the tree. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/camss.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 36c601c595053ddad8d327b1416d7ff587920174..8f2b1d3cd9f289895aa439443d2a18bb036fccde 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -16,6 +16,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_graph.h> +#include <linux/of_platform.h> #include <linux/pm_runtime.h> #include <linux/pm_domain.h> #include <linux/slab.h> @@ -4608,6 +4609,8 @@ static int camss_probe(struct platform_device *pdev) if (!camss) return -ENOMEM; + devm_of_platform_populate(dev); + camss->res = of_device_get_match_data(dev); atomic_set(&camss->ref_count, 0); -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices 2026-04-27 12:43 ` [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices Loic Poulain @ 2026-04-28 6:43 ` Krzysztof Kozlowski 2026-04-28 11:41 ` Loic Poulain 0 siblings, 1 reply; 33+ messages in thread From: Krzysztof Kozlowski @ 2026-04-28 6:43 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 27/04/2026 14:43, Loic Poulain wrote: > From: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > > Use devm_of_platform_populate() to populate subs in the tree. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com> There are no children in DTS and ABI does not allow them, so this is not really correct or you just missed proper order - bindings describing the ABI changes should be always before the actual change implementing it. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices 2026-04-28 6:43 ` Krzysztof Kozlowski @ 2026-04-28 11:41 ` Loic Poulain 0 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-28 11:41 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Tue, Apr 28, 2026 at 8:43 AM Krzysztof Kozlowski <krzk@kernel.org> wrote: > > On 27/04/2026 14:43, Loic Poulain wrote: > > From: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > > > > Use devm_of_platform_populate() to populate subs in the tree. > > > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > > Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > There are no children in DTS and ABI does not allow them, so this is not > really correct or you just missed proper order - bindings describing the > ABI changes should be always before the actual change implementing it. Right, this is a dependency that I cherry-picked from a previous Bryan’s series. I will make sure to place it correctly, or explicitly document the series dependency in the next revision. ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain 2026-04-27 12:43 ` [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 14:04 ` Konrad Dybcio 2026-04-27 12:43 ` [PATCH v2 03/14] media: qcom: camss: Add PM clock definitions for QCM2290 Loic Poulain ` (11 subsequent siblings) 13 siblings, 1 reply; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add optional PM clock support to the CAMSS driver using the PM clock framework. This allows CAMSS clocks to be registered once and automatically managed during runtime suspend and resume. This is especially useful for global CAMSS clocks that are shared across multiple CAMSS subnodes. Now that CAMSS is modeled as a simple-bus, these clocks are automatically enabled whenever a child node becomes active. This avoids the need for each subdevice to reference and manage the shared clocks individually. A typical example is the set of clocks in the top_group, which may be used by CSID, PHY, CCI, OPE, and other CAMSS blocks. Introduce a small PM clock descriptor table in the CAMSS resources structure to describe clocks and their optional rates. Initialize these clocks at probe time and delegate clock ownership to the PM core. Hook PM clock handling into the runtime PM callbacks to ensure clocks are properly suspended and resumed alongside power domains and ICC paths. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/camss.c | 54 ++++++++++++++++++++++++++++++- drivers/media/platform/qcom/camss/camss.h | 6 ++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 8f2b1d3cd9f289895aa439443d2a18bb036fccde..ca68ad7fc9ff30eae23d3baf34cf1ca642acf9d7 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -19,6 +19,7 @@ #include <linux/of_platform.h> #include <linux/pm_runtime.h> #include <linux/pm_domain.h> +#include <linux/pm_clock.h> #include <linux/slab.h> #include <linux/videodev2.h> @@ -4593,6 +4594,49 @@ static void camss_genpd_cleanup(struct camss *camss) dev_pm_domain_detach(camss->genpd, true); } +static int camss_init_pm_clks(struct camss *camss) +{ + struct device *dev = camss->dev; + unsigned int i; + int ret; + + if (!camss->res->pm_clks[0].name) + return 0; + + ret = devm_pm_clk_create(dev); + if (ret) + return ret; + + for (i = 0; i < CAMSS_RES_MAX && camss->res->pm_clks[i].name; i++) { + const struct camss_pm_clk *entry = &camss->res->pm_clks[i]; + struct clk *clk; + + clk = clk_get(dev, entry->name); + if (IS_ERR(clk)) { + dev_warn(dev, "failed to get pm_clk %s: %pe\n", + entry->name, clk); + continue; + } + + if (entry->rate) { + ret = clk_set_rate(clk, entry->rate); + if (ret) + dev_warn(dev, "failed to set rate for pm_clk %s: %d\n", + entry->name, ret); + } + + /* PM takes ownership of the clock, no explicit clk_put() is required. */ + ret = pm_clk_add_clk(dev, clk); + if (ret) { + dev_warn(dev, "failed to add pm_clk %s: %d\n", + entry->name, ret); + clk_put(clk); + } + } + + return 0; +} + /* * camss_probe - Probe CAMSS platform device * @pdev: Pointer to CAMSS platform device @@ -4677,6 +4721,10 @@ static int camss_probe(struct platform_device *pdev) pm_runtime_enable(dev); + ret = camss_init_pm_clks(camss); + if (ret) + goto err_v4l2_device_unregister; + ret = camss_of_parse_ports(camss); if (ret < 0) goto err_v4l2_device_unregister; @@ -4984,7 +5032,7 @@ static int __maybe_unused camss_runtime_suspend(struct device *dev) return ret; } - return 0; + return pm_clk_suspend(dev); } static int __maybe_unused camss_runtime_resume(struct device *dev) @@ -4994,6 +5042,10 @@ static int __maybe_unused camss_runtime_resume(struct device *dev) int i; int ret; + ret = pm_clk_resume(dev); + if (ret) + return ret; + for (i = 0; i < camss->res->icc_path_num; i++) { ret = icc_set_bw(camss->icc_path[i], icc_res[i].icc_bw_tbl.avg, diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..bd5e572f0a0a7daa1668831b7d2fc60e0498200d 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -100,9 +100,15 @@ enum icc_count { ICC_SM8250_COUNT = 4, }; +struct camss_pm_clk { + const char *name; + unsigned long rate; /* 0 = do not set rate */ +}; + struct camss_resources { enum camss_version version; const char *pd_name; + struct camss_pm_clk pm_clks[CAMSS_RES_MAX]; const struct camss_subdev_resources *csiphy_res; const struct camss_subdev_resources *csid_res; const struct camss_subdev_resources *ispif_res; -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM 2026-04-27 12:43 ` [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM Loic Poulain @ 2026-04-27 14:04 ` Konrad Dybcio 0 siblings, 0 replies; 33+ messages in thread From: Konrad Dybcio @ 2026-04-27 14:04 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 4/27/26 2:43 PM, Loic Poulain wrote: > Add optional PM clock support to the CAMSS driver using the PM clock > framework. This allows CAMSS clocks to be registered once and > automatically managed during runtime suspend and resume. > > This is especially useful for global CAMSS clocks that are shared across > multiple CAMSS subnodes. Now that CAMSS is modeled as a simple-bus, > these clocks are automatically enabled whenever a child node becomes > active. > > This avoids the need for each subdevice to reference and manage the > shared clocks individually. A typical example is the set of clocks in > the top_group, which may be used by CSID, PHY, CCI, OPE, and other > CAMSS blocks. > > Introduce a small PM clock descriptor table in the CAMSS resources > structure to describe clocks and their optional rates. Initialize > these clocks at probe time and delegate clock ownership to the PM > core. > > Hook PM clock handling into the runtime PM callbacks to ensure clocks > are properly suspended and resumed alongside power domains and ICC > paths. > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- [...] > + for (i = 0; i < CAMSS_RES_MAX && camss->res->pm_clks[i].name; i++) { > + const struct camss_pm_clk *entry = &camss->res->pm_clks[i]; > + struct clk *clk; > + > + clk = clk_get(dev, entry->name); > + if (IS_ERR(clk)) { > + dev_warn(dev, "failed to get pm_clk %s: %pe\n", > + entry->name, clk); > + continue; > + } > + > + if (entry->rate) { > + ret = clk_set_rate(clk, entry->rate); > + if (ret) > + dev_warn(dev, "failed to set rate for pm_clk %s: %d\n", > + entry->name, ret); > + } So this makes a couple fragile assumptions: * there's only one "on/operational" rate * no OPP votes I would imagine that in the camss-is-the-bus model, the top-level device would house an OPP table.. but we have two somewhat independent clocks that may possibly have separate RPMH requirements for their M/N number of rates, which could result in M*N-long opp table Konrad ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 03/14] media: qcom: camss: Add PM clock definitions for QCM2290 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain 2026-04-27 12:43 ` [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices Loic Poulain 2026-04-27 12:43 ` [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 04/14] media: qcom: camss: Drop top_ahb/axi from QCM2290 subdevice clocks Loic Poulain ` (10 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Provide the required CAMSS PM clock descriptors for the QCM2290 platform. Register the top_ahb and axi clocks with their appropriate nominal rates so they can be managed automatically through the PM clock framework (both are part of the camss-top group). Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/camss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index ca68ad7fc9ff30eae23d3baf34cf1ca642acf9d7..b2b0afc3fb5f597622f9b4ebfee2ec6703bf0890 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4846,6 +4846,7 @@ static const struct camss_resources msm8996_resources = { static const struct camss_resources qcm2290_resources = { .version = CAMSS_2290, + .pm_clks = { { "top_ahb", 80000000 }, { "axi", 300000000 } }, .csiphy_res = csiphy_res_2290, .csid_res = csid_res_2290, .vfe_res = vfe_res_2290, -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 04/14] media: qcom: camss: Drop top_ahb/axi from QCM2290 subdevice clocks 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (2 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 03/14] media: qcom: camss: Add PM clock definitions for QCM2290 Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper Loic Poulain ` (9 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Remove the top_ahb/axi clocks from QCM2290 subdevice clock lists. These clocks are now handled centrally as a global CAMSS PM clock and are automatically enabled when any CAMSS child is active. This avoids redundant clock references in individual subdevices and ensures consistent clock management across the CAMSS pipeline. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/camss.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index b2b0afc3fb5f597622f9b4ebfee2ec6703bf0890..1fa42565c28a8cd461771821518875a8f468834c 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -664,9 +664,8 @@ static const struct camss_subdev_resources csiphy_res_2290[] = { /* CSIPHY0 */ { .regulators = { "vdd-csiphy-1p2", "vdd-csiphy-1p8" }, - .clock = { "top_ahb", "ahb", "csiphy0", "csiphy0_timer" }, + .clock = { "ahb", "csiphy0", "csiphy0_timer" }, .clock_rate = { { 0 }, - { 0 }, { 240000000, 341330000, 384000000 }, { 100000000, 200000000, 268800000 } }, .reg = { "csiphy0" }, @@ -681,9 +680,8 @@ static const struct camss_subdev_resources csiphy_res_2290[] = { /* CSIPHY1 */ { .regulators = { "vdd-csiphy-1p2", "vdd-csiphy-1p8" }, - .clock = { "top_ahb", "ahb", "csiphy1", "csiphy1_timer" }, + .clock = { "ahb", "csiphy1", "csiphy1_timer" }, .clock_rate = { { 0 }, - { 0 }, { 240000000, 341330000, 384000000 }, { 100000000, 200000000, 268800000 } }, .reg = { "csiphy1" }, @@ -700,9 +698,8 @@ static const struct camss_subdev_resources csid_res_2290[] = { /* CSID0 */ { .regulators = {}, - .clock = { "top_ahb", "ahb", "csi0", "vfe0_cphy_rx", "vfe0" }, + .clock = { "ahb", "csi0", "vfe0_cphy_rx", "vfe0" }, .clock_rate = { { 0 }, - { 0 }, { 192000000, 240000000, 384000000, 426400000 }, { 0 }, { 0 } }, @@ -718,9 +715,8 @@ static const struct camss_subdev_resources csid_res_2290[] = { /* CSID1 */ { .regulators = {}, - .clock = { "top_ahb", "ahb", "csi1", "vfe1_cphy_rx", "vfe1" }, - .clock_rate = { { 0 }, - { 0 }, + .clock = { "ahb", "csi1", "vfe1_cphy_rx", "vfe1" }, + .clock_rate = { { 0 }, { 192000000, 240000000, 384000000, 426400000 }, { 0 }, { 0 } }, @@ -738,10 +734,8 @@ static const struct camss_subdev_resources vfe_res_2290[] = { /* VFE0 */ { .regulators = {}, - .clock = { "top_ahb", "ahb", "axi", "vfe0", "camnoc_rt_axi", "camnoc_nrt_axi" }, + .clock = { "ahb", "vfe0", "camnoc_rt_axi", "camnoc_nrt_axi" }, .clock_rate = { { 0 }, - { 0 }, - { 0 }, { 19200000, 153600000, 192000000, 256000000, 384000000, 460800000 }, { 0 }, { 0 }, }, @@ -758,10 +752,8 @@ static const struct camss_subdev_resources vfe_res_2290[] = { /* VFE1 */ { .regulators = {}, - .clock = { "top_ahb", "ahb", "axi", "vfe1", "camnoc_rt_axi", "camnoc_nrt_axi" }, + .clock = { "ahb", "vfe1", "camnoc_rt_axi", "camnoc_nrt_axi" }, .clock_rate = { { 0 }, - { 0 }, - { 0 }, { 19200000, 153600000, 192000000, 256000000, 384000000, 460800000 }, { 0 }, { 0 }, }, -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (3 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 04/14] media: qcom: camss: Drop top_ahb/axi from QCM2290 subdevice clocks Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-28 6:46 ` Krzysztof Kozlowski 2026-04-28 6:56 ` Bryan O'Donoghue 2026-04-27 12:43 ` [PATCH v2 06/14] media: qcom: camss: Add camss-isp-sched helper Loic Poulain ` (8 subsequent siblings) 13 siblings, 2 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add a per-queue ready-buffer FIFO helper for CAMSS offline ISP drivers. camss_isp_bufq provides N spinlock-protected FIFO lists of ready vb2 buffers, one per queue index. This can help multi-queues management and synchronization in ISP context. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/Kconfig | 14 +++ drivers/media/platform/qcom/camss/Makefile | 5 + drivers/media/platform/qcom/camss/camss-isp-bufq.c | 101 +++++++++++++++++++++ drivers/media/platform/qcom/camss/camss-isp-bufq.h | 85 +++++++++++++++++ 4 files changed, 205 insertions(+) diff --git a/drivers/media/platform/qcom/camss/Kconfig b/drivers/media/platform/qcom/camss/Kconfig index 4eda48cb1adf049a7fb6cb59b9da3c0870fe57f4..d77482f3f5eadc65856806b9b237d65ea484f267 100644 --- a/drivers/media/platform/qcom/camss/Kconfig +++ b/drivers/media/platform/qcom/camss/Kconfig @@ -7,3 +7,17 @@ config VIDEO_QCOM_CAMSS select VIDEO_V4L2_SUBDEV_API select VIDEOBUF2_DMA_SG select V4L2_FWNODE + +config VIDEO_QCOM_CAMSS_ISP + tristate "Qualcomm CAMSS ISP common helpers" + depends on VIDEO_DEV + depends on MEDIA_CONTROLLER + select V4L2_ISP + select VIDEOBUF2_CORE + help + Common helper library for Qualcomm CAMSS offline ISP drivers. + Provides buffer queue management, job scheduling, MC pipeline + topology builder, and ISP parameter buffer parsing. + + This module is selected automatically by drivers that need it. + diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index 5e349b4915130c71dbff90e73102e46dfede1520..bfc05db0eada1d801839ceb8a3b157baae613053 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -29,3 +29,8 @@ qcom-camss-objs += \ camss-format.o \ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o + +qcom-camss-isp-objs := camss-isp-bufq.o + +obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o + diff --git a/drivers/media/platform/qcom/camss/camss-isp-bufq.c b/drivers/media/platform/qcom/camss/camss-isp-bufq.c new file mode 100644 index 0000000000000000000000000000000000000000..b1dcf60afcc63d112eee7bd143f08a7b4aac9a18 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-bufq.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-isp-bufq.c + * + * CAMSS ISP per-queue ready-buffer FIFO. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/module.h> +#include <linux/slab.h> + +#include "camss-isp-bufq.h" + +struct camss_isp_bufq *camss_isp_bufq_init(unsigned int num_queues) +{ + struct camss_isp_bufq *bufq; + unsigned int i; + + bufq = kzalloc(struct_size(bufq, entries, num_queues), GFP_KERNEL); + if (!bufq) + return ERR_PTR(-ENOMEM); + + bufq->num_queues = num_queues; + + for (i = 0; i < num_queues; i++) { + INIT_LIST_HEAD(&bufq->entries[i].rdy_queue); + spin_lock_init(&bufq->entries[i].rdy_spinlock); + } + + return bufq; +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_init); + +void camss_isp_bufq_release(struct camss_isp_bufq *bufq) +{ + kfree(bufq); +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_release); + +void camss_isp_bufq_queue(struct camss_isp_bufq *bufq, unsigned int queue_idx, + struct vb2_v4l2_buffer *vbuf) +{ + struct camss_isp_buf *buf = + container_of(vbuf, struct camss_isp_buf, vb); + struct camss_isp_bufq_entry *entry = &bufq->entries[queue_idx]; + unsigned long flags; + + spin_lock_irqsave(&entry->rdy_spinlock, flags); + list_add_tail(&buf->list, &entry->rdy_queue); + entry->num_rdy++; + spin_unlock_irqrestore(&entry->rdy_spinlock, flags); +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_queue); + +struct vb2_v4l2_buffer *camss_isp_bufq_next(struct camss_isp_bufq *bufq, unsigned int queue_idx) +{ + struct camss_isp_bufq_entry *entry = &bufq->entries[queue_idx]; + struct camss_isp_buf *buf; + unsigned long flags; + + spin_lock_irqsave(&entry->rdy_spinlock, flags); + buf = list_first_entry_or_null(&entry->rdy_queue, + struct camss_isp_buf, list); + spin_unlock_irqrestore(&entry->rdy_spinlock, flags); + + return buf ? &buf->vb : NULL; +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_next); + +struct vb2_v4l2_buffer *camss_isp_bufq_remove(struct camss_isp_bufq *bufq, unsigned int queue_idx) +{ + struct camss_isp_bufq_entry *entry = &bufq->entries[queue_idx]; + struct camss_isp_buf *buf; + unsigned long flags; + + spin_lock_irqsave(&entry->rdy_spinlock, flags); + buf = list_first_entry_or_null(&entry->rdy_queue, + struct camss_isp_buf, list); + if (buf) { + list_del(&buf->list); + entry->num_rdy--; + } + spin_unlock_irqrestore(&entry->rdy_spinlock, flags); + + return buf ? &buf->vb : NULL; +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_remove); + +void camss_isp_bufq_drain(struct camss_isp_bufq *bufq, unsigned int queue_idx, + enum vb2_buffer_state state) +{ + struct vb2_v4l2_buffer *vbuf; + + while ((vbuf = camss_isp_bufq_remove(bufq, queue_idx))) + camss_isp_buf_done(vbuf, state); +} +EXPORT_SYMBOL_GPL(camss_isp_bufq_drain); + +MODULE_DESCRIPTION("CAMSS ISP per-queue ready-buffer FIFO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/qcom/camss/camss-isp-bufq.h b/drivers/media/platform/qcom/camss/camss-isp-bufq.h new file mode 100644 index 0000000000000000000000000000000000000000..2f04e38cb1cd198f180d744b0b9869b6f2b8ef46 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-bufq.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-isp-bufq.h + * + * CAMSS ISP per-queue ready-buffer FIFO. + * + * Provides N spinlock-protected FIFO lists of ready vb2 buffers, one per + * queue index. Drivers call these helpers from their vb2 ops and job + * completion paths. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef CAMSS_ISP_BUFQ_H +#define CAMSS_ISP_BUFQ_H + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <media/videobuf2-v4l2.h> + +/** + * struct camss_isp_buf - vb2 buffer wrapper + * + * Use as vb2_queue.buf_struct_size so buffers can be placed on the + * ready lists managed by camss_isp_bufq. + * + * @vb: The vb2 V4L2 buffer — must be first. + * @list: Entry in the per-queue ready list. + */ +struct camss_isp_buf { + struct vb2_v4l2_buffer vb; /* must be first */ + struct list_head list; +}; + +/** + * struct camss_isp_bufq_entry - per-queue ready-buffer state (opaque) + */ +struct camss_isp_bufq_entry { + struct list_head rdy_queue; + spinlock_t rdy_spinlock; + u32 num_rdy; +}; + +/** + * struct camss_isp_bufq - multi-queue ready-buffer state + * + * Allocate with camss_isp_bufq_init(), free with camss_isp_bufq_release(). + * + * @num_queues: Number of entries in @entries. + * @entries: Per-queue state; flexible array. + */ +struct camss_isp_bufq { + unsigned int num_queues; + struct camss_isp_bufq_entry entries[] __counted_by(num_queues); +}; + +struct camss_isp_bufq *camss_isp_bufq_init(unsigned int num_queues); +void camss_isp_bufq_release(struct camss_isp_bufq *bufq); + +void camss_isp_bufq_queue(struct camss_isp_bufq *bufq, unsigned int queue_idx, + struct vb2_v4l2_buffer *vbuf); + +struct vb2_v4l2_buffer *camss_isp_bufq_next(struct camss_isp_bufq *bufq, + unsigned int queue_idx); + +struct vb2_v4l2_buffer *camss_isp_bufq_remove(struct camss_isp_bufq *bufq, + unsigned int queue_idx); + +void camss_isp_bufq_drain(struct camss_isp_bufq *bufq, unsigned int queue_idx, + enum vb2_buffer_state state); + +static inline u32 camss_isp_bufq_num_ready(struct camss_isp_bufq *bufq, + unsigned int queue_idx) +{ + return bufq->entries[queue_idx].num_rdy; +} + +static inline void camss_isp_buf_done(struct vb2_v4l2_buffer *vbuf, + enum vb2_buffer_state state) +{ + vb2_buffer_done(&vbuf->vb2_buf, state); +} + +#endif /* CAMSS_ISP_BUFQ_H */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper 2026-04-27 12:43 ` [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper Loic Poulain @ 2026-04-28 6:46 ` Krzysztof Kozlowski 2026-04-28 6:56 ` Bryan O'Donoghue 1 sibling, 0 replies; 33+ messages in thread From: Krzysztof Kozlowski @ 2026-04-28 6:46 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 27/04/2026 14:43, Loic Poulain wrote: > + > +struct camss_isp_bufq *camss_isp_bufq_init(unsigned int num_queues) > +{ > + struct camss_isp_bufq *bufq; > + unsigned int i; > + > + bufq = kzalloc(struct_size(bufq, entries, num_queues), GFP_KERNEL); > + if (!bufq) > + return ERR_PTR(-ENOMEM); > + > + bufq->num_queues = num_queues; > + > + for (i = 0; i < num_queues; i++) { > + INIT_LIST_HEAD(&bufq->entries[i].rdy_queue); > + spin_lock_init(&bufq->entries[i].rdy_spinlock); > + } > + > + return bufq; > +} > +EXPORT_SYMBOL_GPL(camss_isp_bufq_init); You need kerneldocs for all of exported functions. > + > +void camss_isp_bufq_release(struct camss_isp_bufq *bufq) > +{ > + kfree(bufq); > +} > +EXPORT_SYMBOL_GPL(camss_isp_bufq_release); > + Best regards, Krzysztof ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper 2026-04-27 12:43 ` [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper Loic Poulain 2026-04-28 6:46 ` Krzysztof Kozlowski @ 2026-04-28 6:56 ` Bryan O'Donoghue 2026-04-28 11:29 ` Loic Poulain 1 sibling, 1 reply; 33+ messages in thread From: Bryan O'Donoghue @ 2026-04-28 6:56 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 27/04/2026 13:43, Loic Poulain wrote: > Add a per-queue ready-buffer FIFO helper for CAMSS offline ISP drivers. > camss_isp_bufq provides N spinlock-protected FIFO lists of ready vb2 > buffers, one per queue index. This can help multi-queues management > and synchronization in ISP context. > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- > drivers/media/platform/qcom/camss/Kconfig | 14 +++ > drivers/media/platform/qcom/camss/Makefile | 5 + > drivers/media/platform/qcom/camss/camss-isp-bufq.c | 101 +++++++++++++++++++++ > drivers/media/platform/qcom/camss/camss-isp-bufq.h | 85 +++++++++++++++++ What is the purpose of this and the next three patches - why not just do it inside the OPE ? --- bod ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper 2026-04-28 6:56 ` Bryan O'Donoghue @ 2026-04-28 11:29 ` Loic Poulain 0 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-28 11:29 UTC (permalink / raw) To: Bryan O'Donoghue Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Tue, Apr 28, 2026 at 8:56 AM Bryan O'Donoghue <bod@kernel.org> wrote: > > On 27/04/2026 13:43, Loic Poulain wrote: > > Add a per-queue ready-buffer FIFO helper for CAMSS offline ISP drivers. > > camss_isp_bufq provides N spinlock-protected FIFO lists of ready vb2 > > buffers, one per queue index. This can help multi-queues management > > and synchronization in ISP context. > > > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > --- > > drivers/media/platform/qcom/camss/Kconfig | 14 +++ > > drivers/media/platform/qcom/camss/Makefile | 5 + > > drivers/media/platform/qcom/camss/camss-isp-bufq.c | 101 +++++++++++++++++++++ > > drivers/media/platform/qcom/camss/camss-isp-bufq.h | 85 +++++++++++++++++ > What is the purpose of this and the next three patches - why not just do > it inside the OPE ? The goal is to improve reusability and avoid an complicated/large OPE driver by moving non‑OPE‑specific logic out of the driver. This would allow it to serve as a common framework for both offline and inline CAMSS processing engines. Looking further ahead, some parts could be further generalized or refined to fit within the v4l2‑isp framework, although that is a longer‑term discussion. For now, since OPE is the only consumer, and to keep related logic localized within its respective files, should these components initially be built as part of the OPE module? They can then be moved out later if and when additional users appear. Regards, Loic ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 06/14] media: qcom: camss: Add camss-isp-sched helper 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (4 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 07/14] media: qcom: camss: Add camss-isp-pipeline helper Loic Poulain ` (7 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add a job scheduler for CAMSS offline ISP drivers which serialises job execution, tracks which context is currently running on hardware, and provides cancel/suspend/resume operations. Jobs carry optional ready/run/abort callbacks via camss_isp_job_ops, allowing the scheduler to gate submission on hardware and buffer availability. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/Makefile | 3 +- .../media/platform/qcom/camss/camss-isp-sched.c | 223 +++++++++++++++++++++ .../media/platform/qcom/camss/camss-isp-sched.h | 174 ++++++++++++++++ 3 files changed, 399 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index bfc05db0eada1d801839ceb8a3b157baae613053..f13c9f326cf81962bd165dc8dd2bb60207cd54a7 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -30,7 +30,8 @@ qcom-camss-objs += \ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o -qcom-camss-isp-objs := camss-isp-bufq.o +qcom-camss-isp-objs := camss-isp-bufq.o \ + camss-isp-sched.o obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o diff --git a/drivers/media/platform/qcom/camss/camss-isp-sched.c b/drivers/media/platform/qcom/camss/camss-isp-sched.c new file mode 100644 index 0000000000000000000000000000000000000000..6940087f94d00570a82666e882ffc8b38891736b --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-sched.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CAMSS ISP scheduler helper — ISP job scheduling + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/slab.h> +#include <linux/module.h> + +#include "camss-isp-sched.h" + +/* Job state flags */ +#define ISP_JOB_QUEUED BIT(0) +#define ISP_JOB_RUNNING BIT(1) +#define ISP_JOB_ABORT BIT(2) + +/* Scheduler flags */ +#define ISP_SCHED_PAUSED BIT(0) + +/* -------- Internal helpers -------- */ + +static void isp_sched_try_run(struct camss_isp_sched *sched) +{ + void (*run_fn)(void *priv, bool ctx_changed); + struct camss_isp_job *job; + unsigned long flags; + bool ctx_changed; + void *priv; + + spin_lock_irqsave(&sched->lock, flags); + + if (sched->curr_job || list_empty(&sched->pending) || + (sched->flags & ISP_SCHED_PAUSED)) { + spin_unlock_irqrestore(&sched->lock, flags); + return; + } + + job = list_first_entry(&sched->pending, struct camss_isp_job, queue); + job->flags |= ISP_JOB_RUNNING; + sched->curr_job = job; + run_fn = job->ops ? job->ops->run : NULL; + priv = job->priv; + ctx_changed = (sched->prev_job != job); + + spin_unlock_irqrestore(&sched->lock, flags); + + run_fn(priv, ctx_changed); + sched->prev_job = job; +} + +static void isp_sched_work(struct work_struct *work) +{ + struct camss_isp_sched *sched = + container_of(work, struct camss_isp_sched, work); + + isp_sched_try_run(sched); +} + +/* -------- Public API -------- */ + +void camss_isp_sched_init(struct camss_isp_sched *sched) +{ + sched->curr_job = NULL; + sched->prev_job = NULL; + INIT_LIST_HEAD(&sched->pending); + spin_lock_init(&sched->lock); + INIT_WORK(&sched->work, isp_sched_work); + sched->flags = 0; +} +EXPORT_SYMBOL_GPL(camss_isp_sched_init); + +void camss_isp_sched_destroy(struct camss_isp_sched *sched) +{ + cancel_work_sync(&sched->work); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_destroy); + +void camss_isp_job_init(struct camss_isp_job *job, + const struct camss_isp_job_ops *ops, + void *priv) +{ + INIT_LIST_HEAD(&job->queue); + job->flags = 0; + job->ops = ops; + job->priv = priv; + init_waitqueue_head(&job->finished); +} +EXPORT_SYMBOL_GPL(camss_isp_job_init); + +void camss_isp_sched_try_run(struct camss_isp_sched *sched, + struct camss_isp_job *job) +{ + unsigned long flags; + + if (job->ops && job->ops->ready && !job->ops->ready(job->priv)) + return; + + spin_lock_irqsave(&sched->lock, flags); + + if (job->flags & (ISP_JOB_ABORT | ISP_JOB_QUEUED | ISP_JOB_RUNNING)) { + spin_unlock_irqrestore(&sched->lock, flags); + return; + } + + list_add_tail(&job->queue, &sched->pending); + job->flags |= ISP_JOB_QUEUED; + + spin_unlock_irqrestore(&sched->lock, flags); + + isp_sched_try_run(sched); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_try_run); + +void camss_isp_sched_job_finish(struct camss_isp_sched *sched, + struct camss_isp_job *job, + bool requeue) +{ + unsigned long flags; + + spin_lock_irqsave(&sched->lock, flags); + + if (sched->curr_job != job) { + /* + * curr_job may have been cleared by a racing cancel/streamoff. + * If this job is still marked RUNNING, clear it and wake any + * waiter in camss_isp_sched_cancel() so it can unblock. + */ + if (job->flags & ISP_JOB_RUNNING) { + job->flags &= ~(ISP_JOB_QUEUED | ISP_JOB_RUNNING); + wake_up(&job->finished); + } + spin_unlock_irqrestore(&sched->lock, flags); + return; + } + + list_del(&job->queue); + job->flags &= ~(ISP_JOB_QUEUED | ISP_JOB_RUNNING); + wake_up(&job->finished); + sched->curr_job = NULL; + + if (requeue && !(job->flags & ISP_JOB_ABORT)) { + job->flags |= ISP_JOB_QUEUED; + list_add(&job->queue, &sched->pending); + } + + spin_unlock_irqrestore(&sched->lock, flags); + + schedule_work(&sched->work); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_job_finish); + +void camss_isp_sched_cancel(struct camss_isp_sched *sched, + struct camss_isp_job *job) +{ + unsigned long flags; + + spin_lock_irqsave(&sched->lock, flags); + job->flags |= ISP_JOB_ABORT; + + if (job->flags & ISP_JOB_RUNNING) { + spin_unlock_irqrestore(&sched->lock, flags); + if (job->ops && job->ops->abort) + job->ops->abort(job->priv); + wait_event(job->finished, !(job->flags & ISP_JOB_RUNNING)); + } else if (job->flags & ISP_JOB_QUEUED) { + list_del(&job->queue); + job->flags &= ~(ISP_JOB_QUEUED | ISP_JOB_RUNNING); + spin_unlock_irqrestore(&sched->lock, flags); + } else { + spin_unlock_irqrestore(&sched->lock, flags); + } + + /* Clear abort flag so the job can be reused after cancel */ + spin_lock_irqsave(&sched->lock, flags); + job->flags &= ~ISP_JOB_ABORT; + spin_unlock_irqrestore(&sched->lock, flags); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_cancel); + +void camss_isp_sched_suspend(struct camss_isp_sched *sched) +{ + struct camss_isp_job *curr; + unsigned long flags; + + spin_lock_irqsave(&sched->lock, flags); + sched->flags |= ISP_SCHED_PAUSED; + curr = sched->curr_job; + spin_unlock_irqrestore(&sched->lock, flags); + + if (curr) + wait_event(curr->finished, !(curr->flags & ISP_JOB_RUNNING)); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_suspend); + +void camss_isp_sched_resume(struct camss_isp_sched *sched) +{ + unsigned long flags; + + spin_lock_irqsave(&sched->lock, flags); + sched->flags &= ~ISP_SCHED_PAUSED; + spin_unlock_irqrestore(&sched->lock, flags); + + isp_sched_try_run(sched); +} +EXPORT_SYMBOL_GPL(camss_isp_sched_resume); + +bool camss_isp_sched_is_running(struct camss_isp_sched *sched, + struct camss_isp_job *job) +{ + unsigned long flags; + bool running; + + spin_lock_irqsave(&sched->lock, flags); + running = (sched->curr_job == job); + spin_unlock_irqrestore(&sched->lock, flags); + + return running; +} +EXPORT_SYMBOL_GPL(camss_isp_sched_is_running); + +MODULE_DESCRIPTION("CAMSS ISP job scheduler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/qcom/camss/camss-isp-sched.h b/drivers/media/platform/qcom/camss/camss-isp-sched.h new file mode 100644 index 0000000000000000000000000000000000000000..5b6034976de65be57581ccaa92d1f15d7cb4a688 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-sched.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * CAMSS ISP scheduler helper — ISP job scheduling + * + * Tracks which context is currently running on the hardware and + * serialises job execution. This is a pure helper: it has no knowledge + * of buffers, vb2 queues, or the uAPI. Drivers call these functions + * explicitly from their own code paths. + * + * Usage pattern: + * - Embed struct camss_isp_sched in the driver's device struct. + * - Call camss_isp_sched_init() at probe time. + * - Call camss_isp_job_init() with ready_fn/run_fn/abort_fn/priv. + * - Call camss_isp_sched_try_run() from buf_queue / streamon to start jobs. + * - Call camss_isp_sched_job_finish() from the IRQ handler when done. + * - Call camss_isp_sched_cancel() from streamoff / release. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _CAMSS_ISP_SCHED_H +#define _CAMSS_ISP_SCHED_H + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/workqueue.h> + +/** + * struct camss_isp_job_ops - per-job operation callbacks + * + * @ready: Optional; return %true if the job can be submitted to hardware. + * Called outside the scheduler spinlock. May be NULL (always ready). + * @run: Start the hardware for this job. Called from workqueue context. + * @ctx_changed is %true when this job differs from the previously + * run job (i.e. first run ever, or a different context took over). + * @abort: Optional; abort a running job (e.g. trigger a HW reset). + * Called from process context during camss_isp_sched_cancel(). + * May be NULL. + */ +struct camss_isp_job_ops { + bool (*ready)(void *priv); + void (*run)(void *priv, bool ctx_changed); + void (*abort)(void *priv); +}; + +/** + * struct camss_isp_job - per-context scheduler state + * + * Embed one of these in the driver's per-context struct. + * Initialise with camss_isp_job_init(). + * + * @queue: Entry in the scheduler's pending-job list. + * @flags: Internal state flags (ISP_JOB_*). + * @finished: Wait queue signalled when the running job completes. + * @ops: Job operation callbacks (ready/run/abort). + * @priv: Opaque pointer passed to all callbacks. + */ +struct camss_isp_job { + struct list_head queue; + unsigned long flags; + wait_queue_head_t finished; + const struct camss_isp_job_ops *ops; + void *priv; +}; + +/** + * struct camss_isp_sched - ISP job scheduler + * + * Embed one of these in the driver's device struct. + * Initialise with camss_isp_sched_init(). + * + * @curr_job: Job currently running on hardware (NULL if idle). + * @prev_job: Job that ran most recently (never dereferenced, pointer only). + * @pending: List of jobs waiting to run. + * @lock: Protects @curr_job, @pending, and @flags. + * @work: Work item used to run jobs from non-atomic context. + * @flags: Scheduler-level flags (ISP_SCHED_PAUSED). + */ +struct camss_isp_sched { + struct camss_isp_job *curr_job; + struct camss_isp_job *prev_job; + struct list_head pending; + spinlock_t lock; + struct work_struct work; + unsigned long flags; +}; + +/** + * camss_isp_sched_init() - initialise a scheduler + * @sched: scheduler to initialise + */ +void camss_isp_sched_init(struct camss_isp_sched *sched); + +/** + * camss_isp_sched_destroy() - destroy a scheduler (waits for any running job) + * @sched: scheduler to destroy + */ +void camss_isp_sched_destroy(struct camss_isp_sched *sched); + +/** + * camss_isp_job_init() - initialise per-context job state + * @job: job to initialise + * @ops: operation callbacks (run is required; ready and abort may be NULL) + * @priv: opaque pointer passed to all callbacks + */ +void camss_isp_job_init(struct camss_isp_job *job, + const struct camss_isp_job_ops *ops, + void *priv); + +/** + * camss_isp_sched_try_run() - enqueue a job and try to start it + * @sched: scheduler + * @job: job to enqueue; callbacks and @priv are taken from the job. + * + * Calls @job->ready_fn (if set); returns immediately if it returns %false. + * Otherwise enqueues the job and starts it if the hardware is idle. + * Safe to call from atomic context. + */ +void camss_isp_sched_try_run(struct camss_isp_sched *sched, + struct camss_isp_job *job); + +/** + * camss_isp_sched_job_finish() - signal that the current job has completed + * @sched: scheduler + * @job: job that just finished (must be the currently running job) + * @requeue: if %true and the job's ready_fn passes, immediately re-enqueue + * the job so the next frame starts as soon as the workqueue runs. + * + * Clears the running state, wakes any waiter in camss_isp_sched_cancel(), + * and schedules the next pending job via the work queue. + * Safe to call from atomic/IRQ context. + */ +void camss_isp_sched_job_finish(struct camss_isp_sched *sched, + struct camss_isp_job *job, + bool requeue); + +/** + * camss_isp_sched_cancel() - cancel a pending or running job and wait + * @sched: scheduler + * @job: job to cancel; @job->abort_fn is called if the job is running. + * + * If the job is queued but not yet running, it is simply removed. + * If the job is running, @job->abort_fn is called (if set) and the + * function blocks until camss_isp_sched_job_finish() is called. + * Must be called from process context (may sleep). + */ +void camss_isp_sched_cancel(struct camss_isp_sched *sched, + struct camss_isp_job *job); + +/** + * camss_isp_sched_suspend() - pause the scheduler and wait for current job + * @sched: scheduler + * + * No new jobs will be started until camss_isp_sched_resume() is called. + * Blocks until any currently running job finishes. + */ +void camss_isp_sched_suspend(struct camss_isp_sched *sched); + +/** + * camss_isp_sched_resume() - resume the scheduler + * @sched: scheduler + */ +void camss_isp_sched_resume(struct camss_isp_sched *sched); + +/** + * camss_isp_sched_is_running() - check if a job is currently running + * @sched: scheduler + * @job: job to check + */ +bool camss_isp_sched_is_running(struct camss_isp_sched *sched, + struct camss_isp_job *job); + +#endif /* _CAMSS_ISP_SCHED_H */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 07/14] media: qcom: camss: Add camss-isp-pipeline helper 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (5 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 06/14] media: qcom: camss: Add camss-isp-sched helper Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 08/14] media: qcom-camss: Add image formats for Qualcomm CAMSS parameters buffer Loic Poulain ` (6 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add a declarative MC topology builder for CAMSS offline ISP drivers. Drivers describe their entire media graph, entities (video devices, subdevs, or base entities), their pads, and the links between them in a static descriptor table. The builder validates the table, allocates and registers all entities, and creates all MC pad links. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/Makefile | 3 +- .../media/platform/qcom/camss/camss-isp-pipeline.c | 361 +++++++++++++++++++++ .../media/platform/qcom/camss/camss-isp-pipeline.h | 228 +++++++++++++ 3 files changed, 591 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index f13c9f326cf81962bd165dc8dd2bb60207cd54a7..f3acb1b54b6c1455d72e2d947c860f0c337648de 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -31,7 +31,8 @@ qcom-camss-objs += \ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o qcom-camss-isp-objs := camss-isp-bufq.o \ - camss-isp-sched.o + camss-isp-sched.o \ + camss-isp-pipeline.o obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o diff --git a/drivers/media/platform/qcom/camss/camss-isp-pipeline.c b/drivers/media/platform/qcom/camss/camss-isp-pipeline.c new file mode 100644 index 0000000000000000000000000000000000000000..8e44bedb0a41e3cf4fc7e3a138c1f48854f5efc8 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-pipeline.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CAMSS ISP pipeline helper — declarative MC topology builder + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/slab.h> + +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include "camss-isp-pipeline.h" + +#if !IS_ENABLED(CONFIG_MEDIA_CONTROLLER) +static inline int media_entity_pads_init(struct media_entity *e, u16 n, + struct media_pad *p) { return 0; } +static inline void media_entity_remove_links(struct media_entity *e) {} +static inline int media_create_pad_link(struct media_entity *src, u16 sp, + struct media_entity *sink, u16 dp, + u32 flags) { return 0; } +#endif + +/* -------- Internal elpers -------- */ + +static enum vfl_devnode_direction isp_caps_to_vfl_dir(u32 caps) +{ + if (caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE)) + return VFL_DIR_M2M; + if (caps & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_META_OUTPUT | V4L2_CAP_VBI_OUTPUT | V4L2_CAP_SDR_OUTPUT)) + return VFL_DIR_TX; + return VFL_DIR_RX; +} + +static unsigned int isp_count_pads(const struct camss_isp_pad_desc *pads) +{ + unsigned int n = 0; + + if (!pads) + return 0; + while (pads[n].flags) + n++; + return n; +} + +static struct media_entity *isp_pipeline_media_entity(struct camss_isp_pipeline *pipeline, + unsigned int idx) +{ + struct camss_isp_pipeline_entity *slot = &pipeline->entities[idx]; + + switch (slot->obj_type) { + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE: + return &slot->vdev.entity; + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV: + return &slot->subdev.entity; + default: + return &slot->entity; + } +} + +/* -------- Validation -------- */ + +static int isp_pipeline_validate(struct device *dev, + const struct camss_isp_entity_desc *descs, + unsigned int num_entities) +{ + unsigned int i, pi; + + for (i = 0; i < num_entities; i++) { + const struct camss_isp_pad_desc *pads = descs[i].pads; + unsigned int num_pads = isp_count_pads(pads); + + for (pi = 0; pi < num_pads; pi++) { + const struct camss_isp_pad_desc *pad = &pads[pi]; + const struct camss_isp_pad_desc *peer_pad; + unsigned int peer_num_pads; + int peer_ent = pad->peer_entity; + + if (peer_ent < 0) + continue; + + if ((unsigned int)peer_ent >= num_entities) { + dev_err(dev, "entity[%u].p%u: peer_entity %d out of range\n", + i, pi, peer_ent); + return -EINVAL; + } + + peer_num_pads = isp_count_pads(descs[peer_ent].pads); + if (pad->peer_pad >= peer_num_pads) { + dev_err(dev, "entity[%u].p%u: peer_pad %u out of range\n", + i, pi, pad->peer_pad); + return -EINVAL; + } + + peer_pad = &descs[peer_ent].pads[pad->peer_pad]; + + /* Links are SOURCE->SINK; reject SOURCE->SOURCE or SINK->SINK */ + if (((pad->flags & MEDIA_PAD_FL_SOURCE) && + (peer_pad->flags & MEDIA_PAD_FL_SOURCE)) || + ((pad->flags & MEDIA_PAD_FL_SINK) && + (peer_pad->flags & MEDIA_PAD_FL_SINK))) { + dev_err(dev, "entity[%u].p%u -> entity[%d].p%u: invalid\n", + i, pi, peer_ent, pad->peer_pad); + return -EINVAL; + } + + /* Verify back-reference consistency */ + if (peer_pad->peer_entity >= 0 && + ((unsigned int)peer_pad->peer_entity != i || + peer_pad->peer_pad != pi)) { + dev_err(dev, "entity[%u].p%u <-> entity[%d].p%u: mismatch\n", + i, pi, peer_ent, pad->peer_pad); + return -EINVAL; + } + } + } + + return 0; +} + +/* -------- Allocation / Release -------- */ + +struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities) +{ + struct camss_isp_pipeline *pipeline; + + pipeline = kzalloc(struct_size(pipeline, entities, num_entities), + GFP_KERNEL); + if (!pipeline) + return ERR_PTR(-ENOMEM); + + pipeline->num_entities = num_entities; + return pipeline; +} +EXPORT_SYMBOL_GPL(camss_isp_pipeline_alloc); + +void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline) +{ + kfree(pipeline); +} +EXPORT_SYMBOL_GPL(camss_isp_pipeline_free); + +/* -------- Registration -------- */ + +void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline) +{ + int i; + + /* Unregister entities in reverse order */ + for (i = (int)pipeline->num_entities - 1; i >= 0; i--) { + struct camss_isp_pipeline_entity *slot = &pipeline->entities[i]; + + switch (slot->obj_type) { + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE: + if (slot->vdev.name[0]) + video_unregister_device(&slot->vdev); + break; + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV: + if (slot->subdev.name[0]) + v4l2_device_unregister_subdev(&slot->subdev); + break; + case MEDIA_ENTITY_TYPE_BASE: + if (slot->entity.name) { + media_entity_remove_links(&slot->entity); + media_device_unregister_entity(&slot->entity); + } + break; + } + + kfree(slot->pads); + slot->pads = NULL; + } + + pipeline->v4l2_dev = NULL; +} +EXPORT_SYMBOL_GPL(camss_isp_pipeline_unregister); + +static int isp_register_vdev(struct camss_isp_pipeline_entity *slot, + const struct camss_isp_entity_desc *desc, + struct v4l2_device *v4l2_dev) +{ + struct video_device *vdev = &slot->vdev; + int ret; + + strscpy(vdev->name, desc->name, sizeof(vdev->name)); + vdev->vfl_dir = isp_caps_to_vfl_dir(desc->vdev.caps); + vdev->v4l2_dev = v4l2_dev; + vdev->device_caps = desc->vdev.caps; + vdev->release = video_device_release_empty; + if (desc->vdev.fops) + vdev->fops = desc->vdev.fops; + if (desc->vdev.ioctl_ops) + vdev->ioctl_ops = desc->vdev.ioctl_ops; + + vdev->entity.obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE; + vdev->entity.function = desc->function ? desc->function : MEDIA_ENT_F_IO_V4L; + + ret = media_entity_pads_init(&vdev->entity, slot->num_pads, slot->pads); + if (ret) + return ret; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) + return ret; + + video_set_drvdata(vdev, desc->vdev.drvdata); + + return 0; +} + +static int isp_register_subdev(struct camss_isp_pipeline_entity *slot, + const struct camss_isp_entity_desc *desc, + struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd = &slot->subdev; + int ret; + + v4l2_subdev_init(sd, desc->subdev.ops); + strscpy(sd->name, desc->name, sizeof(sd->name)); + sd->entity.function = desc->function ? + desc->function : MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; + + ret = media_entity_pads_init(&sd->entity, slot->num_pads, slot->pads); + if (ret) + return ret; + + return v4l2_device_register_subdev(v4l2_dev, sd); +} + +static int isp_register_base_entity(struct camss_isp_pipeline_entity *slot, + const struct camss_isp_entity_desc *desc, + struct v4l2_device *v4l2_dev) +{ + struct media_entity *entity = &slot->entity; + int ret; + + entity->obj_type = MEDIA_ENTITY_TYPE_BASE; + entity->name = desc->name; + entity->function = desc->function; + + ret = media_entity_pads_init(entity, slot->num_pads, slot->pads); + if (ret) + return ret; + + return media_device_register_entity(v4l2_dev->mdev, entity); +} + +static int isp_alloc_pads(struct camss_isp_pipeline_entity *slot, + const struct camss_isp_entity_desc *desc) +{ + unsigned int num_pads = isp_count_pads(desc->pads); + unsigned int i; + + if (!num_pads) + goto done; + + slot->pads = kcalloc(num_pads, sizeof(*slot->pads), GFP_KERNEL); + if (!slot->pads) + return -ENOMEM; + + for (i = 0; i < num_pads; i++) + slot->pads[i].flags = desc->pads[i].flags; +done: + slot->num_pads = num_pads; + return 0; +} + +int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline, + struct v4l2_device *v4l2_dev, + const struct camss_isp_entity_desc *descs, + unsigned int num_entities) +{ + unsigned int i, pi; + int ret; + + if (WARN_ON(num_entities != pipeline->num_entities)) + return -EINVAL; + + if (WARN_ON(!v4l2_dev || !v4l2_dev->mdev)) + return -EINVAL; + + ret = isp_pipeline_validate(v4l2_dev->dev, descs, num_entities); + if (ret) + return ret; + + pipeline->v4l2_dev = v4l2_dev; + + /* Register each entity */ + for (i = 0; i < num_entities; i++) { + const struct camss_isp_entity_desc *desc = &descs[i]; + struct camss_isp_pipeline_entity *slot = &pipeline->entities[i]; + + slot->obj_type = desc->obj_type; + + ret = isp_alloc_pads(slot, desc); + if (ret) + goto err_unregister; + + switch (desc->obj_type) { + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE: + ret = isp_register_vdev(slot, desc, v4l2_dev); + break; + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV: + ret = isp_register_subdev(slot, desc, v4l2_dev); + break; + case MEDIA_ENTITY_TYPE_BASE: + default: + ret = isp_register_base_entity(slot, desc, v4l2_dev); + break; + } + if (ret) + goto err_unregister; + } + + /* Create links — only from SOURCE side to avoid duplicates */ + for (i = 0; i < num_entities; i++) { + const struct camss_isp_entity_desc *desc = &descs[i]; + unsigned int num_pads = isp_count_pads(desc->pads); + + for (pi = 0; pi < num_pads; pi++) { + const struct camss_isp_pad_desc *pad = &desc->pads[pi]; + struct media_entity *src_entity, *sink_entity; + unsigned int src_pad_idx, sink_pad_idx; + u32 lflags; + + if (!(pad->flags & MEDIA_PAD_FL_SOURCE)) + continue; + if (pad->peer_entity < 0) + continue; + + src_entity = isp_pipeline_media_entity(pipeline, i); + sink_entity = isp_pipeline_media_entity(pipeline, + (unsigned int)pad->peer_entity); + src_pad_idx = pi; + sink_pad_idx = pad->peer_pad; + + lflags = pad->link_flags ? + pad->link_flags : + (MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + + ret = media_create_pad_link(src_entity, src_pad_idx, + sink_entity, sink_pad_idx, + lflags); + if (ret) + goto err_unregister; + } + } + + return 0; + +err_unregister: + camss_isp_pipeline_unregister(pipeline); + return ret; +} +EXPORT_SYMBOL_GPL(camss_isp_pipeline_register); + +MODULE_DESCRIPTION("CAMSS ISP pipeline topology builder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/qcom/camss/camss-isp-pipeline.h b/drivers/media/platform/qcom/camss/camss-isp-pipeline.h new file mode 100644 index 0000000000000000000000000000000000000000..5dfa32dcafc0a944ca2c160fb5846a2c73214acc --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-pipeline.h @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * CAMSS ISP pipeline helper — declarative MC topology builder + * + * Drivers describe their entire media graph — entities (video devices, + * subdevs, or base entities), their pads, and the links between them — + * in a single static descriptor table. The builder validates the table, + * allocates and registers all entities, and creates all MC links. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _CAMSS_ISP_PIPELINE_H +#define _CAMSS_ISP_PIPELINE_H + +#include <linux/mutex.h> +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +/** + * struct camss_isp_pad_desc - descriptor for one pad and its optional link + * + * @flags: Pad flags: MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + * MEDIA_PAD_FL_MUST_CONNECT. A zero @flags value acts as + * the sentinel that terminates the pad list. + * @peer_entity: Index of the peer entity in the descriptor array, or -1 + * if this pad has no link. + * @peer_pad: Pad index on the peer entity to link to. + * @link_flags: MC link flags (MEDIA_LNK_FL_*). Defaults to + * MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED when zero. + * + * Links are described from both sides (each endpoint references the other), + * but the builder only creates each link once — from the SOURCE side. + */ +struct camss_isp_pad_desc { + u32 flags; + int peer_entity; + unsigned int peer_pad; + u32 link_flags; +}; + +/** + * struct camss_isp_entity_desc - descriptor for one entity in the pipeline + * + * @name: Human-readable entity name (also used as video device name + * suffix when @obj_type is MEDIA_ENTITY_TYPE_VIDEO_DEVICE). + * @obj_type: MEDIA_ENTITY_TYPE_VIDEO_DEVICE, MEDIA_ENTITY_TYPE_V4L2_SUBDEV, + * or MEDIA_ENTITY_TYPE_BASE. + * @function: MEDIA_ENT_F_* function identifier. + * @pads: Sentinel-terminated (flags == 0) array of pad descriptors. + * + * Fields used only for MEDIA_ENTITY_TYPE_VIDEO_DEVICE: + * @vdev.caps: V4L2_CAP_* device capabilities. + * The video device direction (VFL_DIR_RX/TX/M2M) is derived + * automatically from @caps by the builder. + * @vdev.drvdata: Opaque pointer set via video_set_drvdata() after registration. + * @vdev.fops: File operations (may be NULL to use kernel defaults). + * @vdev.ioctl_ops: ioctl operations (may be NULL). + * + * Fields used only for MEDIA_ENTITY_TYPE_V4L2_SUBDEV: + * @subdev.ops: Subdev operations (may be NULL). + */ +struct camss_isp_entity_desc { + const char *name; + u32 obj_type; + u32 function; + const struct camss_isp_pad_desc *pads; + + union { + /* MEDIA_ENTITY_TYPE_VIDEO_DEVICE */ + struct { + u32 caps; + void *drvdata; + const struct v4l2_file_operations *fops; + const struct v4l2_ioctl_ops *ioctl_ops; + } vdev; + /* MEDIA_ENTITY_TYPE_V4L2_SUBDEV */ + struct { + const struct v4l2_subdev_ops *ops; + } subdev; + }; +}; + +/** + * struct camss_isp_pipeline_entity - one registered entity slot + * + * Internal to the pipeline; drivers access entities via the accessor helpers. + * + * @obj_type: mirrors the descriptor's @obj_type. + * @pads: allocated pad array for this entity. + * @num_pads: number of entries in @pads. + * @vdev: valid when @obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE. + * @subdev: valid when @obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV. + * @entity: valid when @obj_type == MEDIA_ENTITY_TYPE_BASE. + */ +struct camss_isp_pipeline_entity { + u32 obj_type; + struct media_pad *pads; + unsigned int num_pads; + union { + struct video_device vdev; + struct v4l2_subdev subdev; + struct media_entity entity; + }; +}; + +/** + * struct camss_isp_pipeline - registered ISP pipeline topology + * + * Allocate with camss_isp_pipeline_alloc(), register with + * camss_isp_pipeline_register(), tear down with + * camss_isp_pipeline_unregister(), free with camss_isp_pipeline_free(). + * + * @v4l2_dev: Pointer to the caller-provided V4L2 device. + * @drv_priv: Driver-private pointer; not touched by the framework. + * @num_entities: Number of entries in @entities. + * @entities: Per-entity state; flexible array. + */ +struct camss_isp_pipeline { + struct v4l2_device *v4l2_dev; + void *drv_priv; + + unsigned int num_entities; + struct camss_isp_pipeline_entity entities[] __counted_by(num_entities); +}; + +/** + * camss_isp_pipeline_alloc() - allocate a pipeline for @num_entities entities + * + * Returns a pointer to the new pipeline or ERR_PTR on failure. + * Free with camss_isp_pipeline_free() if never registered, or call + * camss_isp_pipeline_unregister() followed by camss_isp_pipeline_free(). + */ +struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities); + +/** + * camss_isp_pipeline_free() - free an unregistered pipeline + * @pipeline: pipeline to free (may be NULL) + */ +void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline); + +/** + * camss_isp_pipeline_register() - validate descriptors and register the graph + * @pipeline: pipeline (allocated with camss_isp_pipeline_alloc()) + * @v4l2_dev: caller-owned and already-registered V4L2 device; its + * associated media_device (v4l2_dev->mdev) must also be + * initialised and registered before this call. + * @descs: array of @num_entities entity descriptors + * @num_entities: number of entities; must equal pipeline->num_entities + * + * Validates the descriptor table (link direction consistency, index bounds), + * then registers all entities into the provided v4l2_device / media_device + * and creates all MC pad links. + * + * Returns 0 on success or a negative error code. + */ +int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline, + struct v4l2_device *v4l2_dev, + const struct camss_isp_entity_desc *descs, + unsigned int num_entities); + +/** + * camss_isp_pipeline_unregister() - tear down a registered pipeline + * @pipeline: pipeline to unregister + */ +void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline); + +/** + * camss_isp_pipeline_get_vdev() - return the video_device for entity @idx + * @pipeline: registered pipeline + * @idx: entity index (must be MEDIA_ENTITY_TYPE_VIDEO_DEVICE) + * + * Returns NULL if @idx is out of range or the entity is not a video device. + */ +static inline struct video_device * +camss_isp_pipeline_get_vdev(struct camss_isp_pipeline *pipeline, + unsigned int idx) +{ + if (WARN_ON(idx >= pipeline->num_entities)) + return NULL; + if (WARN_ON(pipeline->entities[idx].obj_type != + MEDIA_ENTITY_TYPE_VIDEO_DEVICE)) + return NULL; + return &pipeline->entities[idx].vdev; +} + +/** + * camss_isp_pipeline_get_subdev() - return the v4l2_subdev for entity @idx + * @pipeline: registered pipeline + * @idx: entity index (must be MEDIA_ENTITY_TYPE_V4L2_SUBDEV) + * + * Returns NULL if @idx is out of range or the entity is not a subdev. + */ +static inline struct v4l2_subdev * +camss_isp_pipeline_get_subdev(struct camss_isp_pipeline *pipeline, + unsigned int idx) +{ + if (WARN_ON(idx >= pipeline->num_entities)) + return NULL; + if (WARN_ON(pipeline->entities[idx].obj_type != + MEDIA_ENTITY_TYPE_V4L2_SUBDEV)) + return NULL; + return &pipeline->entities[idx].subdev; +} + +/** + * camss_isp_pipeline_get_entity() - return the media_entity for entity @idx + * @pipeline: registered pipeline + * @idx: entity index (must be MEDIA_ENTITY_TYPE_BASE) + * + * Returns NULL if @idx is out of range or the entity is not a base entity. + */ +static inline struct media_entity * +camss_isp_pipeline_get_entity(struct camss_isp_pipeline *pipeline, + unsigned int idx) +{ + if (WARN_ON(idx >= pipeline->num_entities)) + return NULL; + if (WARN_ON(pipeline->entities[idx].obj_type != + MEDIA_ENTITY_TYPE_BASE)) + return NULL; + return &pipeline->entities[idx].entity; +} + +#endif /* _CAMSS_ISP_PIPELINE_H */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 08/14] media: qcom-camss: Add image formats for Qualcomm CAMSS parameters buffer 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (6 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 07/14] media: qcom: camss: Add camss-isp-pipeline helper Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 09/14] media: qcom: camss: Add camss-isp-params helper Loic Poulain ` (5 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add a V4L2 meta format code for the Qualcomm CAMSS ISP parameters. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 98512ea4cc5b9d725e1851af2ed38df85bb4fa8c..7b6e9a9a514f037190d55d59409dd6cc97522943 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1471,6 +1471,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; case V4L2_META_FMT_MALI_C55_PARAMS: descr = "ARM Mali-C55 ISP Parameters"; break; case V4L2_META_FMT_MALI_C55_STATS: descr = "ARM Mali-C55 ISP 3A Statistics"; break; + case V4L2_META_FMT_QCOM_ISP_PARAMS: descr = "Qualcomm CAMSS ISP Parameters"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index add08188f06890182a5c399a223c1ab0a546cae1..f861211ebd7aefbcf4096885388069efed27ddc3 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -888,6 +888,9 @@ struct v4l2_pix_format { #define V4L2_META_FMT_MALI_C55_PARAMS v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */ #define V4L2_META_FMT_MALI_C55_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ +/* Vendor specific - used for Qualcomm CAMSS offline ISP */ +#define V4L2_META_FMT_QCOM_ISP_PARAMS v4l2_fourcc('Q', 'C', 'I', 'P') /* Qualcomm CAMSS ISP Parameters */ + #ifdef __KERNEL__ /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 09/14] media: qcom: camss: Add camss-isp-params helper 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (7 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 08/14] media: qcom-camss: Add image formats for Qualcomm CAMSS parameters buffer Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain ` (4 subsequent siblings) 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add an ISP parameter buffer parser for CAMSS offline ISP drivers. camss_isp_params_apply() wraps the upstream v4l2-isp buffer validation and adds a dispatch layer: after validation each block is forwarded to a driver-supplied handler indexed by block type. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/Makefile | 3 +- .../media/platform/qcom/camss/camss-isp-params.c | 67 ++++++++++++++++++++++ .../media/platform/qcom/camss/camss-isp-params.h | 62 ++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index f3acb1b54b6c1455d72e2d947c860f0c337648de..fba6f34b8d9f70ea258f7ae1a293a8d58d866498 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -32,7 +32,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o qcom-camss-isp-objs := camss-isp-bufq.o \ camss-isp-sched.o \ - camss-isp-pipeline.o + camss-isp-pipeline.o \ + camss-isp-params.o obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o diff --git a/drivers/media/platform/qcom/camss/camss-isp-params.c b/drivers/media/platform/qcom/camss/camss-isp-params.c new file mode 100644 index 0000000000000000000000000000000000000000..66dc717bb3a2a26707d206e537691deb4d58f04d --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-params.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-isp-params.c + * + * CAMSS ISP parameter buffer parser. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <media/videobuf2-core.h> +#include <media/v4l2-isp.h> + +#include "camss-isp-params.h" + +int camss_isp_params_apply(struct device *dev, + struct vb2_buffer *vb, + const struct v4l2_isp_params_block_type_info *type_info, + const camss_isp_params_handler_fn *handlers, + unsigned int num_handlers, + void *priv) +{ + const struct v4l2_isp_params_buffer *buf; + unsigned int remaining; + unsigned int offset = 0; + int ret; + + ret = v4l2_isp_params_validate_buffer_size(dev, vb, + v4l2_isp_params_buffer_size(CAMSS_PARAMS_MAX_PAYLOAD)); + if (ret) + return ret; + + buf = vb2_plane_vaddr(vb, 0); + + ret = v4l2_isp_params_validate_buffer(dev, vb, buf, type_info, num_handlers); + if (ret) + return ret; + + dev_dbg(dev, "params: version=%u data_size=%u\n", buf->version, buf->data_size); + + remaining = buf->data_size; + + while (remaining >= sizeof(struct v4l2_isp_params_block_header)) { + const union camss_isp_params_block *block = + (const union camss_isp_params_block *)&buf->data[offset]; + u16 type = block->header.type; + u32 bsize = block->header.size; + + if (type < num_handlers && handlers[type]) + handlers[type](priv, block); + else + dev_dbg(dev, "params: no handler for block type %u\n", type); + + offset += bsize; + remaining -= bsize; + } + + dev_dbg(dev, "params: buffer parsed successfully\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(camss_isp_params_apply); + +MODULE_DESCRIPTION("CAMSS ISP parameter buffer parser"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/qcom/camss/camss-isp-params.h b/drivers/media/platform/qcom/camss/camss-isp-params.h new file mode 100644 index 0000000000000000000000000000000000000000..4cedfbc745f81655569ff8bdd8e389b35f2c67a7 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-params.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-isp-params.h + * + * CAMSS ISP parameter buffer parser. + * + * Wraps the upstream v4l2_isp_params_validate_buffer() validation and adds + * a dispatch layer: after validation each block is forwarded to a + * driver-supplied handler. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef CAMSS_ISP_PARAMS_H +#define CAMSS_ISP_PARAMS_H + +#include <linux/types.h> +#include <media/v4l2-isp.h> +#include <uapi/linux/camss-config.h> + +#define CAMSS_ISP_PARAMS_FMT_INIT \ + { .fourcc = V4L2_META_FMT_QCOM_ISP_PARAMS, .depth = 8, .align = 0, .num_planes = 1 } + +#define CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY (1U << V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(0)) + +struct device; +struct vb2_buffer; +struct camss_isp_fmt; + +union camss_isp_params_block { + struct v4l2_isp_params_block_header header; + struct camss_params_wb_gain wb_gain; + struct camss_params_chroma_enhan chroma_enhan; + struct camss_params_color_correct color_correct; +}; + +typedef void (*camss_isp_params_handler_fn)(void *priv, const union camss_isp_params_block *block); + +/** + * camss_isp_params_apply - validate and dispatch a params buffer + * + * @dev: device for error logging + * @vb: the vb2 buffer (used for size validation) + * @type_info: per-block-type validation info, indexed by block type + * @handlers: per-block-type handlers, indexed by block type + * @num_handlers: number of entries in @type_info and @handlers + * @priv: opaque pointer forwarded to each handler + * + * Calls v4l2_isp_params_validate_buffer_size(), then + * v4l2_isp_params_validate_buffer(), then walks the validated block stream + * dispatching each block to its handler. + * + * Returns 0 on success, negative errno on validation failure. + */ +int camss_isp_params_apply(struct device *dev, + struct vb2_buffer *vb, + const struct v4l2_isp_params_block_type_info *type_info, + const camss_isp_params_handler_fn *handlers, + unsigned int num_handlers, + void *priv); + +#endif /* CAMSS_ISP_PARAMS_H */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (8 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 09/14] media: qcom: camss: Add camss-isp-params helper Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 14:22 ` Konrad Dybcio 2026-04-28 6:31 ` Krzysztof Kozlowski 2026-04-27 12:43 ` [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode Loic Poulain ` (3 subsequent siblings) 13 siblings, 2 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add Devicetree binding documentation for the Qualcomm Camera Subsystem Offline Processing Engine (OPE) found on platforms such as Agatti. The OPE is a memory-to-memory image processing block which operates on frames read from and written back to system memory. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- .../bindings/media/qcom,qcm2290-camss-ope.yaml | 131 +++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss-ope.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss-ope.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c91d73af61f5cbf8384be5ff9b03683de8413542 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss-ope.yaml @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,qcm2290-camss-ope.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Camera Subsystem Offline Processing Engine + +maintainers: + - Loic Poulain <loic.poulain@oss.qualcomm.com> + +description: + The Qualcomm Camera Subsystem (CAMSS) Offline Processing Engine (OPE) + is a memory-to-memory image processing block. It supports a range of + pixel-processing operations such as scaling, cropping, gain adjustments, + white-balancing, and various format conversions. The OPE does not + interface directly with image sensors, instead, it processes frames + sourced from and written back to system memory. + +properties: + compatible: + const: qcom,qcm2290-camss-ope + + reg: + maxItems: 5 + + reg-names: + items: + - const: top + - const: qos + - const: pipeline + - const: bus_read + - const: bus_write + + clocks: + maxItems: 3 + + clock-names: + items: + - const: core + - const: iface + - const: data + + interrupts: + maxItems: 1 + + interconnects: + maxItems: 2 + + interconnect-names: + items: + - const: config + - const: data + + iommus: + maxItems: 2 + + operating-points-v2: true + + opp-table: + type: object + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interconnects + - interconnect-names + - iommus + - power-domains + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,gcc-qcm2290.h> + #include <dt-bindings/interconnect/qcom,qcm2290.h> + #include <dt-bindings/interconnect/qcom,rpm-icc.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/power/qcom-rpmpd.h> + + camss_ope: isp@5c42400 { + compatible = "qcom,qcm2290-camss-ope"; + + reg = <0x5c42400 0x200>, + <0x5c42600 0x200>, + <0x5c42800 0x4400>, + <0x5c46c00 0x190>, + <0x5c46d90 0xa00>; + reg-names = "top", "qos", "pipeline", + "bus_read", "bus_write"; + + clocks = <&gcc GCC_CAMSS_OPE_CLK>, + <&gcc GCC_CAMSS_OPE_AHB_CLK>, + <&gcc GCC_CAMSS_NRT_AXI_CLK>; + clock-names = "core", "iface", "data"; + + interrupts = <GIC_SPI 209 IRQ_TYPE_EDGE_RISING>; + + interconnects = <&bimc MASTER_APPSS_PROC RPM_ACTIVE_TAG + &config_noc SLAVE_CAMERA_CFG RPM_ACTIVE_TAG>, + <&mmnrt_virt MASTER_CAMNOC_SF RPM_ALWAYS_TAG + &bimc SLAVE_EBI1 RPM_ALWAYS_TAG>; + interconnect-names = "config", "data"; + + iommus = <&apps_smmu 0x820 0x0>, + <&apps_smmu 0x840 0x0>; + + operating-points-v2 = <&ope_opp_table>; + power-domains = <&rpmpd QCM2290_VDDCX>; + + ope_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-200000000 { + opp-hz = /bits/ 64 <200000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp-465000000 { + opp-hz = /bits/ 64 <465000000>; + required-opps = <&rpmpd_opp_nom>; + }; + }; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) 2026-04-27 12:43 ` [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain @ 2026-04-27 14:22 ` Konrad Dybcio 2026-04-27 20:33 ` Loic Poulain 2026-04-28 6:31 ` Krzysztof Kozlowski 1 sibling, 1 reply; 33+ messages in thread From: Konrad Dybcio @ 2026-04-27 14:22 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 4/27/26 2:43 PM, Loic Poulain wrote: > Add Devicetree binding documentation for the Qualcomm Camera Subsystem > Offline Processing Engine (OPE) found on platforms such as Agatti. > The OPE is a memory-to-memory image processing block which operates > on frames read from and written back to system memory. > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- [...] > + clocks = <&gcc GCC_CAMSS_OPE_CLK>, > + <&gcc GCC_CAMSS_OPE_AHB_CLK>, > + <&gcc GCC_CAMSS_NRT_AXI_CLK>; Should the two AXI clocks be aggregated by camss-top instead? Otherwise we run the risk of the OPE driver setting a rate of A and another sub-device setting a rate of B Konrad ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) 2026-04-27 14:22 ` Konrad Dybcio @ 2026-04-27 20:33 ` Loic Poulain 2026-04-28 10:15 ` Konrad Dybcio 0 siblings, 1 reply; 33+ messages in thread From: Loic Poulain @ 2026-04-27 20:33 UTC (permalink / raw) To: Konrad Dybcio Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Mon, Apr 27, 2026 at 4:22 PM Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> wrote: > > On 4/27/26 2:43 PM, Loic Poulain wrote: > > Add Devicetree binding documentation for the Qualcomm Camera Subsystem > > Offline Processing Engine (OPE) found on platforms such as Agatti. > > The OPE is a memory-to-memory image processing block which operates > > on frames read from and written back to system memory. > > > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > --- > > [...] > > > + clocks = <&gcc GCC_CAMSS_OPE_CLK>, > > + <&gcc GCC_CAMSS_OPE_AHB_CLK>, > > + <&gcc GCC_CAMSS_NRT_AXI_CLK>; > > Should the two AXI clocks be aggregated by camss-top instead? > > Otherwise we run the risk of the OPE driver setting a rate of A > and another sub-device setting a rate of B On qcm2290, OPE appears to be the only consumer of the NRT AXI clock, while the capture path (VFE/TFE) relies on the RT AXI clock. That said, this may not always be the case and these clocks (AXI / NRT‑AXI / RT‑AXI) seem like they could reasonably be managed at the camss-bus/top level. The open question is how the NRT AXI clock should be enabled when required? enabling them unconditionally (similar to other camss PM clocks), introducing a dedicated CAMSS top‑level interface for voting, or leveraging an existing framework to handle this? Regards, Loic ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) 2026-04-27 20:33 ` Loic Poulain @ 2026-04-28 10:15 ` Konrad Dybcio 0 siblings, 0 replies; 33+ messages in thread From: Konrad Dybcio @ 2026-04-28 10:15 UTC (permalink / raw) To: Loic Poulain Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 4/27/26 10:33 PM, Loic Poulain wrote: > On Mon, Apr 27, 2026 at 4:22 PM Konrad Dybcio > <konrad.dybcio@oss.qualcomm.com> wrote: >> >> On 4/27/26 2:43 PM, Loic Poulain wrote: >>> Add Devicetree binding documentation for the Qualcomm Camera Subsystem >>> Offline Processing Engine (OPE) found on platforms such as Agatti. >>> The OPE is a memory-to-memory image processing block which operates >>> on frames read from and written back to system memory. >>> >>> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> >>> --- >> >> [...] >> >>> + clocks = <&gcc GCC_CAMSS_OPE_CLK>, >>> + <&gcc GCC_CAMSS_OPE_AHB_CLK>, >>> + <&gcc GCC_CAMSS_NRT_AXI_CLK>; >> >> Should the two AXI clocks be aggregated by camss-top instead? >> >> Otherwise we run the risk of the OPE driver setting a rate of A >> and another sub-device setting a rate of B > > On qcm2290, OPE appears to be the only consumer of the NRT AXI clock, > while the capture path (VFE/TFE) relies on the RT AXI clock. That > said, this may not always be the case and these clocks (AXI / NRT‑AXI > / RT‑AXI) seem like they could reasonably be managed at the > camss-bus/top level. > > The open question is how the NRT AXI clock should be enabled when > required? enabling them unconditionally (similar to other camss PM > clocks), introducing a dedicated CAMSS top‑level interface for voting, > or leveraging an existing framework to handle this? So, interconnect, or some internal, smaller version of it? Konrad ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) 2026-04-27 12:43 ` [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain 2026-04-27 14:22 ` Konrad Dybcio @ 2026-04-28 6:31 ` Krzysztof Kozlowski 1 sibling, 0 replies; 33+ messages in thread From: Krzysztof Kozlowski @ 2026-04-28 6:31 UTC (permalink / raw) To: Loic Poulain Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Mon, Apr 27, 2026 at 02:43:37PM +0200, Loic Poulain wrote: > Add Devicetree binding documentation for the Qualcomm Camera Subsystem > Offline Processing Engine (OPE) found on platforms such as Agatti. > The OPE is a memory-to-memory image processing block which operates > on frames read from and written back to system memory. > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- > .../bindings/media/qcom,qcm2290-camss-ope.yaml | 131 +++++++++++++++++++++ > 1 file changed, 131 insertions(+) Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> Best regards, Krzysztof ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (9 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-28 6:35 ` Krzysztof Kozlowski 2026-04-27 12:43 ` [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition Loic Poulain ` (2 subsequent siblings) 13 siblings, 1 reply; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Extend the qcm2290 CAMSS binding to describe CAMSS as a simple bus by allowing child ISP nodes. Add the required address and size cells, as well as ranges, and validate ISP subnodes against the existing qcom,qcm2290-camss-ope schema. On qcm2290 the OPE (Offline Processing Engine) is a memory-to-memory ISP (Image Signal Processor). Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- .../devicetree/bindings/media/qcom,qcm2290-camss.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml index 391d0f6f67ef5fdfea31dd3683477561516b1556..d8b356028e24c3c5b2e9b7f20e220db7d491ad68 100644 --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml @@ -52,6 +52,14 @@ properties: - const: vfe1 - const: vfe1_cphy_rx + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + + ranges: true + interrupts: maxItems: 8 @@ -117,6 +125,11 @@ properties: required: - data-lanes +patternProperties: + "^isp@[0-9a-f]+$": + $ref: /schemas/media/qcom,qcm2290-camss.yaml + unevaluatedProperties: false + required: - compatible - reg -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode 2026-04-27 12:43 ` [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode Loic Poulain @ 2026-04-28 6:35 ` Krzysztof Kozlowski 2026-04-28 9:50 ` Konrad Dybcio 0 siblings, 1 reply; 33+ messages in thread From: Krzysztof Kozlowski @ 2026-04-28 6:35 UTC (permalink / raw) To: Loic Poulain Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Mon, Apr 27, 2026 at 02:43:38PM +0200, Loic Poulain wrote: > Extend the qcm2290 CAMSS binding to describe CAMSS as a simple bus by > allowing child ISP nodes. Add the required address and size cells, as > well as ranges, and validate ISP subnodes against the existing > qcom,qcm2290-camss-ope schema. > > On qcm2290 the OPE (Offline Processing Engine) is a memory-to-memory > ISP (Image Signal Processor). > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- > .../devicetree/bindings/media/qcom,qcm2290-camss.yaml | 13 +++++++++++++ > 1 file changed, 13 insertions(+) > > diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml > index 391d0f6f67ef5fdfea31dd3683477561516b1556..d8b356028e24c3c5b2e9b7f20e220db7d491ad68 100644 > --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml > +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml > @@ -52,6 +52,14 @@ properties: > - const: vfe1 > - const: vfe1_cphy_rx > > + '#address-cells': Please use consistent quotes, either ' or " > + const: 2 This means the child will use 64-bit addressing, but does it need that? Or do you need that to have 36-bit DMA addressing? If there are true no needs for above, usually recommendation is to use narrowe address space for children, so only 32-bit. > + > + '#size-cells': > + const: 2 Best regards, Krzysztof ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode 2026-04-28 6:35 ` Krzysztof Kozlowski @ 2026-04-28 9:50 ` Konrad Dybcio 2026-04-28 9:54 ` Krzysztof Kozlowski 0 siblings, 1 reply; 33+ messages in thread From: Konrad Dybcio @ 2026-04-28 9:50 UTC (permalink / raw) To: Krzysztof Kozlowski, Loic Poulain Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 4/28/26 8:35 AM, Krzysztof Kozlowski wrote: > On Mon, Apr 27, 2026 at 02:43:38PM +0200, Loic Poulain wrote: >> Extend the qcm2290 CAMSS binding to describe CAMSS as a simple bus by >> allowing child ISP nodes. Add the required address and size cells, as >> well as ranges, and validate ISP subnodes against the existing >> qcom,qcm2290-camss-ope schema. >> >> On qcm2290 the OPE (Offline Processing Engine) is a memory-to-memory >> ISP (Image Signal Processor). >> >> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> >> --- >> .../devicetree/bindings/media/qcom,qcm2290-camss.yaml | 13 +++++++++++++ >> 1 file changed, 13 insertions(+) >> >> diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >> index 391d0f6f67ef5fdfea31dd3683477561516b1556..d8b356028e24c3c5b2e9b7f20e220db7d491ad68 100644 >> --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >> +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >> @@ -52,6 +52,14 @@ properties: >> - const: vfe1 >> - const: vfe1_cphy_rx >> >> + '#address-cells': > > Please use consistent quotes, either ' or " > >> + const: 2 > > This means the child will use 64-bit addressing, but does it need that? > Or do you need that to have 36-bit DMA addressing? If there are true no > needs for above, usually recommendation is to use narrowe address space > for children, so only 32-bit. I can't find data for this platform, but on e.g. Hamoa, most camera components can address 36b Konrad ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode 2026-04-28 9:50 ` Konrad Dybcio @ 2026-04-28 9:54 ` Krzysztof Kozlowski 0 siblings, 0 replies; 33+ messages in thread From: Krzysztof Kozlowski @ 2026-04-28 9:54 UTC (permalink / raw) To: Konrad Dybcio, Loic Poulain Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 28/04/2026 11:50, Konrad Dybcio wrote: >>> diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >>> index 391d0f6f67ef5fdfea31dd3683477561516b1556..d8b356028e24c3c5b2e9b7f20e220db7d491ad68 100644 >>> --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >>> +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-camss.yaml >>> @@ -52,6 +52,14 @@ properties: >>> - const: vfe1 >>> - const: vfe1_cphy_rx >>> >>> + '#address-cells': >> >> Please use consistent quotes, either ' or " >> >>> + const: 2 >> >> This means the child will use 64-bit addressing, but does it need that? >> Or do you need that to have 36-bit DMA addressing? If there are true no >> needs for above, usually recommendation is to use narrowe address space >> for children, so only 32-bit. > > I can't find data for this platform, but on e.g. Hamoa, most camera > components can address 36b If this is a conscious choice, should be mentioned in commit msg or as comment in the code with such explanation. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (10 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:56 ` Konrad Dybcio 2026-04-27 21:57 ` Bryan O'Donoghue 2026-04-27 12:43 ` [PATCH v2 13/14] media: qcom: camss: Add CAMSS Offline Processing Engine driver Loic Poulain 2026-04-27 12:43 ` [PATCH v2 14/14] arm64: dts: qcom: agatti: Add OPE node Loic Poulain 13 siblings, 2 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add the uapi header camss-config.h defining the ISP parameter structures used by the CAMSS Offline Processing Engine (OPE) driver. This includes structures for white balance, chroma enhancement and color correction configuration. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- include/uapi/linux/camss-config.h | 118 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/include/uapi/linux/camss-config.h b/include/uapi/linux/camss-config.h new file mode 100644 index 0000000000000000000000000000000000000000..2e15f03f16025c4a4852ad62afc39a5df6542422 --- /dev/null +++ b/include/uapi/linux/camss-config.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Qualcomm CAMSS ISP parameters UAPI + * + * Uses the generic V4L2 extensible ISP parameters buffer format defined in + * <uapi/linux/media/v4l2-isp.h>. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _UAPI_LINUX_CAMSS_CONFIG_H +#define _UAPI_LINUX_CAMSS_CONFIG_H + +#include <linux/types.h> +#include <linux/media/v4l2-isp.h> + +#include <linux/videodev2.h> + +/** + * enum camss_params_block_type - CAMSS ISP parameter block identifiers + * + * Each value identifies one ISP processing block. The value is placed in + * the @type field of &struct v4l2_isp_params_block_header. + */ +enum camss_params_block_type { + CAMSS_PARAMS_WB_GAIN = 1, + CAMSS_PARAMS_CHROMA_ENHAN = 2, + CAMSS_PARAMS_COLOR_CORRECT = 3, + CAMSS_PARAMS_MAX, +}; + +/** + * struct camss_params_wb_gain - White Balance gains + * + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN + * @g_gain: green channel gain (15uQ10) + * @b_gain: blue channel gain (15uQ10) + * @r_gain: red channel gain (15uQ10) + */ +struct camss_params_wb_gain { + struct v4l2_isp_params_block_header header; + __u16 g_gain; + __u16 b_gain; + __u16 r_gain; + __u16 _pad; +} __attribute__((aligned(8))); + +/** + * struct camss_params_chroma_enhan - RGB to YUV colour matrix + * + * Implements the CLC_CHROMA_ENHAN pipeline module. All coefficients are + * signed 12-bit fixed-point Q3.8 (range roughly -8.0 to +7.996). + * + * Luma (Y) row of the matrix: + * @luma_v0: R-to-Y coefficient (12sQ8, e.g. 0x04d = 0.299) + * @luma_v1: G-to-Y coefficient (12sQ8, e.g. 0x096 = 0.587) + * @luma_v2: B-to-Y coefficient (12sQ8, e.g. 0x01d = 0.114) + * @luma_k: Y output offset (9s, 0 = no offset) + * + * Chroma (Cb) row: + * @coeff_ap: Cb positive coefficient (12sQ8, e.g. 0x0e6 = 0.886) + * @coeff_am: Cb negative coefficient (12sQ8, e.g. 0xfb3 = -0.338) + * @kcb: Cb output offset (11s, 128 = +128) + * + * Chroma (Cr) row: + * @coeff_cp: Cr positive coefficient (12sQ8, e.g. 0x0b3 = 0.701) + * @coeff_cm: Cr negative coefficient (12sQ8, e.g. 0xfe3 = -0.114) + * @coeff_dp: Cr positive coefficient (12sQ8) + * @coeff_dm: Cr negative coefficient (12sQ8) + * @kcr: Cr output offset (11s, 128 = +128) + * + * @header: generic block header; @header.type = CAMSS_PARAMS_CHROMA_ENHAN + */ +struct camss_params_chroma_enhan { + struct v4l2_isp_params_block_header header; + __u16 luma_v0; + __u16 luma_v1; + __u16 luma_v2; + __u16 luma_k; + __u16 coeff_ap; + __u16 coeff_am; + __u16 coeff_cp; + __u16 coeff_cm; + __u16 coeff_dp; + __u16 coeff_dm; + __u16 kcb; + __u16 kcr; +} __attribute__((aligned(8))); + +/** + * struct camss_params_color_correct - colour correction matrix + * + * signed 12-bit fixed-point (Qm) + * + * Out_ch0 (g) = A0*G+B0*B+C0*R + K0 + * Out_ch1 (b) = A1*G+B1*B+C1*R + K1 + * Out_ch2 (r) = A2*G+B2*B+C2*R + K2 + * + * m = 0x0 - Coefficients are signed 12sQ7 numbers + * m = 0x1 - Coefficients are signed 12sQ8 numbers + * m = 0x2 - Coefficients are signed 12sQ9 numbers + * m = 0x3 - Coefficients are signed 12sQ10 numbers + */ +struct camss_params_color_correct { + struct v4l2_isp_params_block_header header; + __u16 a[3]; + __u16 b[3]; + __u16 c[3]; + __u16 k[3]; + __u16 m; +} __attribute__((aligned(8))); + +#define CAMSS_PARAMS_MAX_PAYLOAD \ + (sizeof(struct camss_params_wb_gain) +\ + sizeof(struct camss_params_chroma_enhan) +\ + sizeof(struct camss_params_color_correct)) + +#endif /* _UAPI_LINUX_CAMSS_CONFIG_H */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 12:43 ` [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition Loic Poulain @ 2026-04-27 12:56 ` Konrad Dybcio 2026-04-27 20:08 ` Loic Poulain 2026-04-27 21:57 ` Bryan O'Donoghue 1 sibling, 1 reply; 33+ messages in thread From: Konrad Dybcio @ 2026-04-27 12:56 UTC (permalink / raw) To: Loic Poulain, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 4/27/26 2:43 PM, Loic Poulain wrote: > Add the uapi header camss-config.h defining the ISP parameter > structures used by the CAMSS Offline Processing Engine (OPE) driver. > This includes structures for white balance, chroma enhancement and > color correction configuration. > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > --- [...] > +/** > + * struct camss_params_wb_gain - White Balance gains > + * > + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN > + * @g_gain: green channel gain (15uQ10) > + * @b_gain: blue channel gain (15uQ10) > + * @r_gain: red channel gain (15uQ10) > + */ > +struct camss_params_wb_gain { > + struct v4l2_isp_params_block_header header; > + __u16 g_gain; > + __u16 b_gain; > + __u16 r_gain; > + __u16 _pad; > +} __attribute__((aligned(8))); Should this be __le for all of the related types? Konrad ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 12:56 ` Konrad Dybcio @ 2026-04-27 20:08 ` Loic Poulain 2026-04-27 20:25 ` Laurent Pinchart 0 siblings, 1 reply; 33+ messages in thread From: Loic Poulain @ 2026-04-27 20:08 UTC (permalink / raw) To: Konrad Dybcio Cc: Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On Mon, Apr 27, 2026 at 2:56 PM Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> wrote: > > On 4/27/26 2:43 PM, Loic Poulain wrote: > > Add the uapi header camss-config.h defining the ISP parameter > > structures used by the CAMSS Offline Processing Engine (OPE) driver. > > This includes structures for white balance, chroma enhancement and > > color correction configuration. > > > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > --- > > [...] > > > > +/** > > + * struct camss_params_wb_gain - White Balance gains > > + * > > + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN > > + * @g_gain: green channel gain (15uQ10) > > + * @b_gain: blue channel gain (15uQ10) > > + * @r_gain: red channel gain (15uQ10) > > + */ > > +struct camss_params_wb_gain { > > + struct v4l2_isp_params_block_header header; > > + __u16 g_gain; > > + __u16 b_gain; > > + __u16 r_gain; > > + __u16 _pad; > > +} __attribute__((aligned(8))); > > Should this be __le for all of the related types? At the moment, this is purely a UAPI, the values are not dumped directly to hardware as-is. Instead, each field is translated into one or more register writes, with the appropriate math, masking and shifting applied. Adding explicit endianness in the definition would therefore require special handling on both user and kernel side (to_le16, from_le16). On the other side, there are scenarios, such as platforms that rely on ICP (firmware-driven processing), where we may want to forward these structures directly within an HFI packet to the ICP MCU. In that context, explicitly defining the endianness could make some sense... Regards, Loic ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 20:08 ` Loic Poulain @ 2026-04-27 20:25 ` Laurent Pinchart 2026-04-27 21:01 ` Loic Poulain 0 siblings, 1 reply; 33+ messages in thread From: Laurent Pinchart @ 2026-04-27 20:25 UTC (permalink / raw) To: Loic Poulain Cc: Konrad Dybcio, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, kieran.bingham On Mon, Apr 27, 2026 at 10:08:59PM +0200, Loic Poulain wrote: > On Mon, Apr 27, 2026 at 2:56 PM Konrad Dybcio wrote: > > On 4/27/26 2:43 PM, Loic Poulain wrote: > > > Add the uapi header camss-config.h defining the ISP parameter > > > structures used by the CAMSS Offline Processing Engine (OPE) driver. > > > This includes structures for white balance, chroma enhancement and > > > color correction configuration. > > > > > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > > --- > > > > [...] > > > > > > > +/** > > > + * struct camss_params_wb_gain - White Balance gains > > > + * > > > + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN > > > + * @g_gain: green channel gain (15uQ10) > > > + * @b_gain: blue channel gain (15uQ10) > > > + * @r_gain: red channel gain (15uQ10) > > > + */ > > > +struct camss_params_wb_gain { > > > + struct v4l2_isp_params_block_header header; > > > + __u16 g_gain; > > > + __u16 b_gain; > > > + __u16 r_gain; > > > + __u16 _pad; > > > +} __attribute__((aligned(8))); > > > > Should this be __le for all of the related types? > > At the moment, this is purely a UAPI, the values are not dumped > directly to hardware as-is. Instead, each field is translated into one > or more register writes, with the appropriate math, masking and > shifting applied. Adding explicit endianness in the definition would > therefore require special handling on both user and kernel side > (to_le16, from_le16). > > On the other side, there are scenarios, such as platforms that rely on > ICP (firmware-driven processing), where we may want to forward these > structures directly within an HFI packet to the ICP MCU. In that > context, explicitly defining the endianness could make some sense... Would those be different structures, or do you envision that someone could develop an ICP firmware that understands these structures ? -- Regards, Laurent Pinchart ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 20:25 ` Laurent Pinchart @ 2026-04-27 21:01 ` Loic Poulain 0 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 21:01 UTC (permalink / raw) To: Laurent Pinchart Cc: Konrad Dybcio, Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, kieran.bingham On Mon, Apr 27, 2026 at 10:25 PM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > On Mon, Apr 27, 2026 at 10:08:59PM +0200, Loic Poulain wrote: > > On Mon, Apr 27, 2026 at 2:56 PM Konrad Dybcio wrote: > > > On 4/27/26 2:43 PM, Loic Poulain wrote: > > > > Add the uapi header camss-config.h defining the ISP parameter > > > > structures used by the CAMSS Offline Processing Engine (OPE) driver. > > > > This includes structures for white balance, chroma enhancement and > > > > color correction configuration. > > > > > > > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> > > > > --- > > > > > > [...] > > > > > > > > > > +/** > > > > + * struct camss_params_wb_gain - White Balance gains > > > > + * > > > > + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN > > > > + * @g_gain: green channel gain (15uQ10) > > > > + * @b_gain: blue channel gain (15uQ10) > > > > + * @r_gain: red channel gain (15uQ10) > > > > + */ > > > > +struct camss_params_wb_gain { > > > > + struct v4l2_isp_params_block_header header; > > > > + __u16 g_gain; > > > > + __u16 b_gain; > > > > + __u16 r_gain; > > > > + __u16 _pad; > > > > +} __attribute__((aligned(8))); > > > > > > Should this be __le for all of the related types? > > > > At the moment, this is purely a UAPI, the values are not dumped > > directly to hardware as-is. Instead, each field is translated into one > > or more register writes, with the appropriate math, masking and > > shifting applied. Adding explicit endianness in the definition would > > therefore require special handling on both user and kernel side > > (to_le16, from_le16). > > > > On the other side, there are scenarios, such as platforms that rely on > > ICP (firmware-driven processing), where we may want to forward these > > structures directly within an HFI packet to the ICP MCU. In that > > context, explicitly defining the endianness could make some sense... > > Would those be different structures, or do you envision that someone > could develop an ICP firmware that understands these structures ? I believe some of these structures could be reused across different platforms and bus implementations. In particular, a given e.g. wb_gain parameter struct could potentially be shared between OPE, ICP (firmware‑based), and the inline engine (IFE), with minimal driver-side adaptation (limited to shifting and masking) or even zero adaptation. Bryan has sent another RFC [1] defining parameter structures and layouts that (as far as I understand) mirror the HFI data layout, so I plan to sync with him to identify opportunities to leverage common types or structures. [1] https://lore.kernel.org/all/20260426000418.1158716-1-bryan.odonoghue@linaro.org/ ^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition 2026-04-27 12:43 ` [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition Loic Poulain 2026-04-27 12:56 ` Konrad Dybcio @ 2026-04-27 21:57 ` Bryan O'Donoghue 1 sibling, 0 replies; 33+ messages in thread From: Bryan O'Donoghue @ 2026-04-27 21:57 UTC (permalink / raw) To: Loic Poulain, Vladimir Zapolskiy, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham On 27/04/2026 13:43, Loic Poulain wrote: > +/** > + * struct camss_params_wb_gain - White Balance gains > + * > + * @header: generic block header; @header.type = CAMSS_PARAMS_WB_GAIN > + * @g_gain: green channel gain (15uQ10) > + * @b_gain: blue channel gain (15uQ10) > + * @r_gain: red channel gain (15uQ10) > + */ > +struct camss_params_wb_gain { > + struct v4l2_isp_params_block_header header; > + __u16 g_gain; > + __u16 b_gain; > + __u16 r_gain; > + __u16 _pad; > +} __attribute__((aligned(8))); So I published a comprehensive list of these structures a few days ago. https://lore.kernel.org/linux-media/20260426000418.1158716-1-bryan.odonoghue@linaro.org/T/#u See: struct camss_params_wb_gain - White balance gains and offsets The ICP/HFI structures - at least for the stats I believe are how hardware writes data to memory. OTOH on the way in, the params ought to be pretty uniform as again HFI/ICP needs to take that representation and either - Hand it over to CDM or - Pass it over HFI so that ICP can hand it over to CDM The ordering and precision is a resolved thing. I'm ~ sure OPE must write and consume in the same format. --- bod ^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v2 13/14] media: qcom: camss: Add CAMSS Offline Processing Engine driver 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (11 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 14/14] arm64: dts: qcom: agatti: Add OPE node Loic Poulain 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add a V4L2 mem-to-mem driver for the Qualcomm Offline Processing Engine (OPE). OPE is a memory-to-memory ISP block that converts raw Bayer frames to YUV, performing white balance, demosaic, chroma enhancement, color correction and downscaling. The hardware architecture consists of Fetch Engines and Write Engines, connected through intermediate pipeline modules for pix processing. The driver exposes three video nodes per pipeline instance: - frame-input: Bayer RAW input (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) - frame-output: YUV output (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) - params: ISP parameters (V4L2_BUF_TYPE_META_OUTPUT) Hardware features: - Stripe-based processing (up to 336 pixels wide per stripe) - White balance (CLC_WB) - Demosaic / Bayer-to-RGB (CLC_DEMO) - RGB-to-YUV conversion (CLC_CHROMA_ENHAN) - Color correction matrix (CLC_CC) - MN downscaler for chroma and luma planes Default configuration values are based on public standards such as BT.601. Processing Model: OPE processes frames in stripes of up to 336 pixels. Therefore, frames must be split into stripes for processing. Each stripe is configured after the previous one has been acquired (double buffered registers). To minimize inter-stripe latency, stripe configurations are generated ahead of time. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- drivers/media/platform/qcom/camss/Kconfig | 18 + drivers/media/platform/qcom/camss/Makefile | 3 + drivers/media/platform/qcom/camss/camss-isp-ope.c | 2466 +++++++++++++++++++++ 3 files changed, 2487 insertions(+) diff --git a/drivers/media/platform/qcom/camss/Kconfig b/drivers/media/platform/qcom/camss/Kconfig index d77482f3f5eadc65856806b9b237d65ea484f267..aae87e109b336a953a25a7b3bc8ada66b29135ce 100644 --- a/drivers/media/platform/qcom/camss/Kconfig +++ b/drivers/media/platform/qcom/camss/Kconfig @@ -21,3 +21,21 @@ config VIDEO_QCOM_CAMSS_ISP This module is selected automatically by drivers that need it. +config VIDEO_QCOM_CAMSS_OPE + tristate "Qualcomm CAMSS Offline Processing Engine (OPE) driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_QCOM_CAMSS_ISP + select V4L2_ISP + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + help + Enable support for the Qualcomm Offline Processing Engine (OPE). + OPE is a memory-to-memory ISP block that converts raw Bayer frames + to YUV, performing white balance, demosaic, chroma enhancement and + downscaling. Found on QCM2290 and related SoCs. + + To compile this driver as a module, choose M here: the module + will be called qcom-camss-ope. diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile index fba6f34b8d9f70ea258f7ae1a293a8d58d866498..9d47e4f5fe6dc8d168d866813732655eb0c547a6 100644 --- a/drivers/media/platform/qcom/camss/Makefile +++ b/drivers/media/platform/qcom/camss/Makefile @@ -37,3 +37,6 @@ qcom-camss-isp-objs := camss-isp-bufq.o \ obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o +qcom-camss-ope-objs := camss-isp-ope.o + +obj-$(CONFIG_VIDEO_QCOM_CAMSS_OPE) += qcom-camss-ope.o diff --git a/drivers/media/platform/qcom/camss/camss-isp-ope.c b/drivers/media/platform/qcom/camss/camss-isp-ope.c new file mode 100644 index 0000000000000000000000000000000000000000..6c5792311e19f0b4feda5741e0e40af575338684 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-isp-ope.c @@ -0,0 +1,2466 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-ope.c + * + * Qualcomm MSM Camera Subsystem - Offline Processing Engine + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/* + * This driver provides driver implementation for the Qualcomm Offline + * Processing Engine (OPE). OPE is a memory-to-memory hardware block + * designed for image processing on a source frame. Typically, the input + * frame originates from the SoC CSI capture path, though not limited to. + * + * The hardware architecture consists of Fetch Engines and Write Engines, + * connected through intermediate pipeline modules: + * [FETCH ENGINES] => [Pipeline Modules] => [WRITE ENGINES] + * + * Current Configuration: + * Fetch Engine: One fetch engine is used for Bayer frame input. + * Write Engines: Two display write engines for Y and UV planes output. + * + * Only a subset of the pipeline modules are enabled: + * CLC_WB: White balance for channel gain configuration + * CLC_DEMO: Demosaic for Bayer to RGB conversion + * CLC_CC: Color Correct, coefficient based RGB correction + * CLC_CHROMA_ENHAN: for RGB to YUV conversion + * CLC_DOWNSCALE*: Downscaling for UV (YUV444 -> YUV422/YUV420) and YUV planes + * + * Default configuration values are based on public standards such as BT.601. + * + * Processing Model: + * OPE processes frames in stripes of up to 336 pixels. Therefore, frames must + * be split into stripes for processing. Each stripe is configured after the + * previous one has been acquired (double buffered registers). To minimize + * inter-stripe latency, the stripe configurations are generated ahead of time. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/interconnect.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_clock.h> +#include <linux/pm_domain.h> +#include <linux/pm_opp.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/units.h> + +#include <media/v4l2-device.h> +#include <media/media-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include "camss-isp-bufq.h" +#include "camss-isp-pipeline.h" +#include "camss-isp-sched.h" +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-vmalloc.h> + +#include <uapi/linux/camss-config.h> + +#include "camss-isp-params.h" + +#define OPE_NAME "qcom-camss-ope" + +/* Format descriptor */ +struct ope_fmt { + u32 fourcc; + unsigned int depth; + unsigned int align; + unsigned int num_planes; +}; + +/* Per-queue format state */ +struct ope_fmt_state { + const struct ope_fmt *fmt; + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + unsigned int sequence; + struct v4l2_fract timeperframe; +}; + +/* -------- Register layout -------- */ + +#define OPE_TOP_HW_VERSION 0x000 +#define OPE_TOP_HW_VERSION_STEP GENMASK(15, 0) +#define OPE_TOP_HW_VERSION_REV GENMASK(27, 16) +#define OPE_TOP_HW_VERSION_GEN GENMASK(31, 28) +#define OPE_TOP_RESET_CMD 0x004 +#define OPE_TOP_RESET_CMD_HW BIT(0) +#define OPE_TOP_RESET_CMD_SW BIT(1) +#define OPE_TOP_IRQ_STATUS 0x014 +#define OPE_TOP_IRQ_MASK 0x018 +#define OPE_TOP_IRQ_STATUS_RST_DONE BIT(0) +#define OPE_TOP_IRQ_STATUS_WE BIT(1) +#define OPE_TOP_IRQ_STATUS_FE BIT(2) +#define OPE_TOP_IRQ_STATUS_VIOL BIT(3) +#define OPE_TOP_IRQ_STATUS_IDLE BIT(4) +#define OPE_TOP_IRQ_CLEAR 0x01c +#define OPE_TOP_IRQ_CMD 0x024 +#define OPE_TOP_IRQ_CMD_CLEAR BIT(0) +#define OPE_TOP_VIOLATION_STATUS 0x028 + +/* Fetch engine */ +#define OPE_BUS_RD_INPUT_IF_IRQ_MASK 0x00c +#define OPE_BUS_RD_INPUT_IF_IRQ_CLEAR 0x010 +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD 0x014 +#define OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR BIT(0) +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS 0x018 +#define OPE_BUS_RD_INPUT_IF_CMD 0x01c +#define OPE_BUS_RD_INPUT_IF_CMD_GO_CMD BIT(0) +#define OPE_BUS_RD_CLIENT_0_CORE_CFG 0x050 +#define OPE_BUS_RD_CLIENT_0_CORE_CFG_EN BIT(0) +#define OPE_BUS_RD_CLIENT_0_CCIF_META_DATA 0x054 +#define OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN GENMASK(7, 2) +#define OPE_BUS_RD_CLIENT_0_ADDR_IMAGE 0x058 +#define OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE 0x05c +#define OPE_BUS_RD_CLIENT_0_RD_STRIDE 0x060 +#define OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0 0x064 + +/* Write engines */ +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_0 0x018 +#define OPE_BUS_WR_INPUT_IF_IRQ_MASK_1 0x01c +#define OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0 0x020 +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0 0x028 +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE BIT(0) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_BUF_DONE BIT(8) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL BIT(28) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL BIT(30) +#define OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL BIT(31) +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD 0x030 +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR BIT(0) +#define OPE_BUS_WR_VIOLATION_STATUS 0x064 +#define OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS 0x070 +#define OPE_BUS_WR_CLIENT_CFG(c) (0x200 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_CFG_EN BIT(0) +#define OPE_BUS_WR_CLIENT_CFG_AUTORECOVER BIT(4) +#define OPE_BUS_WR_CLIENT_ADDR_IMAGE(c) (0x204 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_0(c) (0x20c + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_1(c) (0x210 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_IMAGE_CFG_2(c) (0x214 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_PACKER_CFG(c) (0x218 + (c) * 0x100) +#define OPE_BUS_WR_CLIENT_MAX 8 + +/* Pipeline modules */ +#define OPE_PP_CLC_WB_GAIN_MODULE_CFG (0x200 + 0x60) +#define OPE_PP_CLC_WB_GAIN_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_WB_GAIN_WB_CFG(ch) (0x200 + 0x68 + 4 * (ch)) + +#define OPE_PP_CLC_CC_BASE 0x400 +#define OPE_PP_CLC_CC_MODULE_CFG (OPE_PP_CLC_CC_BASE + 0x60) +#define OPE_PP_CLC_CC_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_CC_COEFF_A_CFG_0 (OPE_PP_CLC_CC_BASE + 0x68) +#define OPE_PP_CLC_CC_COEFF_A_CFG_0_A0 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_A_CFG_0_A1 GENMASK(27, 16) +#define OPE_PP_CLC_CC_COEFF_A_CFG_1 (OPE_PP_CLC_CC_BASE + 0x6c) +#define OPE_PP_CLC_CC_COEFF_A_CFG_1_A2 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_B_CFG_0 (OPE_PP_CLC_CC_BASE + 0x70) +#define OPE_PP_CLC_CC_COEFF_B_CFG_0_B0 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_B_CFG_0_B1 GENMASK(27, 16) +#define OPE_PP_CLC_CC_COEFF_B_CFG_1 (OPE_PP_CLC_CC_BASE + 0x74) +#define OPE_PP_CLC_CC_COEFF_B_CFG_1_B2 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_C_CFG_0 (OPE_PP_CLC_CC_BASE + 0x78) +#define OPE_PP_CLC_CC_COEFF_C_CFG_0_C0 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_C_CFG_0_C1 GENMASK(27, 16) +#define OPE_PP_CLC_CC_COEFF_C_CFG_1 (OPE_PP_CLC_CC_BASE + 0x7c) +#define OPE_PP_CLC_CC_COEFF_C_CFG_1_C2 GENMASK(11, 0) +#define OPE_PP_CLC_CC_COEFF_K_CFG_0 (OPE_PP_CLC_CC_BASE + 0x80) +#define OPE_PP_CLC_CC_COEFF_K_CFG_0_K0 GENMASK(12, 0) +#define OPE_PP_CLC_CC_COEFF_K_CFG_1 (OPE_PP_CLC_CC_BASE + 0x84) +#define OPE_PP_CLC_CC_COEFF_K_CFG_1_K1 GENMASK(12, 0) +#define OPE_PP_CLC_CC_COEFF_K_CFG_2 (OPE_PP_CLC_CC_BASE + 0x88) +#define OPE_PP_CLC_CC_COEFF_K_CFG_2_K2 GENMASK(12, 0) +#define OPE_PP_CLC_CC_COEFF_M_CFG (OPE_PP_CLC_CC_BASE + 0x8c) +#define OPE_PP_CLC_CC_COEFF_M_CFG_M GENMASK(11, 0) + +#define OPE_PP_CLC_DEMO_MODULE_CFG (0x800 + 0x60) +#define OPE_PP_CLC_DEMO_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_DEMO_MODULE_CFG_DYN_G_CLAMP_EN BIT(4) +#define OPE_PP_CLC_DEMO_INTERP_COEFF_CFG (0x800 + 0x68) +#define OPE_PP_CLC_DEMO_INTERP_COEFF_CFG_LAMBDA_G GENMASK(15, 8) +#define OPE_PP_CLC_DEMO_INTERP_COEFF_CFG_LAMBDA_RB GENMASK(7, 0) +#define OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_0 (0x800 + 0x6c) +#define OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_0_AK GENMASK(15, 0) +#define OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_1 (0x800 + 0x70) +#define OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_1_WK GENMASK(15, 0) + +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_PRE_BASE 0x1c00 +#define OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE 0x3000 +#define OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE 0x3200 +#define OPE_PP_CLC_DOWNSCALE_MN_CFG(ds) ((ds) + 0x60) +#define OPE_PP_CLC_DOWNSCALE_MN_CFG_EN BIT(0) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(ds) ((ds) + 0x64) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN BIT(9) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN BIT(10) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(ds) ((ds) + 0x68) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(ds) ((ds) + 0x6c) +#define OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(ds) ((ds) + 0x74) + +#define OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG (0x1200 + 0x60) +#define OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN BIT(0) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0 (0x1200 + 0x68) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0 GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1 GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1 (0x1200 + 0x6c) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K GENMASK(31, 23) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2 (0x1200 + 0x70) +#define OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2 GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG (0x1200 + 0x74) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG (0x1200 + 0x78) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG (0x1200 + 0x7C) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG (0x1200 + 0x80) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP GENMASK(11, 0) +#define OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM GENMASK(27, 16) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0 (0x1200 + 0x84) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB GENMASK(31, 21) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1 (0x1200 + 0x88) +#define OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR GENMASK(31, 21) + +/* -------- OPE-specific constants -------- */ + +#define OPE_STRIPE_MAX_W 336 +#define OPE_STRIPE_MAX_H 8192 +#define OPE_STRIPE_MIN_W 16 +#define OPE_STRIPE_MIN_H OPE_STRIPE_MIN_W +#define OPE_MAX_STRIPE 16 +#define OPE_ALIGN_H 1 +#define OPE_ALIGN_W 1 +#define OPE_MIN_W 24 +#define OPE_MIN_H 16 +#define OPE_MAX_W (OPE_STRIPE_MAX_W * OPE_MAX_STRIPE) +#define OPE_MAX_H OPE_STRIPE_MAX_H +#define OPE_RESET_TIMEOUT_MS 100 +#define DEFAULT_FRAMERATE 60 + +/* Downscaler fixed-point helpers */ +#define Q21(v) (((uint64_t)(v)) << 21) +#define DS_Q21(n, d) ((uint32_t)(((uint64_t)(n) << 21) / (d))) +#define DS_RESOLUTION(in, out) \ + (((out) * 128 <= (in)) ? 0x0 : \ + ((out) * 16 <= (in)) ? 0x1 : \ + ((out) * 8 <= (in)) ? 0x2 : 0x3) +#define DS_OUTPUT_PIX(in, phase_init, phase_step) \ + ((Q21(in) - (phase_init)) / (phase_step)) + +#define OPE_WB(n, d) (((n) << 10) / (d)) + +enum ope_downscaler { + OPE_DS_C_PRE, + OPE_DS_C_DISP, + OPE_DS_Y_DISP, + OPE_DS_MAX, +}; + +static const u32 ope_ds_base[OPE_DS_MAX] = { + OPE_PP_CLC_DOWNSCALE_MN_DS_C_PRE_BASE, + OPE_PP_CLC_DOWNSCALE_MN_DS_C_DISP_BASE, + OPE_PP_CLC_DOWNSCALE_MN_DS_Y_DISP_BASE, +}; + +enum ope_wr_client { + OPE_WR_CLIENT_VID_Y, + OPE_WR_CLIENT_VID_C, + OPE_WR_CLIENT_DISP_Y, + OPE_WR_CLIENT_DISP_C, + OPE_WR_CLIENT_ARGB, + OPE_WR_CLIENT_MAX, +}; + +enum ope_pixel_pattern { + OPE_PIXEL_PATTERN_RGRGRG, + OPE_PIXEL_PATTERN_GRGRGR, + OPE_PIXEL_PATTERN_BGBGBG, + OPE_PIXEL_PATTERN_GBGBGB, + OPE_PIXEL_PATTERN_YCBYCR, + OPE_PIXEL_PATTERN_YCRYCB, + OPE_PIXEL_PATTERN_CBYCRY, + OPE_PIXEL_PATTERN_CRYCBY, +}; + +enum ope_stripe_location { + OPE_STRIPE_LOCATION_FULL, + OPE_STRIPE_LOCATION_LEFT, + OPE_STRIPE_LOCATION_RIGHT, + OPE_STRIPE_LOCATION_MIDDLE, +}; + +enum ope_unpacker_format { + OPE_UNPACKER_FMT_PLAIN_8 = 1, + OPE_UNPACKER_FMT_PLAIN_16_10BPP = 2, + OPE_UNPACKER_FMT_MIPI_10 = 13, +}; + +enum ope_packer_format { + OPE_PACKER_FMT_PLAIN_8 = 1, + OPE_PACKER_FMT_PLAIN_8_ODD_EVEN = 2, + OPE_PACKER_FMT_PLAIN_64 = 10, + OPE_PACKER_FMT_MIPI_10 = 12, +}; + +struct ope_hw_fmt { + u32 fourcc; + enum ope_pixel_pattern pattern; + enum ope_unpacker_format unpacker; + enum ope_packer_format packer; +}; + +static const struct ope_hw_fmt ope_hw_fmts[] = { + { V4L2_PIX_FMT_SBGGR10P, OPE_PIXEL_PATTERN_BGBGBG, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 }, + { V4L2_PIX_FMT_SGBRG10P, OPE_PIXEL_PATTERN_GBGBGB, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 }, + { V4L2_PIX_FMT_SGRBG10P, OPE_PIXEL_PATTERN_GRGRGR, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 }, + { V4L2_PIX_FMT_SRGGB10P, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 }, + { V4L2_PIX_FMT_SRGGB8, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_SBGGR8, OPE_PIXEL_PATTERN_BGBGBG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_SGBRG8, OPE_PIXEL_PATTERN_GBGBGB, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_SGRBG8, OPE_PIXEL_PATTERN_GRGRGR, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_NV24, OPE_PIXEL_PATTERN_YCBYCR, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_NV42, OPE_PIXEL_PATTERN_YCRYCB, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN }, + { V4L2_PIX_FMT_NV16, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_NV61, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN }, + { V4L2_PIX_FMT_NV12, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, + { V4L2_PIX_FMT_NV21, OPE_PIXEL_PATTERN_CBYCRY, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN }, + { V4L2_PIX_FMT_GREY, OPE_PIXEL_PATTERN_RGRGRG, + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 }, +}; + +static const struct ope_hw_fmt *ope_find_hw_fmt(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ope_hw_fmts); i++) + if (ope_hw_fmts[i].fourcc == fourcc) + return &ope_hw_fmts[i]; + + return NULL; +} + +static const struct ope_fmt ope_input_fmts[] = { + { V4L2_PIX_FMT_SBGGR10P, 10, 2, 1 }, + { V4L2_PIX_FMT_SGBRG10P, 10, 2, 1 }, + { V4L2_PIX_FMT_SGRBG10P, 10, 2, 1 }, + { V4L2_PIX_FMT_SRGGB10P, 10, 2, 1 }, + { V4L2_PIX_FMT_SRGGB8, 8, 0, 1 }, + { V4L2_PIX_FMT_SBGGR8, 8, 0, 1 }, + { V4L2_PIX_FMT_SGBRG8, 8, 0, 1 }, + { V4L2_PIX_FMT_SGRBG8, 8, 0, 1 }, +}; + +static const struct ope_fmt ope_output_fmts[] = { + { V4L2_PIX_FMT_NV24, 24, 0, 1 }, + { V4L2_PIX_FMT_NV42, 24, 0, 1 }, + { V4L2_PIX_FMT_NV16, 16, 1, 1 }, + { V4L2_PIX_FMT_NV61, 16, 1, 1 }, + { V4L2_PIX_FMT_NV12, 12, 1, 1 }, + { V4L2_PIX_FMT_NV21, 12, 1, 1 }, + { V4L2_PIX_FMT_GREY, 8, 0, 1 }, +}; + +struct ope_dsc_config { + u32 input_width, input_height; + u32 output_width, output_height; + u32 phase_step_h, phase_step_v; +}; + +struct ope_stripe { + struct { + dma_addr_t addr; + u32 width, height, stride; + enum ope_stripe_location location; + enum ope_pixel_pattern pattern; + enum ope_unpacker_format format; + } src; + struct { + dma_addr_t addr; + u32 width, height, stride, x_init; + enum ope_packer_format format; + bool enabled; + } dst[OPE_WR_CLIENT_MAX]; + struct ope_dsc_config dsc[OPE_DS_MAX]; +}; + +/* OPE tuning parameter state */ + +/* Demosaic (CLC_DEMO) coefficients — internal only, not user-configurable. */ +#define CAMSS_PARAMS_INTERNAL (-1) + +struct ope_params_demo { + struct v4l2_isp_params_block_header header; + u8 lambda_rb; + u8 lambda_g; + u16 a_k; + u16 w_k; + u16 _pad; +}; + +/* OPE tuning parameter state */ +struct ope_config { + struct camss_params_wb_gain wb_gain; + struct ope_params_demo demo; + struct camss_params_chroma_enhan chroma_enhan; + struct camss_params_color_correct color_correct; +}; + +enum ope_queue_idx { + OPE_QUEUE_FRAME_IN = 0, + OPE_QUEUE_FRAME_OUT = 1, + OPE_QUEUE_PARAMS = 2, + OPE_QUEUE_COUNT, +}; + +#define OPE_ENTITY_PROC OPE_QUEUE_COUNT + +/* per-context state */ +struct ope_ctx { + struct camss_isp_bufq *bufq; + struct camss_isp_job job; + + struct ope_dev *ope; + struct mutex vbq_lock; + + unsigned int framerate; + + struct ope_fmt_state fmt_in; + struct ope_fmt_state fmt_out; + + struct list_head list; + bool started; + + struct vb2_queue vqs[OPE_QUEUE_COUNT]; + + struct ope_config config; + u8 current_stripe; + struct ope_stripe stripe[OPE_MAX_STRIPE]; +}; + +/* Per OPE device state */ +struct ope_dev { + struct device *dev; + struct v4l2_device v4l2_dev; + struct media_device mdev; + struct camss_isp_pipeline *pipeline; + struct mutex mutex; + struct camss_isp_sched sched; + + struct icc_path *icc_data; + struct icc_path *icc_config; + + void __iomem *base; + void __iomem *base_rd; + void __iomem *base_wr; + void __iomem *base_pp; + + struct completion reset_complete; + + struct list_head ctx_list; + struct ope_ctx *shared_ctx; + unsigned int open_count; + + /* Currently active hardware context (set at job start) */ + struct ope_ctx *hw_ctx; +}; + +/* -------- Register accessors -------- */ + +static inline u32 ope_read(struct ope_dev *ope, u32 reg) +{ + return readl(ope->base + reg); +} + +static inline void ope_write(struct ope_dev *ope, u32 reg, u32 val) +{ + writel(val, ope->base + reg); +} + +static inline void ope_write_wr(struct ope_dev *ope, u32 reg, u32 val) +{ + writel_relaxed(val, ope->base_wr + reg); +} + +static inline u32 ope_read_wr(struct ope_dev *ope, u32 reg) +{ + return readl_relaxed(ope->base_wr + reg); +} + +static inline void ope_write_rd(struct ope_dev *ope, u32 reg, u32 val) +{ + writel_relaxed(val, ope->base_rd + reg); +} + +static inline void ope_write_pp(struct ope_dev *ope, u32 reg, u32 val) +{ + writel_relaxed(val, ope->base_pp + reg); +} + +static inline void ope_start(struct ope_dev *ope) +{ + wmb(); /* ensure all register writes are visible before GO_CMD */ + ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_CMD, OPE_BUS_RD_INPUT_IF_CMD_GO_CMD); +} + +/* -------- Stripe helpers -------- */ + +static inline enum ope_stripe_location ope_stripe_location(unsigned int idx, unsigned int count) +{ + if (count == 1) + return OPE_STRIPE_LOCATION_FULL; + if (idx == 0) + return OPE_STRIPE_LOCATION_LEFT; + if (idx == count - 1) + return OPE_STRIPE_LOCATION_RIGHT; + + return OPE_STRIPE_LOCATION_MIDDLE; +} + +static inline bool ope_stripe_is_last(const struct ope_stripe *s) +{ + return s && (s->src.location == OPE_STRIPE_LOCATION_RIGHT || + s->src.location == OPE_STRIPE_LOCATION_FULL); +} + +static inline struct ope_stripe *ope_current_stripe(struct ope_ctx *ctx) +{ + if (ctx->current_stripe >= OPE_MAX_STRIPE) + return NULL; + + return &ctx->stripe[ctx->current_stripe]; +} + +static inline unsigned int ope_stripe_index(struct ope_ctx *ctx, + const struct ope_stripe *stripe) +{ + return stripe - &ctx->stripe[0]; +} + +static inline struct ope_stripe *ope_prev_stripe(struct ope_ctx *ctx, + struct ope_stripe *stripe) +{ + unsigned int idx = ope_stripe_index(ctx, stripe); + + return idx ? &ctx->stripe[idx - 1] : NULL; +} + +static inline bool ope_pix_fmt_is_yuv(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV24: case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV42: + case V4L2_PIX_FMT_GREY: + return true; + default: + return false; + } +} + +static void ope_gen_stripe_chroma_dsc(struct ope_ctx *ctx, + struct ope_stripe *stripe) +{ + struct ope_dsc_config *dsc = &stripe->dsc[OPE_DS_C_PRE]; + u32 dst_fourcc = ctx->fmt_out.fmt->fourcc; + + dsc->input_width = stripe->src.width; + dsc->input_height = stripe->src.height; + + switch (dst_fourcc) { + case V4L2_PIX_FMT_NV61: case V4L2_PIX_FMT_NV16: + dsc->output_width = dsc->input_width / 2; + dsc->output_height = dsc->input_height; + break; + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: + dsc->output_width = dsc->input_width / 2; + dsc->output_height = dsc->input_height / 2; + break; + default: + dsc->output_width = dsc->input_width; + dsc->output_height = dsc->input_height; + } + + dsc->phase_step_h = DS_Q21(dsc->input_width, dsc->output_width); + dsc->phase_step_v = DS_Q21(dsc->input_height, dsc->output_height); +} + +static void ope_gen_stripe_dsc(struct ope_ctx *ctx, struct ope_stripe *stripe, + u32 h_scale, u32 v_scale) +{ + struct ope_dsc_config *dsc_c = &stripe->dsc[OPE_DS_C_DISP]; + struct ope_dsc_config *dsc_y = &stripe->dsc[OPE_DS_Y_DISP]; + + dsc_c->phase_step_h = dsc_y->phase_step_h = h_scale; + dsc_c->phase_step_v = dsc_y->phase_step_v = v_scale; + + dsc_c->input_width = stripe->dsc[OPE_DS_C_PRE].output_width; + dsc_c->input_height = stripe->dsc[OPE_DS_C_PRE].output_height; + dsc_y->input_width = stripe->src.width; + dsc_y->input_height = stripe->src.height; + + dsc_c->output_width = DS_OUTPUT_PIX(dsc_c->input_width, 0, h_scale); + dsc_c->output_height = DS_OUTPUT_PIX(dsc_c->input_height, 0, v_scale); + dsc_y->output_width = DS_OUTPUT_PIX(dsc_y->input_width, 0, h_scale); + dsc_y->output_height = DS_OUTPUT_PIX(dsc_y->input_height, 0, v_scale); +} + +static void ope_gen_stripe_yuv_dst(struct ope_ctx *ctx, + struct ope_stripe *stripe, + dma_addr_t dst) +{ + const struct ope_fmt_state *fo = &ctx->fmt_out; + unsigned int img_w = fo->width, img_h = fo->height; + const struct ope_hw_fmt *hw = ope_find_hw_fmt(fo->fmt->fourcc); + struct ope_stripe *prev = ope_prev_stripe(ctx, stripe); + u32 x_init = 0; + + stripe->dst[OPE_WR_CLIENT_DISP_Y].enabled = true; + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = true; + + /* Y plane */ + if (prev) + x_init = prev->dst[OPE_WR_CLIENT_DISP_Y].x_init + + prev->dst[OPE_WR_CLIENT_DISP_Y].width; + + stripe->dst[OPE_WR_CLIENT_DISP_Y].addr = dst; + stripe->dst[OPE_WR_CLIENT_DISP_Y].x_init = x_init; + stripe->dst[OPE_WR_CLIENT_DISP_Y].width = stripe->dsc[OPE_DS_Y_DISP].output_width; + stripe->dst[OPE_WR_CLIENT_DISP_Y].height = stripe->dsc[OPE_DS_Y_DISP].output_height; + stripe->dst[OPE_WR_CLIENT_DISP_Y].stride = img_w; + stripe->dst[OPE_WR_CLIENT_DISP_Y].format = OPE_PACKER_FMT_PLAIN_8; + + /* UV plane */ + x_init = 0; + if (prev) + x_init = prev->dst[OPE_WR_CLIENT_DISP_C].x_init + + prev->dst[OPE_WR_CLIENT_DISP_C].width; + + stripe->dst[OPE_WR_CLIENT_DISP_C].addr = dst + img_w * img_h; + stripe->dst[OPE_WR_CLIENT_DISP_C].x_init = x_init; + stripe->dst[OPE_WR_CLIENT_DISP_C].format = hw ? hw->packer : OPE_PACKER_FMT_PLAIN_8; + stripe->dst[OPE_WR_CLIENT_DISP_C].width = stripe->dsc[OPE_DS_C_DISP].output_width * 2; + stripe->dst[OPE_WR_CLIENT_DISP_C].height = stripe->dsc[OPE_DS_C_DISP].output_height; + + switch (fo->fmt->fourcc) { + case V4L2_PIX_FMT_NV42: + case V4L2_PIX_FMT_NV24: + stripe->dst[OPE_WR_CLIENT_DISP_C].stride = img_w * 2; + break; + case V4L2_PIX_FMT_GREY: + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = false; + break; + default: + stripe->dst[OPE_WR_CLIENT_DISP_C].stride = img_w; + } +} + +static void ope_gen_stripe_argb_dst(struct ope_ctx *ctx, struct ope_stripe *stripe, dma_addr_t dst) +{ + unsigned int img_w = ctx->fmt_out.width; + dma_addr_t addr; + + stripe->dst[OPE_WR_CLIENT_ARGB].enabled = true; + + struct ope_stripe *prev = ope_prev_stripe(ctx, stripe); + + if (prev) + addr = prev->dst[OPE_WR_CLIENT_ARGB].addr + + prev->dst[OPE_WR_CLIENT_ARGB].width * 8; + else + addr = dst; + + stripe->dst[OPE_WR_CLIENT_ARGB].addr = addr; + stripe->dst[OPE_WR_CLIENT_ARGB].x_init = 0; + stripe->dst[OPE_WR_CLIENT_ARGB].width = stripe->src.width; + stripe->dst[OPE_WR_CLIENT_ARGB].height = stripe->src.height; + stripe->dst[OPE_WR_CLIENT_ARGB].stride = img_w * 8; + stripe->dst[OPE_WR_CLIENT_ARGB].format = OPE_PACKER_FMT_PLAIN_64; +} + +static void ope_gen_stripes(struct ope_ctx *ctx, dma_addr_t src, dma_addr_t dst) +{ + const struct ope_fmt_state *fi = &ctx->fmt_in; + const struct ope_fmt_state *fo = &ctx->fmt_out; + const struct ope_hw_fmt *src_hw = ope_find_hw_fmt(fi->fmt->fourcc); + unsigned int num_stripes, width, i; + u32 h_scale, v_scale; + + width = fi->width; + num_stripes = DIV_ROUND_UP(fi->width, OPE_STRIPE_MAX_W); + h_scale = DS_Q21(fi->width, fo->width); + v_scale = DS_Q21(fi->height, fo->height); + + for (i = 0; i < num_stripes; i++) { + struct ope_stripe *stripe = &ctx->stripe[i]; + + memset(stripe, 0, sizeof(*stripe)); + + stripe->src.addr = src; + stripe->src.width = width; + stripe->src.height = fi->height; + stripe->src.stride = fi->bytesperline; + stripe->src.location = ope_stripe_location(i, num_stripes); + stripe->src.pattern = src_hw ? src_hw->pattern : 0; + stripe->src.format = src_hw ? src_hw->unpacker : 0; + + /* Ensure last stripe is wide enough */ + if (width > OPE_STRIPE_MAX_W && + width < OPE_STRIPE_MAX_W + OPE_STRIPE_MIN_W) + stripe->src.width -= OPE_STRIPE_MIN_W * 2; + + v4l_bound_align_image(&stripe->src.width, + OPE_STRIPE_MIN_W, OPE_STRIPE_MAX_W, + fi->fmt->align, + &stripe->src.height, + OPE_STRIPE_MIN_H, OPE_STRIPE_MAX_H, + OPE_ALIGN_H, 0); + + width -= stripe->src.width; + src += stripe->src.width * fi->fmt->depth / 8; + + if (ope_pix_fmt_is_yuv(fo->fmt->fourcc)) { + ope_gen_stripe_chroma_dsc(ctx, stripe); + ope_gen_stripe_dsc(ctx, stripe, h_scale, v_scale); + ope_gen_stripe_yuv_dst(ctx, stripe, dst); + } else { + ope_gen_stripe_argb_dst(ctx, stripe, dst); + } + + /* Width in bytes for the fetch engine */ + stripe->src.width = stripe->src.width * fi->fmt->depth / 8; + } +} + +/* -------- Pipeline IQ module programming -------- */ + +static bool ope_module_update(struct ope_ctx *ctx, u32 module_cfg_reg, u32 enable_mask, + struct v4l2_isp_params_block_header *hdr, bool force) +{ + bool enable = !(hdr->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE); + bool dirty = hdr->flags & CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY; + + /* skip if neither forced (new context) nor dirty (updated params) */ + if (!force && !dirty) + return false; + + hdr->flags &= ~CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY; + + ope_write_pp(ctx->ope, module_cfg_reg, enable ? enable_mask : 0); + + /* If module is disabled, not need to update content */ + if (!enable) + return false; + + dev_dbg(ctx->ope->dev, "IQ module (%u) update (flags:0x%x)\n", hdr->type, hdr->flags); + + return true; +} + +static void ope_prog_wb(struct ope_ctx *ctx, bool force) +{ + struct camss_params_wb_gain *wb = &ctx->config.wb_gain; + struct ope_dev *ope = ctx->ope; + + if (!ope_module_update(ctx, OPE_PP_CLC_WB_GAIN_MODULE_CFG, + OPE_PP_CLC_WB_GAIN_MODULE_CFG_EN, + &wb->header, force)) + return; + + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(0), wb->g_gain); + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(1), wb->b_gain); + ope_write_pp(ope, OPE_PP_CLC_WB_GAIN_WB_CFG(2), wb->r_gain); +} + +static void ope_prog_bayer2rgb(struct ope_ctx *ctx, bool force) +{ + struct ope_params_demo *demo = &ctx->config.demo; + struct ope_dev *ope = ctx->ope; + + if (!ope_module_update(ctx, OPE_PP_CLC_DEMO_MODULE_CFG, + OPE_PP_CLC_DEMO_MODULE_CFG_EN | + OPE_PP_CLC_DEMO_MODULE_CFG_DYN_G_CLAMP_EN, + &demo->header, force)) + return; + + ope_write_pp(ope, OPE_PP_CLC_DEMO_INTERP_COEFF_CFG, + FIELD_PREP(OPE_PP_CLC_DEMO_INTERP_COEFF_CFG_LAMBDA_G, demo->lambda_g) | + FIELD_PREP(OPE_PP_CLC_DEMO_INTERP_COEFF_CFG_LAMBDA_RB, demo->lambda_rb)); + ope_write_pp(ope, OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_0, + FIELD_PREP(OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_0_AK, demo->a_k)); + ope_write_pp(ope, OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_1, + FIELD_PREP(OPE_PP_CLC_DEMO_INTERP_CLASSIFIER_CFG_1_WK, demo->w_k)); +} + +static void ope_prog_rgb2yuv(struct ope_ctx *ctx, bool force) +{ + struct camss_params_chroma_enhan *cc = &ctx->config.chroma_enhan; + struct ope_dev *ope = ctx->ope; + + if (!ope_module_update(ctx, OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG, + OPE_PP_CLC_CHROMA_ENHAN_MODULE_CFG_EN, + &cc->header, force)) + return; + + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V0, cc->luma_v0) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_0_V1, cc->luma_v1)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_2_V2, cc->luma_v2)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_LUMA_CFG_1_K, cc->luma_k)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AP, cc->coeff_ap) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_A_CFG_AM, cc->coeff_am)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BP, cc->coeff_dp) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_B_CFG_BM, cc->coeff_dm)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CP, cc->coeff_cp) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_C_CFG_CM, cc->coeff_cm)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DP, cc->coeff_dp) | + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_COEFF_D_CFG_DM, cc->coeff_dm)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_0_KCB, cc->kcb)); + ope_write_pp(ope, OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1, + FIELD_PREP(OPE_PP_CLC_CHROMA_ENHAN_CHROMA_CFG_1_KCR, cc->kcr)); +} + +static void ope_prog_color_correct(struct ope_ctx *ctx, bool force) +{ + struct camss_params_color_correct *cc = &ctx->config.color_correct; + struct ope_dev *ope = ctx->ope; + + if (!ope_module_update(ctx, OPE_PP_CLC_CC_MODULE_CFG, + OPE_PP_CLC_CC_MODULE_CFG_EN, + &cc->header, force)) + return; + + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_A_CFG_0, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_A_CFG_0_A0, cc->a[0]) | + FIELD_PREP(OPE_PP_CLC_CC_COEFF_A_CFG_0_A1, cc->a[1])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_A_CFG_1, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_A_CFG_1_A2, cc->a[2])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_B_CFG_0, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_B_CFG_0_B0, cc->b[0]) | + FIELD_PREP(OPE_PP_CLC_CC_COEFF_B_CFG_0_B1, cc->b[1])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_B_CFG_1, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_B_CFG_1_B2, cc->b[2])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_C_CFG_0, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_C_CFG_0_C0, cc->c[0]) | + FIELD_PREP(OPE_PP_CLC_CC_COEFF_C_CFG_0_C1, cc->c[1])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_C_CFG_1, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_C_CFG_1_C2, cc->c[2])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_K_CFG_0, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_K_CFG_0_K0, cc->k[0])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_K_CFG_1, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_K_CFG_1_K1, cc->k[1])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_K_CFG_2, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_K_CFG_2_K2, cc->k[2])); + ope_write_pp(ope, OPE_PP_CLC_CC_COEFF_M_CFG, + FIELD_PREP(OPE_PP_CLC_CC_COEFF_M_CFG_M, cc->m)); +} + +static void ope_prog_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe) +{ + struct ope_dev *ope = ctx->ope; + int i; + + dev_dbg(ope->dev, "ctx=%p programming stripe %u\n", + ctx, (unsigned int)(stripe - ctx->stripe)); + + /* Fetch Engine */ + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_UNPACK_CFG_0, stripe->src.format); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_BUFFER_SIZE, + (stripe->src.width << 16) | stripe->src.height); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_ADDR_IMAGE, stripe->src.addr); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_RD_STRIDE, stripe->src.stride); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CCIF_META_DATA, + FIELD_PREP(OPE_BUS_RD_CLIENT_0_CCIF_MD_PIX_PATTERN, + stripe->src.pattern)); + ope_write_rd(ope, OPE_BUS_RD_CLIENT_0_CORE_CFG, + OPE_BUS_RD_CLIENT_0_CORE_CFG_EN); + + /* Write Engines */ + for (i = 0; i < OPE_WR_CLIENT_MAX; i++) { + if (!stripe->dst[i].enabled) { + ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i), 0); + continue; + } + ope_write_wr(ope, OPE_BUS_WR_CLIENT_ADDR_IMAGE(i), + stripe->dst[i].addr); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_0(i), + (stripe->dst[i].height << 16) | stripe->dst[i].width); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_1(i), + stripe->dst[i].x_init); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_IMAGE_CFG_2(i), + stripe->dst[i].stride); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_PACKER_CFG(i), + stripe->dst[i].format); + ope_write_wr(ope, OPE_BUS_WR_CLIENT_CFG(i), + OPE_BUS_WR_CLIENT_CFG_EN | + OPE_BUS_WR_CLIENT_CFG_AUTORECOVER); + } + + /* Downscalers */ + for (i = 0; i < OPE_DS_MAX; i++) { + struct ope_dsc_config *dsc = &stripe->dsc[i]; + u32 base = ope_ds_base[i]; + u32 cfg = 0; + + if (dsc->input_width != dsc->output_width) { + dsc->phase_step_h |= + DS_RESOLUTION(dsc->input_width, + dsc->output_width) << 30; + cfg |= OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_H_SCALE_EN; + } + if (dsc->input_height != dsc->output_height) { + dsc->phase_step_v |= + DS_RESOLUTION(dsc->input_height, + dsc->output_height) << 30; + cfg |= OPE_PP_CLC_DOWNSCALE_MN_DS_CFG_V_SCALE_EN; + } + + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_CFG(base), cfg); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(base), + ((dsc->input_width - 1) << 16) | (dsc->input_height - 1)); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_H_CFG(base), + dsc->phase_step_h); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_DS_MN_V_CFG(base), + dsc->phase_step_v); + ope_write_pp(ope, OPE_PP_CLC_DOWNSCALE_MN_CFG(base), + cfg ? OPE_PP_CLC_DOWNSCALE_MN_CFG_EN : 0); + } +} + +static void ope_params_apply_wb(void *priv, const union camss_isp_params_block *block) +{ + struct ope_ctx *ctx = priv; + + ctx->config.wb_gain = block->wb_gain; + ctx->config.wb_gain.header.flags |= CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY; +} + +static void ope_params_apply_chroma_enhan(void *priv, const union camss_isp_params_block *block) +{ + struct ope_ctx *ctx = priv; + + ctx->config.chroma_enhan = block->chroma_enhan; + ctx->config.chroma_enhan.header.flags |= CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY; +} + +static void ope_params_apply_color_correct(void *priv, const union camss_isp_params_block *block) +{ + struct ope_ctx *ctx = priv; + + ctx->config.color_correct = block->color_correct; + ctx->config.color_correct.header.flags |= CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY; +} + +static const struct v4l2_isp_params_block_type_info ope_params_type_info[] = { + [CAMSS_PARAMS_WB_GAIN] = { sizeof(struct camss_params_wb_gain) }, + [CAMSS_PARAMS_CHROMA_ENHAN] = { sizeof(struct camss_params_chroma_enhan) }, + [CAMSS_PARAMS_COLOR_CORRECT] = { sizeof(struct camss_params_color_correct) }, +}; + +static const camss_isp_params_handler_fn ope_params_handlers[] = { + [CAMSS_PARAMS_WB_GAIN] = ope_params_apply_wb, + [CAMSS_PARAMS_CHROMA_ENHAN] = ope_params_apply_chroma_enhan, + [CAMSS_PARAMS_COLOR_CORRECT] = ope_params_apply_color_correct, +}; + +static void ope_apply_params(struct ope_ctx *ctx) +{ + struct vb2_v4l2_buffer *vbuf; + + vbuf = camss_isp_bufq_next(ctx->bufq, OPE_QUEUE_PARAMS); + if (!vbuf) + return; + + camss_isp_params_apply(ctx->ope->dev, &vbuf->vb2_buf, + ope_params_type_info, + ope_params_handlers, + ARRAY_SIZE(ope_params_handlers), + ctx); +} + +/* Default/initial tuning parameters */ +static const struct ope_config ope_default_config = { + .wb_gain = { + .header.type = CAMSS_PARAMS_WB_GAIN, + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE | + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY, + .g_gain = OPE_WB(1, 1), + .b_gain = OPE_WB(3, 2), + .r_gain = OPE_WB(3, 2), + }, + .demo = { + .header.type = CAMSS_PARAMS_INTERNAL, + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE | + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY, + .lambda_rb = 0, + .lambda_g = 128, + .a_k = 128, + .w_k = 102, + }, + .chroma_enhan = { + .header.type = CAMSS_PARAMS_CHROMA_ENHAN, + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE | + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY, + .luma_v0 = 0x04d, + .luma_v1 = 0x096, + .luma_v2 = 0x01d, + .luma_k = 0, + .coeff_ap = 0x0e6, + .coeff_am = 0x0e6, + .coeff_cp = 0x0b3, + .coeff_cm = 0x0b3, + .coeff_dp = 0xfb3, + .coeff_dm = 0xfb3, + .kcb = 128, + .kcr = 128, + }, + .color_correct = { + .header.type = CAMSS_PARAMS_COLOR_CORRECT, + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_DISABLE | + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY, + } +}; + +/* -------- Job scheduling -------- */ +static bool ope_job_ready(void *priv) +{ + struct ope_ctx *ctx = priv; + + return ctx->started && + camss_isp_bufq_num_ready(ctx->bufq, OPE_QUEUE_FRAME_IN) >= 1 && + camss_isp_bufq_num_ready(ctx->bufq, OPE_QUEUE_FRAME_OUT) >= 1; +} + +static void ope_job_finish(struct ope_ctx *ctx, enum vb2_buffer_state state) +{ + struct vb2_v4l2_buffer *src, *dst, *params; + bool requeue = false; + + src = camss_isp_bufq_remove(ctx->bufq, OPE_QUEUE_FRAME_IN); + dst = camss_isp_bufq_remove(ctx->bufq, OPE_QUEUE_FRAME_OUT); + params = camss_isp_bufq_remove(ctx->bufq, OPE_QUEUE_PARAMS); + + if (dst) { + dst->sequence = ctx->fmt_in.sequence++; + dst->vb2_buf.timestamp = src->vb2_buf.timestamp; + } + + if (dst) + camss_isp_buf_done(dst, state); + if (src) + camss_isp_buf_done(src, state); + if (params) + camss_isp_buf_done(params, state); + + if (ope_job_ready(ctx) && state == VB2_BUF_STATE_DONE) + requeue = true; + + dev_dbg(ctx->ope->dev, "done ctx=%p continue=%s\n", ctx, requeue ? "yes" : "no"); + + ctx->ope->hw_ctx = NULL; + camss_isp_sched_job_finish(&ctx->ope->sched, &ctx->job, requeue); +} + +static dma_addr_t ope_buf_dma_addr(struct ope_ctx *ctx, unsigned int queue_idx) +{ + struct vb2_v4l2_buffer *vbuf = camss_isp_bufq_next(ctx->bufq, queue_idx); + + if (!vbuf) + return 0; + + return vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); +} + +static void ope_run_job(void *priv, bool ctx_changed) +{ + struct ope_ctx *ctx = priv; + struct ope_dev *ope = ctx->ope; + dma_addr_t src, dst; + + src = ope_buf_dma_addr(ctx, OPE_QUEUE_FRAME_IN); + dst = ope_buf_dma_addr(ctx, OPE_QUEUE_FRAME_OUT); + + if (!src || !dst) { + dev_warn(ope->dev, "Job cannot run, missing buffer\n"); + camss_isp_sched_job_finish(&ope->sched, &ctx->job, false); + return; + } + + dev_dbg(ope->dev, "start ctx=%p->%p src=%pad dst=%pad\n", + ope->hw_ctx, ctx, &src, &dst); + + ope_apply_params(ctx); + ope_gen_stripes(ctx, src, dst); + + ope_prog_wb(ctx, ctx_changed); + ope_prog_bayer2rgb(ctx, ctx_changed); + ope_prog_color_correct(ctx, ctx_changed); + ope_prog_rgb2yuv(ctx, ctx_changed); + + ctx->current_stripe = 0; + ope->hw_ctx = ctx; + ope_prog_stripe(ctx, &ctx->stripe[0]); + + ope_start(ope); +} + +static void ope_abort_job(void *priv) +{ + struct ope_ctx *ctx = priv; + + dev_dbg(ctx->ope->dev, "job abort ctx=%p\n", ctx); + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + + /* Scheduler will wait for completion (on reset complete isr) */ +} + +static void ope_try_schedule(struct ope_ctx *ctx) +{ + camss_isp_sched_try_run(&ctx->ope->sched, &ctx->job); +} + +static const struct camss_isp_job_ops ope_job_ops = { + .ready = ope_job_ready, + .run = ope_run_job, + .abort = ope_abort_job, +}; + +/* -------- Interrupt handlers -------- */ + +static void ope_fe_irq(struct ope_dev *ope) +{ + u32 status = readl_relaxed(ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_STATUS); + + writel_relaxed(status, ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_CLEAR); + writel_relaxed(OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR, + ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_CMD); +} + +static void ope_we_irq(struct ope_dev *ope, struct ope_ctx *ctx) +{ + u32 status; + + status = ope_read_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0); + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0, status); + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CMD, + OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR); + + if (!ctx) + return; + + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL) { + dev_err_ratelimited(ope->dev, + "Write Engine: configuration constraint violation\n"); + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + } + + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL) { + u32 viol = ope_read_wr(ope, OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS); + int i; + + for (i = 0; i < OPE_WR_CLIENT_MAX; i++) { + if (BIT(i) & viol) + dev_err_ratelimited(ope->dev, + "Write Engine WE%d: image size violation\n", i); + } + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + } + + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL) { + dev_err_ratelimited(ope->dev, "Write Engine: fatal violation\n"); + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + } + + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE) { + /* Register update done: program next stripe (double-buffered) */ + struct ope_stripe *stripe = ope_current_stripe(ctx); + + if (stripe && !ope_stripe_is_last(stripe)) + ope_prog_stripe(ctx, stripe + 1); + } +} + + +static void __ope_irq_init(struct ope_dev *ope) +{ + ope_write(ope, OPE_TOP_IRQ_MASK, + OPE_TOP_IRQ_STATUS_RST_DONE | + OPE_TOP_IRQ_STATUS_WE | + OPE_TOP_IRQ_STATUS_VIOL | + OPE_TOP_IRQ_STATUS_IDLE); + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_MASK_0, + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL | + OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL); +} + +static irqreturn_t ope_irq(int irq, void *dev_id) +{ + struct ope_dev *ope = dev_id; + struct ope_ctx *ctx; + u32 status; + + /* + * hw_ctx is safe to read here without a lock: ope_run_job() sets it + * before calling ope_start(), so the hardware cannot raise an IRQ + * before hw_ctx is visible. ope_job_finish() clears it in IRQ context + * (i.e. here), so the clear is serialised with this read by the IRQ + * itself. + */ + ctx = ope->hw_ctx; + + status = ope_read(ope, OPE_TOP_IRQ_STATUS); + ope_write(ope, OPE_TOP_IRQ_CLEAR, status); + ope_write(ope, OPE_TOP_IRQ_CMD, OPE_TOP_IRQ_CMD_CLEAR); + + if (status & OPE_TOP_IRQ_STATUS_RST_DONE) { + dev_dbg(ope->dev, "reset done ctx=%p\n", ctx); + if (ctx) + ope_job_finish(ctx, VB2_BUF_STATE_ERROR); + complete(&ope->reset_complete); + } + + if (status & OPE_TOP_IRQ_STATUS_VIOL) + dev_warn(ope->dev, "OPE violation: 0x%08x\n", + ope_read(ope, OPE_TOP_VIOLATION_STATUS)); + + if (status & OPE_TOP_IRQ_STATUS_FE) + ope_fe_irq(ope); + + if (status & OPE_TOP_IRQ_STATUS_WE) + ope_we_irq(ope, ctx); + + if ((status & OPE_TOP_IRQ_STATUS_IDLE) && ctx) { + struct ope_stripe *stripe = ope_current_stripe(ctx); + + dev_dbg(ope->dev, "stripe %u done ctx=%p\n", ctx->current_stripe, ctx); + + if (ope_stripe_is_last(stripe)) { + ctx->current_stripe = 0; + ope_job_finish(ctx, VB2_BUF_STATE_DONE); + } else { + ctx->current_stripe++; + ope_start(ope); + } + } + + return IRQ_HANDLED; +} + +/* -------- vb2 queue private data -------- */ + +struct ope_vq_priv { + struct ope_ctx *ctx; + unsigned int queue_idx; +}; + +static inline struct ope_ctx *ope_ctx_from_vq(struct vb2_queue *q) +{ + return ((struct ope_vq_priv *)vb2_get_drv_priv(q))->ctx; +} + +static inline unsigned int ope_idx_from_vq(struct vb2_queue *q) +{ + return ((struct ope_vq_priv *)vb2_get_drv_priv(q))->queue_idx; +} + +/* -------- vb2 ops -------- */ + +static int ope_queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ope_ctx *ctx = ope_ctx_from_vq(q); + unsigned int idx = ope_idx_from_vq(q); + unsigned int size; + + if (idx == OPE_QUEUE_FRAME_IN) + size = ctx->fmt_in.sizeimage ? ctx->fmt_in.sizeimage : PAGE_SIZE; + else if (idx == OPE_QUEUE_FRAME_OUT) + size = ctx->fmt_out.sizeimage ? ctx->fmt_out.sizeimage : PAGE_SIZE; + else + size = v4l2_isp_params_buffer_size(CAMSS_PARAMS_MAX_PAYLOAD); + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < size) + return -EINVAL; + } else { + *nplanes = 1; + sizes[0] = size; + } + return 0; +} + +static int ope_buf_prepare(struct vb2_buffer *vb) +{ + struct ope_ctx *ctx = ope_ctx_from_vq(vb->vb2_queue); + unsigned int idx = ope_idx_from_vq(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + unsigned int sizeimage; + + if (idx == OPE_QUEUE_FRAME_IN) + sizeimage = ctx->fmt_in.sizeimage; + else if (idx == OPE_QUEUE_FRAME_OUT) + sizeimage = ctx->fmt_out.sizeimage; + else + sizeimage = v4l2_isp_params_buffer_size(CAMSS_PARAMS_MAX_PAYLOAD); + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < sizeimage) + return -EINVAL; + + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, 0, sizeimage); + + + return 0; +} + +static void ope_buf_queue(struct vb2_buffer *vb) +{ + struct ope_ctx *ctx = ope_ctx_from_vq(vb->vb2_queue); + unsigned int idx = ope_idx_from_vq(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + camss_isp_bufq_queue(ctx->bufq, idx, vbuf); + ope_try_schedule(ctx); +} + +/* ---- Power scaling ---------------------------------------------------- */ + +static inline unsigned long ope_pixclk(const struct ope_fmt_state *fs, + unsigned int fps) +{ + return (unsigned long)fs->width * fs->height * fps; +} + +static inline unsigned int ope_load_avg(const struct ope_fmt_state *fs, + unsigned int fps) +{ + return mult_frac(ope_pixclk(fs, fps), fs->fmt->depth, 1000) / 8; +} + +static inline unsigned int ope_load_peak(const struct ope_fmt_state *fs, + unsigned int fps) +{ + return ope_load_avg(fs, fps) * 2; +} + +static inline unsigned int ope_load_config(const struct ope_fmt_state *fs, + unsigned int fps) +{ + unsigned int stripe_count = fs->width / OPE_STRIPE_MAX_W + 1; + + /* In worst case we have ~50 32-bit registers to write */ + return mult_frac(stripe_count * 50 * 4, fps, 1000); +} + +static void ope_adjust_power(struct ope_dev *ope) +{ + unsigned int loadavg = 0, loadpeak = 0, loadconfig = 0; + unsigned long pixclk = 0; + struct dev_pm_opp *opp; + struct ope_ctx *ctx; + int ret; + + list_for_each_entry(ctx, &ope->ctx_list, list) { + unsigned int fps; + + if (!ctx->started) + continue; + + fps = ctx->framerate ? ctx->framerate : DEFAULT_FRAMERATE; + + pixclk += ope_pixclk(&ctx->fmt_in, fps); + loadavg += ope_load_avg(&ctx->fmt_in, fps); + loadavg += ope_load_avg(&ctx->fmt_out, fps); + loadpeak += ope_load_peak(&ctx->fmt_in, fps); + loadpeak += ope_load_peak(&ctx->fmt_out, fps); + loadconfig += ope_load_config(&ctx->fmt_in, fps); + } + + /* 30% margin for overhead */ + pixclk = mult_frac(pixclk, 13, 10); + + dev_dbg(ope->dev, "adjust power: clk=%luHz avg=%uKBps peak=%uKBps cfg=%uKBps\n", + pixclk, loadavg, loadpeak, loadconfig); + + opp = dev_pm_opp_find_freq_ceil(ope->dev, &pixclk); + if (IS_ERR(opp)) + dev_warn(ope->dev, "Requested pixel clock %luHz exceeds hardware limit\n", pixclk); + else + dev_pm_opp_put(opp); + + ret = dev_pm_opp_set_rate(ope->dev, pixclk); + if (ret) + dev_warn(ope->dev, "Failed to set OPP rate: %d\n", ret); + + ret = icc_set_bw(ope->icc_data, loadavg, loadpeak); + if (ret) + dev_warn(ope->dev, "Failed to set data path BW: %d\n", ret); + + ret = icc_set_bw(ope->icc_config, loadconfig, loadconfig * 5); + if (ret) + dev_warn(ope->dev, "Failed to set config path BW: %d\n", ret); +} + +static int ope_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ope_ctx *ctx = ope_ctx_from_vq(q); + unsigned int idx = ope_idx_from_vq(q); + int ret; + + dev_dbg(ctx->ope->dev, "streaming start ctx=%p queue=%u\n", ctx, idx); + + ret = pm_runtime_resume_and_get(ctx->ope->dev); + if (ret) { + dev_err(ctx->ope->dev, "resume failed (%d)\n", ret); + return ret; + } + + if (idx == OPE_QUEUE_FRAME_IN) { + ctx->fmt_in.sequence = 0; + ctx->started = true; + ope_adjust_power(ctx->ope); + __ope_irq_init(ctx->ope); + } + + ope_try_schedule(ctx); + + return 0; +} + +static void ope_stop_streaming(struct vb2_queue *q) +{ + struct ope_ctx *ctx = ope_ctx_from_vq(q); + unsigned int idx = ope_idx_from_vq(q); + + dev_dbg(ctx->ope->dev, "streaming stop ctx=%p queue=%u\n", ctx, idx); + + if (idx == OPE_QUEUE_FRAME_IN) { + ctx->started = false; + ope_adjust_power(ctx->ope); + } + + camss_isp_bufq_drain(ctx->bufq, idx, VB2_BUF_STATE_ERROR); + + pm_runtime_put(ctx->ope->dev); +} + +static const struct vb2_ops ope_vb2_ops = { + .queue_setup = ope_queue_setup, + .buf_prepare = ope_buf_prepare, + .buf_queue = ope_buf_queue, + .start_streaming = ope_start_streaming, + .stop_streaming = ope_stop_streaming, +}; + +static int ope_init_vq(struct ope_ctx *ctx, unsigned int idx) +{ + struct ope_dev *ope = ctx->ope; + struct ope_vq_priv *qpriv; + struct vb2_queue *q = &ctx->vqs[idx]; + + qpriv = devm_kzalloc(ope->dev, sizeof(*qpriv), GFP_KERNEL); + if (!qpriv) + return -ENOMEM; + + qpriv->ctx = ctx; + qpriv->queue_idx = idx; + q->drv_priv = qpriv; + q->ops = &ope_vb2_ops; + q->lock = &ctx->vbq_lock; + q->dev = ope->dev; + q->buf_struct_size = sizeof(struct camss_isp_buf); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + if (idx == OPE_QUEUE_PARAMS) { + q->type = V4L2_BUF_TYPE_META_OUTPUT; + q->mem_ops = &vb2_vmalloc_memops; + q->io_modes = VB2_MMAP | VB2_USERPTR; + } else if (idx == OPE_QUEUE_FRAME_OUT) { + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->mem_ops = &vb2_dma_contig_memops; + q->io_modes = VB2_MMAP | VB2_DMABUF; + } else { + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + q->mem_ops = &vb2_dma_contig_memops; + q->io_modes = VB2_MMAP | VB2_DMABUF; + } + + return vb2_queue_init(q); +} + +/* -------- Format helpers -------- */ + +static const struct ope_fmt *ope_find_fmt(const struct ope_fmt *fmts, + unsigned int n, u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < n; i++) + if (fmts[i].fourcc == fourcc) + return &fmts[i]; + return NULL; +} + +static const struct ope_fmt *ope_fmt_try(struct ope_dev *ope, bool is_output, + struct v4l2_pix_format_mplane *pix, + const struct ope_fmt_state *input) +{ + const struct ope_fmt *fmts = is_output ? ope_output_fmts : ope_input_fmts; + unsigned int n = is_output ? ARRAY_SIZE(ope_output_fmts) : ARRAY_SIZE(ope_input_fmts); + const struct ope_fmt *fmt; + unsigned int max_w = OPE_MAX_W, max_h = OPE_MAX_H; + + fmt = ope_find_fmt(fmts, n, pix->pixelformat); + if (!fmt) { + fmt = &fmts[0]; + pix->pixelformat = fmt->fourcc; + } + + if (is_output && input && input->fmt) { + max_w = min(max_w, input->width); + max_h = min(max_h, input->height); + } + + v4l_bound_align_image(&pix->width, OPE_MIN_W, max_w, fmt->align, + &pix->height, OPE_MIN_H, max_h, 0, 0); + + pix->num_planes = 1; + pix->field = V4L2_FIELD_NONE; + if (!pix->colorspace) + pix->colorspace = is_output ? V4L2_COLORSPACE_SRGB + : V4L2_COLORSPACE_RAW; + if (pix->plane_fmt[0].bytesperline < pix->width * fmt->depth / 8) + pix->plane_fmt[0].bytesperline = pix->width * fmt->depth / 8; + pix->plane_fmt[0].sizeimage = (u64)fmt->depth * pix->width * pix->height / 8; + + return fmt; +} + +/* -------- ioctl helpers/ops -------- */ + +static inline unsigned int ope_queue_idx_from_file(struct file *file) +{ + return (unsigned int)(uintptr_t)video_get_drvdata(video_devdata(file)); +} + +static struct vb2_queue *ope_vq_from_file(struct file *file, struct ope_ctx *ctx) +{ + unsigned int idx = ope_queue_idx_from_file(file); + + if (idx >= OPE_QUEUE_COUNT) + return NULL; + + return &ctx->vqs[idx]; +} + +static struct ope_ctx *ope_ctx_from_file(struct file *file) +{ + struct ope_dev *ope = container_of(video_devdata(file)->v4l2_dev, + struct ope_dev, v4l2_dev); + + return ope->shared_ctx; +} + +static int ope_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + strscpy(cap->driver, OPE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Qualcomm CAMSS OPE", sizeof(cap->card)); + + return 0; +} + +static int ope_enum_fmt_vid_out(struct file *file, void *priv, struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(ope_input_fmts)) + return -EINVAL; + + f->pixelformat = ope_input_fmts[f->index].fourcc; + + return 0; +} + +static int ope_g_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct ope_fmt_state *fs = &ctx->fmt_in; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + + pix->pixelformat = fs->fmt->fourcc; + pix->width = fs->width; + pix->height = fs->height; + pix->num_planes = 1; + pix->field = V4L2_FIELD_NONE; + pix->colorspace = fs->colorspace; + pix->xfer_func = fs->xfer_func; + pix->ycbcr_enc = fs->ycbcr_enc; + pix->quantization = fs->quantization; + pix->plane_fmt[0].bytesperline = fs->bytesperline; + pix->plane_fmt[0].sizeimage = fs->sizeimage; + + return 0; +} + +static int ope_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + + ope_fmt_try(ctx->ope, false, &f->fmt.pix_mp, NULL); + + return 0; +} + +static int ope_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + const struct ope_fmt *fmt; + + if (vb2_is_busy(&ctx->vqs[OPE_QUEUE_FRAME_IN])) + return -EBUSY; + + fmt = ope_fmt_try(ctx->ope, false, &f->fmt.pix_mp, NULL); + ctx->fmt_in.fmt = fmt; + ctx->fmt_in.width = f->fmt.pix_mp.width; + ctx->fmt_in.height = f->fmt.pix_mp.height; + ctx->fmt_in.bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline; + ctx->fmt_in.sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage; + ctx->fmt_in.colorspace = f->fmt.pix_mp.colorspace; + ctx->fmt_in.xfer_func = f->fmt.pix_mp.xfer_func; + ctx->fmt_in.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->fmt_in.quantization = f->fmt.pix_mp.quantization; + + return 0; +} + +static int ope_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(ope_output_fmts)) + return -EINVAL; + + f->pixelformat = ope_output_fmts[f->index].fourcc; + + return 0; +} + +static int ope_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct ope_fmt_state *fs = &ctx->fmt_out; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + + pix->pixelformat = fs->fmt->fourcc; + pix->width = fs->width; + pix->height = fs->height; + pix->num_planes = 1; + pix->field = V4L2_FIELD_NONE; + pix->colorspace = fs->colorspace; + pix->xfer_func = fs->xfer_func; + pix->ycbcr_enc = fs->ycbcr_enc; + pix->quantization = fs->quantization; + pix->plane_fmt[0].bytesperline = fs->bytesperline; + pix->plane_fmt[0].sizeimage = fs->sizeimage; + + return 0; +} + +static int ope_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + + ope_fmt_try(ctx->ope, true, &f->fmt.pix_mp, &ctx->fmt_in); + + return 0; +} + +static int ope_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + const struct ope_fmt *fmt; + + if (vb2_is_busy(&ctx->vqs[OPE_QUEUE_FRAME_OUT])) + return -EBUSY; + + fmt = ope_fmt_try(ctx->ope, true, &f->fmt.pix_mp, &ctx->fmt_in); + ctx->fmt_out.fmt = fmt; + ctx->fmt_out.width = f->fmt.pix_mp.width; + ctx->fmt_out.height = f->fmt.pix_mp.height; + ctx->fmt_out.bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline; + ctx->fmt_out.sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage; + ctx->fmt_out.colorspace = f->fmt.pix_mp.colorspace; + ctx->fmt_out.xfer_func = f->fmt.pix_mp.xfer_func; + ctx->fmt_out.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->fmt_out.quantization = f->fmt.pix_mp.quantization; + + return 0; +} + +static int ope_g_fmt_meta(struct file *file, void *priv, struct v4l2_format *f) +{ + f->fmt.meta.dataformat = V4L2_META_FMT_QCOM_ISP_PARAMS; + f->fmt.meta.buffersize = v4l2_isp_params_buffer_size(CAMSS_PARAMS_MAX_PAYLOAD); + + return 0; +} + +static int ope_enum_fmt_meta_out(struct file *file, void *priv, struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + + f->pixelformat = V4L2_META_FMT_QCOM_ISP_PARAMS; + + return 0; +} + +static int ope_g_parm(struct file *file, void *priv, struct v4l2_streamparm *sp) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + + if (!V4L2_TYPE_IS_OUTPUT(sp->type)) + return -EINVAL; + + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + sp->parm.output.timeperframe = ctx->fmt_in.timeperframe; + + return 0; +} + +static int ope_s_parm(struct file *file, void *priv, struct v4l2_streamparm *sp) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct v4l2_fract *tpf = &sp->parm.output.timeperframe; + + if (!V4L2_TYPE_IS_OUTPUT(sp->type)) + return -EINVAL; + + if (vb2_is_busy(&ctx->vqs[OPE_QUEUE_FRAME_IN])) + return -EBUSY; + + if (!tpf->denominator) + tpf->denominator = 1; + if (!tpf->numerator) + tpf->numerator = 1; + + ctx->fmt_in.timeperframe = *tpf; + ctx->framerate = tpf->denominator / tpf->numerator; + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + + ope_adjust_power(ctx->ope); + + return 0; +} + +static int ope_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) +{ + unsigned int idx = ope_queue_idx_from_file(file); + const struct ope_fmt *fmts; + unsigned int n; + + if (idx == OPE_QUEUE_FRAME_IN) { + fmts = ope_input_fmts; + n = ARRAY_SIZE(ope_input_fmts); + } else { + fmts = ope_output_fmts; + n = ARRAY_SIZE(ope_output_fmts); + } + + if (fsize->index > 0 || !ope_find_fmt(fmts, n, fsize->pixel_format)) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = OPE_MIN_W; + fsize->stepwise.max_width = OPE_MAX_W; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = OPE_MIN_H; + fsize->stepwise.max_height = OPE_MAX_H; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int ope_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *rb) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + int ret; + + if (!vq) + return -EINVAL; + + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + + ret = vb2_reqbufs(vq, rb); + if (!ret) + vq->owner = rb->count ? file->private_data : NULL; + + return ret; +} + +static int ope_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + return vq ? vb2_querybuf(vq, buf) : -EINVAL; +} + +static int ope_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct video_device *vdev = video_devdata(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + if (!vq) + return -EINVAL; + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + return vb2_qbuf(vq, vdev->v4l2_dev->mdev, buf); +} + +static int ope_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + return vq ? vb2_dqbuf(vq, buf, file->f_flags & O_NONBLOCK) : -EINVAL; +} + +static int ope_prepare_buf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct video_device *vdev = video_devdata(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + if (!vq) + return -EINVAL; + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + return vb2_prepare_buf(vq, vdev->v4l2_dev->mdev, buf); +} + +static int ope_create_bufs(struct file *file, void *priv, struct v4l2_create_buffers *create) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + int ret; + + if (!vq) + return -EINVAL; + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + ret = vb2_create_bufs(vq, create); + if (!ret && create->count) + vq->owner = file->private_data; + return ret; +} + +static int ope_expbuf(struct file *file, void *priv, struct v4l2_exportbuffer *eb) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + if (!vq) + return -EINVAL; + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + return vb2_expbuf(vq, eb); +} + +static int ope_streamon(struct file *file, void *priv, enum v4l2_buf_type type) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + if (!vq) + return -EINVAL; + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + return vb2_streamon(vq, type); +} + +static int ope_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) +{ + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct vb2_queue *vq = ope_vq_from_file(file, ctx); + + if (!vq) + return -EINVAL; + + if (vb2_queue_is_busy(vq, file)) + return -EBUSY; + + camss_isp_sched_cancel(&ctx->ope->sched, &ctx->job); + + return vb2_streamoff(vq, type); +} + +static const struct v4l2_ioctl_ops ope_video_ioctl_ops = { + .vidioc_querycap = ope_querycap, + .vidioc_enum_fmt_vid_out = ope_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = ope_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = ope_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = ope_s_fmt_vid_out, + .vidioc_enum_fmt_vid_cap = ope_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = ope_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = ope_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = ope_s_fmt_vid_cap, + .vidioc_enum_framesizes = ope_enum_framesizes, + .vidioc_g_parm = ope_g_parm, + .vidioc_s_parm = ope_s_parm, + .vidioc_reqbufs = ope_reqbufs, + .vidioc_querybuf = ope_querybuf, + .vidioc_qbuf = ope_qbuf, + .vidioc_dqbuf = ope_dqbuf, + .vidioc_prepare_buf = ope_prepare_buf, + .vidioc_create_bufs = ope_create_bufs, + .vidioc_expbuf = ope_expbuf, + .vidioc_streamon = ope_streamon, + .vidioc_streamoff = ope_streamoff, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_ioctl_ops ope_meta_ioctl_ops = { + .vidioc_querycap = ope_querycap, + .vidioc_enum_fmt_meta_out = ope_enum_fmt_meta_out, + .vidioc_g_fmt_meta_out = ope_g_fmt_meta, + .vidioc_s_fmt_meta_out = ope_g_fmt_meta, + .vidioc_try_fmt_meta_out = ope_g_fmt_meta, + .vidioc_reqbufs = ope_reqbufs, + .vidioc_querybuf = ope_querybuf, + .vidioc_qbuf = ope_qbuf, + .vidioc_dqbuf = ope_dqbuf, + .vidioc_prepare_buf = ope_prepare_buf, + .vidioc_create_bufs = ope_create_bufs, + .vidioc_expbuf = ope_expbuf, + .vidioc_streamon = ope_streamon, + .vidioc_streamoff = ope_streamoff, +}; + +/* -------- File/Context ops -------- */ + +static struct ope_ctx *ope_ctx_create(struct ope_dev *ope) +{ + struct ope_ctx *ctx; + unsigned int i; + int ret; + + ctx = kvzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->ope = ope; + + mutex_init(&ctx->vbq_lock); + + ctx->bufq = camss_isp_bufq_init(OPE_QUEUE_COUNT); + if (IS_ERR(ctx->bufq)) { + ret = PTR_ERR(ctx->bufq); + kvfree(ctx); + return ERR_PTR(ret); + } + + camss_isp_job_init(&ctx->job, &ope_job_ops, ctx); + ctx->config = ope_default_config; + + /* Default input format */ + ctx->fmt_in.fmt = &ope_input_fmts[0]; + ctx->fmt_in.width = OPE_MIN_W; + ctx->fmt_in.height = OPE_MIN_H; + ctx->fmt_in.bytesperline = OPE_MIN_W * ope_input_fmts[0].depth / 8; + ctx->fmt_in.sizeimage = ctx->fmt_in.bytesperline * OPE_MIN_H; + ctx->fmt_in.colorspace = V4L2_COLORSPACE_RAW; + ctx->fmt_in.timeperframe.numerator = 1; + ctx->fmt_in.timeperframe.denominator = DEFAULT_FRAMERATE; + + /* Default output format */ + ctx->fmt_out.fmt = &ope_output_fmts[0]; + ctx->fmt_out.width = OPE_MIN_W; + ctx->fmt_out.height = OPE_MIN_H; + ctx->fmt_out.bytesperline = OPE_MIN_W * ope_output_fmts[0].depth / 8; + ctx->fmt_out.sizeimage = (u64)ope_output_fmts[0].depth * + OPE_MIN_W * OPE_MIN_H / 8; + ctx->fmt_out.colorspace = V4L2_COLORSPACE_SRGB; + + for (i = 0; i < OPE_QUEUE_COUNT; i++) { + ret = ope_init_vq(ctx, i); + if (ret) { + while (i--) + vb2_queue_release(&ctx->vqs[i]); + camss_isp_bufq_release(ctx->bufq); + kvfree(ctx); + return ERR_PTR(ret); + } + } + + INIT_LIST_HEAD(&ctx->list); + list_add(&ctx->list, &ope->ctx_list); + ope->shared_ctx = ctx; + + return ctx; +} + +static void ope_ctx_destroy(struct ope_ctx *ctx) +{ + struct ope_dev *ope = ctx->ope; + unsigned int i; + + list_del(&ctx->list); + camss_isp_sched_cancel(&ope->sched, &ctx->job); + for (i = 0; i < OPE_QUEUE_COUNT; i++) + vb2_queue_release(&ctx->vqs[i]); + camss_isp_bufq_release(ctx->bufq); + mutex_destroy(&ctx->vbq_lock); + kvfree(ctx); +} + +static int ope_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct ope_dev *ope = container_of(vdev->v4l2_dev, struct ope_dev, v4l2_dev); + struct ope_ctx *ctx; + struct v4l2_fh *fh; + int ret = 0; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) + return -ENOMEM; + + if (mutex_lock_interruptible(&ope->mutex)) { + kfree(fh); + return -ERESTARTSYS; + } + + /* + * For now, only a single shared context is supported, + * until media multi-context support is available. + */ + if (!ope->shared_ctx) { + ctx = ope_ctx_create(ope); + if (IS_ERR(ctx)) { + ret = PTR_ERR(ctx); + goto unlock; + } + } else { + ctx = ope->shared_ctx; + } + + v4l2_fh_init(fh, vdev); + v4l2_fh_add(fh, file); + ope->open_count++; + +unlock: + if (ret) + kfree(fh); + mutex_unlock(&ope->mutex); + return ret; +} + +static int ope_release(struct file *file) +{ + struct v4l2_fh *fh = file_to_v4l2_fh(file); + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct ope_dev *ope = ctx->ope; + + v4l2_fh_del(fh, file); + v4l2_fh_exit(fh); + kfree(fh); + + mutex_lock(&ope->mutex); + + if (--ope->open_count == 0) { + ope->shared_ctx = NULL; + mutex_unlock(&ope->mutex); + ope_ctx_destroy(ctx); + } else { + mutex_unlock(&ope->mutex); + } + + return 0; +} + +static __poll_t ope_poll(struct file *file, poll_table *wait) +{ + unsigned int idx = ope_queue_idx_from_file(file); + struct ope_ctx *ctx = ope_ctx_from_file(file); + struct v4l2_fh *fh = file_to_v4l2_fh(file); + struct vb2_queue *vq; + unsigned long flags; + __poll_t rc = 0; + + if (idx >= OPE_QUEUE_COUNT) + return EPOLLERR; + + vq = &ctx->vqs[idx]; + + poll_wait(file, &vq->done_wq, wait); + poll_wait(file, &fh->wait, wait); + + spin_lock_irqsave(&vq->done_lock, flags); + if (!list_empty(&vq->done_list)) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + rc |= EPOLLOUT | EPOLLWRNORM; + else + rc |= EPOLLIN | EPOLLRDNORM; + } + spin_unlock_irqrestore(&vq->done_lock, flags); + + if (v4l2_event_pending(fh)) + rc |= EPOLLPRI; + + return rc; +} + +static int ope_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned int idx = ope_queue_idx_from_file(file); + struct ope_ctx *ctx = ope_ctx_from_file(file); + + if (idx >= OPE_QUEUE_COUNT) + return -EINVAL; + + return vb2_mmap(&ctx->vqs[idx], vma); +} + +static const struct v4l2_file_operations ope_fops = { + .owner = THIS_MODULE, + .open = ope_open, + .release = ope_release, + .poll = ope_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = ope_mmap, +}; + +/* Pipeline descriptor */ +static const struct camss_isp_entity_desc ope_entity_descs[] = { + [OPE_QUEUE_FRAME_IN] = { + .name = "frame-input", + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE, + .function = MEDIA_ENT_F_IO_V4L, + .vdev.caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING, + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_FRAME_IN, + .vdev.fops = &ope_fops, + .vdev.ioctl_ops = &ope_video_ioctl_ops, + .pads = (const struct camss_isp_pad_desc[]) { + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_PROC, 0, 0 }, + { } + }, + }, + [OPE_QUEUE_FRAME_OUT] = { + .name = "frame-output", + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE, + .function = MEDIA_ENT_F_IO_V4L, + .vdev.caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING, + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_FRAME_OUT, + .vdev.fops = &ope_fops, + .vdev.ioctl_ops = &ope_video_ioctl_ops, + .pads = (const struct camss_isp_pad_desc[]) { + { MEDIA_PAD_FL_SINK, OPE_ENTITY_PROC, 2, 0 }, + { } + }, + }, + [OPE_QUEUE_PARAMS] = { + .name = "params", + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE, + .function = MEDIA_ENT_F_IO_V4L, + .vdev.caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING, + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_PARAMS, + .vdev.fops = &ope_fops, + .vdev.ioctl_ops = &ope_meta_ioctl_ops, + .pads = (const struct camss_isp_pad_desc[]) { + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_PROC, 1, 0 }, + { } + }, + }, + [OPE_ENTITY_PROC] = { + .name = "proc", + .obj_type = MEDIA_ENTITY_TYPE_BASE, + .function = MEDIA_ENT_F_PROC_VIDEO_ISP, + .pads = (const struct camss_isp_pad_desc[]) { + { MEDIA_PAD_FL_SINK, OPE_QUEUE_FRAME_IN, 0, 0 }, + { MEDIA_PAD_FL_SINK, OPE_QUEUE_PARAMS, 0, 0 }, + { MEDIA_PAD_FL_SOURCE, OPE_QUEUE_FRAME_OUT, 0, 0 }, + { } + }, + }, +}; + +static int ope_v4l2_init(struct ope_dev *ope) +{ + int ret; + + mutex_init(&ope->mutex); + INIT_LIST_HEAD(&ope->ctx_list); + camss_isp_sched_init(&ope->sched); + + ope->mdev.dev = ope->dev; + strscpy(ope->mdev.model, OPE_NAME, sizeof(ope->mdev.model)); + media_device_init(&ope->mdev); + ope->v4l2_dev.mdev = &ope->mdev; + + ret = v4l2_device_register(ope->dev, &ope->v4l2_dev); + if (ret) + goto err_mdev_cleanup; + + ret = media_device_register(&ope->mdev); + if (ret) + goto err_v4l2; + + ope->pipeline = camss_isp_pipeline_alloc(ARRAY_SIZE(ope_entity_descs)); + if (IS_ERR(ope->pipeline)) { + ret = PTR_ERR(ope->pipeline); + goto err_media; + } + + ret = camss_isp_pipeline_register(ope->pipeline, &ope->v4l2_dev, + ope_entity_descs, ARRAY_SIZE(ope_entity_descs)); + if (ret) { + camss_isp_pipeline_free(ope->pipeline); + ope->pipeline = NULL; + goto err_media; + } + + ope->pipeline->drv_priv = ope; + return 0; + +err_media: + media_device_unregister(&ope->mdev); +err_v4l2: + v4l2_device_unregister(&ope->v4l2_dev); +err_mdev_cleanup: + media_device_cleanup(&ope->mdev); + return ret; +} + +static void ope_v4l2_cleanup(struct ope_dev *ope) +{ + if (ope->pipeline) { + camss_isp_pipeline_unregister(ope->pipeline); + camss_isp_pipeline_free(ope->pipeline); + ope->pipeline = NULL; + } + media_device_unregister(&ope->mdev); + v4l2_device_unregister(&ope->v4l2_dev); + media_device_cleanup(&ope->mdev); + camss_isp_sched_destroy(&ope->sched); +} + +static int ope_soft_reset(struct ope_dev *ope) +{ + u32 version; + int ret; + + ret = pm_runtime_resume_and_get(ope->dev); + if (ret) + return dev_err_probe(ope->dev, ret, "resume failed\n"); + + version = ope_read(ope, OPE_TOP_HW_VERSION); + dev_dbg(ope->dev, "HW version %u.%u.%u\n", + (u32)FIELD_GET(OPE_TOP_HW_VERSION_GEN, version), + (u32)FIELD_GET(OPE_TOP_HW_VERSION_REV, version), + (u32)FIELD_GET(OPE_TOP_HW_VERSION_STEP, version)); + + reinit_completion(&ope->reset_complete); + ope_write(ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW); + + if (!wait_for_completion_timeout(&ope->reset_complete, + msecs_to_jiffies(OPE_RESET_TIMEOUT_MS))) { + dev_err(ope->dev, "Reset timeout\n"); + pm_runtime_put(ope->dev); + return -ETIMEDOUT; + } + + pm_runtime_put(ope->dev); + + return 0; +} + +static int ope_init_power(struct ope_dev *ope) +{ + struct dev_pm_domain_list *pmdomains; + struct device *dev = ope->dev; + int ret; + + ope->icc_data = devm_of_icc_get(dev, "data"); + if (IS_ERR(ope->icc_data)) + return dev_err_probe(dev, PTR_ERR(ope->icc_data), + "failed to get interconnect data path\n"); + + ope->icc_config = devm_of_icc_get(dev, "config"); + if (IS_ERR(ope->icc_config)) + return dev_err_probe(dev, PTR_ERR(ope->icc_config), + "failed to get interconnect config path\n"); + + devm_pm_domain_attach_list(dev, NULL, &pmdomains); + + ret = devm_pm_opp_set_clkname(dev, "core"); + if (ret) + return ret; + + ret = devm_pm_opp_of_add_table(dev); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "invalid OPP table\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = devm_pm_clk_create(dev); + if (ret) + return ret; + + ret = of_pm_clk_add_clks(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int ope_init_mmio(struct ope_dev *ope) +{ + struct platform_device *pdev = to_platform_device(ope->dev); + + ope->base = devm_platform_ioremap_resource_byname(pdev, "top"); + if (IS_ERR(ope->base)) + return PTR_ERR(ope->base); + + ope->base_rd = devm_platform_ioremap_resource_byname(pdev, "bus_read"); + if (IS_ERR(ope->base_rd)) + return PTR_ERR(ope->base_rd); + + ope->base_wr = devm_platform_ioremap_resource_byname(pdev, "bus_write"); + if (IS_ERR(ope->base_wr)) + return PTR_ERR(ope->base_wr); + + ope->base_pp = devm_platform_ioremap_resource_byname(pdev, "pipeline"); + if (IS_ERR(ope->base_pp)) + return PTR_ERR(ope->base_pp); + + return 0; +} + +static int ope_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ope_dev *ope; + int ret, irq; + + ope = devm_kzalloc(dev, sizeof(*ope), GFP_KERNEL); + if (!ope) + return -ENOMEM; + + ope->dev = dev; + init_completion(&ope->reset_complete); + + ret = ope_init_power(ope); + if (ret) + return dev_err_probe(dev, ret, "Power init failed\n"); + + ret = ope_init_mmio(ope); + if (ret) + return dev_err_probe(dev, ret, "MMIO init failed\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(dev, irq, "Unable to get IRQ\n"); + + ret = devm_request_irq(dev, irq, ope_irq, 0, "camss-ope", ope); + if (ret < 0) + return dev_err_probe(dev, ret, "Requesting IRQ failed\n"); + + ret = ope_soft_reset(ope); + if (ret) + return ret; + + ret = ope_v4l2_init(ope); + if (ret) + return dev_err_probe(dev, ret, "V4L2 init failed\n"); + + platform_set_drvdata(pdev, ope); + + return 0; +} + +static void ope_remove(struct platform_device *pdev) +{ + struct ope_dev *ope = platform_get_drvdata(pdev); + + ope_v4l2_cleanup(ope); +} + +static const struct of_device_id ope_dt_ids[] = { + { .compatible = "qcom,qcm2290-camss-ope" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ope_dt_ids); + +static const struct dev_pm_ops ope_pm_ops = { + SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) +}; + +static struct platform_driver ope_driver = { + .probe = ope_probe, + .remove = ope_remove, + .driver = { + .name = OPE_NAME, + .of_match_table = ope_dt_ids, + .pm = &ope_pm_ops, + }, +}; + +module_platform_driver(ope_driver); + +MODULE_DESCRIPTION("CAMSS Offline Processing Engine"); +MODULE_AUTHOR("Loic Poulain <loic.poulain@oss.qualcomm.com>"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v2 14/14] arm64: dts: qcom: agatti: Add OPE node 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain ` (12 preceding siblings ...) 2026-04-27 12:43 ` [PATCH v2 13/14] media: qcom: camss: Add CAMSS Offline Processing Engine driver Loic Poulain @ 2026-04-27 12:43 ` Loic Poulain 13 siblings, 0 replies; 33+ messages in thread From: Loic Poulain @ 2026-04-27 12:43 UTC (permalink / raw) To: Bryan O'Donoghue, Vladimir Zapolskiy, Loic Poulain, Mauro Carvalho Chehab, Kees Cook, Gustavo A. R. Silva, Bryan O'Donoghue, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio Cc: linux-media, linux-arm-msm, linux-kernel, linux-hardening, devicetree, laurent.pinchart, kieran.bingham Add the Offline Processing Engine (OPE) device tree node for the Agatti platform (QCM2290). The node describes the five register regions (top, bus_read, bus_write, pipeline, qos), clocks, interrupt, interconnects, IOMMU mappings, and OPP table. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> --- arch/arm64/boot/dts/qcom/agatti.dtsi | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/agatti.dtsi b/arch/arm64/boot/dts/qcom/agatti.dtsi index f9b46cf1c6462a89784429565e1636ce2ba68d73..ee0fe1832248eed8405968bf05d4c73dd6e8d13d 100644 --- a/arch/arm64/boot/dts/qcom/agatti.dtsi +++ b/arch/arm64/boot/dts/qcom/agatti.dtsi @@ -1919,6 +1919,10 @@ &bimc SLAVE_EBI1 RPM_ALWAYS_TAG>, power-domains = <&gcc GCC_CAMSS_TOP_GDSC>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; ports { @@ -1933,6 +1937,73 @@ port@1 { reg = <1>; }; }; + + camss_ope: isp@5c42400 { + compatible = "qcom,qcm2290-camss-ope"; + + reg = <0x0 0x5c42400 0x0 0x200>, + <0x0 0x5c42600 0x0 0x200>, + <0x0 0x5c42800 0x0 0x4400>, + <0x0 0x5c46c00 0x0 0x190>, + <0x0 0x5c46d90 0x0 0xa00>; + reg-names = "top", + "qos", + "pipeline", + "bus_read", + "bus_write"; + + clocks = <&gcc GCC_CAMSS_OPE_CLK>, + <&gcc GCC_CAMSS_OPE_AHB_CLK>, + <&gcc GCC_CAMSS_NRT_AXI_CLK>; + clock-names = "core", + "iface", + "data"; + + interrupts = <GIC_SPI 209 IRQ_TYPE_EDGE_RISING>; + + interconnects = <&bimc MASTER_APPSS_PROC RPM_ACTIVE_TAG + &config_noc SLAVE_CAMERA_CFG RPM_ACTIVE_TAG>, + <&mmnrt_virt MASTER_CAMNOC_SF RPM_ALWAYS_TAG + &bimc SLAVE_EBI1 RPM_ALWAYS_TAG>; + interconnect-names = "config", + "data"; + + iommus = <&apps_smmu 0x820 0x0>, + <&apps_smmu 0x840 0x0>; + + operating-points-v2 = <&ope_opp_table>; + power-domains = <&rpmpd QCM2290_VDDCX>; + + ope_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-19200000 { + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmpd_opp_min_svs>; + }; + + opp-200000000 { + opp-hz = /bits/ 64 <200000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp-266600000 { + opp-hz = /bits/ 64 <266600000>; + required-opps = <&rpmpd_opp_svs_plus>; + }; + + opp-465000000 { + opp-hz = /bits/ 64 <465000000>; + required-opps = <&rpmpd_opp_nom>; + }; + + opp-580000000 { + opp-hz = /bits/ 64 <580000000>; + required-opps = <&rpmpd_opp_turbo>; + turbo-mode; + }; + }; + }; }; mdss: display-subsystem@5e00000 { -- 2.34.1 ^ permalink raw reply related [flat|nested] 33+ messages in thread
end of thread, other threads:[~2026-04-28 11:41 UTC | newest] Thread overview: 33+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-27 12:43 [PATCH v2 00/14] media: qcom: camss: CAMSS Offline Processing Engine support Loic Poulain 2026-04-27 12:43 ` [PATCH v2 01/14] media: qcom: camss: Add support to populate sub-devices Loic Poulain 2026-04-28 6:43 ` Krzysztof Kozlowski 2026-04-28 11:41 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 02/14] media: qcom: camss: Add PM clock support and integrate with runtime PM Loic Poulain 2026-04-27 14:04 ` Konrad Dybcio 2026-04-27 12:43 ` [PATCH v2 03/14] media: qcom: camss: Add PM clock definitions for QCM2290 Loic Poulain 2026-04-27 12:43 ` [PATCH v2 04/14] media: qcom: camss: Drop top_ahb/axi from QCM2290 subdevice clocks Loic Poulain 2026-04-27 12:43 ` [PATCH v2 05/14] media: qcom: camss: Add camss-isp-bufq helper Loic Poulain 2026-04-28 6:46 ` Krzysztof Kozlowski 2026-04-28 6:56 ` Bryan O'Donoghue 2026-04-28 11:29 ` Loic Poulain 2026-04-27 12:43 ` [PATCH v2 06/14] media: qcom: camss: Add camss-isp-sched helper Loic Poulain 2026-04-27 12:43 ` [PATCH v2 07/14] media: qcom: camss: Add camss-isp-pipeline helper Loic Poulain 2026-04-27 12:43 ` [PATCH v2 08/14] media: qcom-camss: Add image formats for Qualcomm CAMSS parameters buffer Loic Poulain 2026-04-27 12:43 ` [PATCH v2 09/14] media: qcom: camss: Add camss-isp-params helper Loic Poulain 2026-04-27 12:43 ` [PATCH v2 10/14] dt-bindings: media: qcom: Add CAMSS Offline Processing Engine (OPE) Loic Poulain 2026-04-27 14:22 ` Konrad Dybcio 2026-04-27 20:33 ` Loic Poulain 2026-04-28 10:15 ` Konrad Dybcio 2026-04-28 6:31 ` Krzysztof Kozlowski 2026-04-27 12:43 ` [PATCH v2 11/14] dt-bindings: media: qcom,qcm2290-camss: Add OPE ISP subnode Loic Poulain 2026-04-28 6:35 ` Krzysztof Kozlowski 2026-04-28 9:50 ` Konrad Dybcio 2026-04-28 9:54 ` Krzysztof Kozlowski 2026-04-27 12:43 ` [PATCH v2 12/14] media: uapi: Add CAMSS ISP configuration definition Loic Poulain 2026-04-27 12:56 ` Konrad Dybcio 2026-04-27 20:08 ` Loic Poulain 2026-04-27 20:25 ` Laurent Pinchart 2026-04-27 21:01 ` Loic Poulain 2026-04-27 21:57 ` Bryan O'Donoghue 2026-04-27 12:43 ` [PATCH v2 13/14] media: qcom: camss: Add CAMSS Offline Processing Engine driver Loic Poulain 2026-04-27 12:43 ` [PATCH v2 14/14] arm64: dts: qcom: agatti: Add OPE node Loic Poulain
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox