2021-12-13 13:50:10

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 00/23] media: atmel: atmel-isc: implement media controller

This series is the v3 of the series that attempts to support media controller
in the atmel ISC and XISC drivers.
This series also includes the csi2dc driver which was previously sent in a
separate series:
https://www.spinics.net/lists/linux-media/msg181042.html
https://www.spinics.net/lists/linux-media/msg181044.html
The driver now addresses comments received in latest v5 series from last year.

The series includes some minor changes and fixes that improve the isc common
code base, like removing the enum frameintervals VIDIOC, fixing bytesperline
for planar formats, etc.

Many thanks to folks from libcamera who helped a lot with understanding
how a media controller driver should behave.

Feedback is welcome !

Changes in v3:
- change in bindings, small fixes in csi2dc driver and conversion to mc
for the isc-base.
- removed some MAINTAINERS patches and used patterns in MAINTAINERS

Changes in v2:
- integrated many changes suggested by Jacopo in the review of the v1 series.
- add a few new patches


Eugen Hristev (23):
MAINTAINERS: add microchip csi2dc
dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc
media: atmel: introduce microchip csi2dc driver
media: atmel: atmel-isc: split the clock code into separate source
file
media: atmel: atmel-isc: replace video device name with module name
media: atmel: atmel-sama7g5-isc: fix ispck leftover
media: atmel: atmel-isc-base: use streaming status when queueing
buffers
media: atmel: atmel-isc-base: remove frameintervals VIDIOC
media: atmel: atmel-isc-base: report frame sizes as full supported
range
media: atmel: atmel-isc-base: implement mbus_code support in enumfmt
media: atmel: atmel-isc-base: fix bytesperline value for planar
formats
media: atmel: atmel-isc: implement media controller
ARM: dts: at91: sama7g5: add nodes for video capture
ARM: configs: at91: sama7: add xisc and csi2dc
ARM: multi_v7_defconfig: add atmel video pipeline modules
media: atmel: atmel-sama5d2-isc: fix wrong mask in YUYV format check
media: atmel: atmel-isc-base: use mutex to lock awb workqueue from
streaming
media: atmel: atmel-isc-base: add wb debug messages
media: atmel: atmel-isc-base: clamp wb gain coefficients
media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo
media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats
media: atmel: atmel-isc: compact the controller formats list
media: atmel: atmel-isc: change format propagation to subdev into only
verification

.../bindings/media/microchip,csi2dc.yaml | 197 +++++
MAINTAINERS | 14 +-
arch/arm/boot/dts/sama7g5.dtsi | 48 ++
arch/arm/configs/multi_v7_defconfig | 3 +
arch/arm/configs/sama7_defconfig | 2 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/atmel/Kconfig | 15 +
drivers/media/platform/atmel/Makefile | 4 +-
drivers/media/platform/atmel/atmel-isc-base.c | 790 ++++++-----------
drivers/media/platform/atmel/atmel-isc-clk.c | 311 +++++++
drivers/media/platform/atmel/atmel-isc-mc.c | 287 +++++++
drivers/media/platform/atmel/atmel-isc.h | 49 ++
.../media/platform/atmel/atmel-sama5d2-isc.c | 59 +-
.../media/platform/atmel/atmel-sama7g5-isc.c | 72 +-
.../media/platform/atmel/microchip-csi2dc.c | 806 ++++++++++++++++++
15 files changed, 2085 insertions(+), 573 deletions(-)
create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c

--
2.25.1



2021-12-13 13:50:25

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 01/23] MAINTAINERS: add microchip csi2dc

Add Microchip CSI2DC driver in the list.

Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index b2239eaff8d3..afb74460e5c1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12468,6 +12468,13 @@ L: [email protected] (moderated for non-subscribers)
S: Supported
F: sound/soc/atmel

+MICROCHIP CSI2DC DRIVER
+M: Eugen Hristev <[email protected]>
+L: [email protected]
+S: Supported
+F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
+F: drivers/media/platform/atmel/microchip-csi2dc.c
+
MICROCHIP ECC DRIVER
M: Tudor Ambarus <[email protected]>
L: [email protected]
--
2.25.1


2021-12-13 13:50:54

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 02/23] dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc

Add bindings documentation for Microchip CSI2 Demultiplexer controller.

CSI2DC is a demultiplexer from Synopsys IDI interface specification to
parallel interface connection or direct memory access.
CSI2DC can also act as a parallel bypass from a parallel sensor to the
image sensor controller/interface.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v3:
- Changed to port base as suggested by Rob
- Added properties from video-interfaces which are being used by the csi2dc
- added mention to the parallel port type as input
- did some rephrasing and rewording

Changes in v2:
- changed the endpoint/port references as suggested by Rob

Changes in this version :
- fixed 'sink' name to be actually source.
- added dma properties and example with dma

Previous change log:
Changes in v5:
- modified bindings as per Rob Herring review

Changes in v4:
- Removed property for inter-line-delay and for clock continuous/non-continuous
- Removed virtual channel by reg for second endpoint

Changes in v3:
- Removed some text from description, as it was explained in the schema
- fixed other things as per Rob's review
- moved some text inside the schema, like the clock description

Changes in v2:
- fixed warnings reported by dt_binding_check


.../bindings/media/microchip,csi2dc.yaml | 197 ++++++++++++++++++
1 file changed, 197 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml

diff --git a/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
new file mode 100644
index 000000000000..e8544fb2d034
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
@@ -0,0 +1,197 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/microchip,csi2dc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip CSI2 Demux Controller (CSI2DC)
+
+maintainers:
+ - Eugen Hristev <[email protected]>
+
+description:
+ CSI2DC - Camera Serial Interface 2 Demux Controller
+
+ CSI2DC is a hardware block that receives incoming data from either from an
+ IDI interface or from a parallel bus interface.
+ It filters IDI packets based on their data type and virtual channel
+ identifier, then converts the byte stream to a pixel stream into a cross
+ clock domain towards a parallel interface that can be read by a sensor
+ controller.
+ IDI interface is Synopsys proprietary.
+ CSI2DC can act a simple bypass bridge if the incoming data is coming from
+ a parallel interface.
+
+ CSI2DC provides two pipes, one video pipe and one data pipe. Video pipe
+ is connected at the output to a sensor controller and the data pipe is
+ accessible as a DMA slave port to a DMA controller.
+
+ CSI2DC supports a single 'port' node as a sink port with either Synopsys
+ 32-bit IDI interface or a parallel interface.
+
+ CSI2DC supports one 'port' node as source port with parallel interface.
+ This is called video pipe.
+ This port has an 'endpoint' that can be connected to a sink port of another
+ controller (next in pipeline).
+
+ CSI2DC also supports direct access to the data through AHB, via DMA channel,
+ called data pipe.
+ For data pipe to be available, a dma controller and a dma channel must be
+ referenced.
+
+properties:
+ compatible:
+ const: microchip,sama7g5-csi2dc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ minItems: 2
+ maxItems: 2
+
+ clock-names:
+ description:
+ CSI2DC must have two clocks to function correctly. One clock is the
+ peripheral clock for the inside functionality of the hardware block.
+ This is named 'pclk'. The second clock must be the cross domain clock,
+ in which CSI2DC will perform clock crossing. This clock must be fed
+ by the next controller in pipeline, which usually is a sensor controller.
+ Normally this clock should be given by this sensor controller who
+ is also a clock source. This clock is named 'scck', sensor controller clock.
+ items:
+ - const: pclk
+ - const: scck
+
+ dmas:
+ maxItems: 1
+
+ dma-names:
+ const: rx
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description:
+ Input port node, single endpoint describing the input port.
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+ description: Endpoint connected to input device
+
+ properties:
+ bus-type:
+ enum: [4, 5, 6]
+ default: 4
+
+ bus-width:
+ enum: [8, 9, 10, 11, 12, 13, 14]
+ default: 14
+
+ clock-noncontinuous:
+ type: boolean
+ description:
+ Presence of this boolean property decides whether clock is
+ continuous or noncontinuous.
+
+ remote-endpoint: true
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description:
+ Output port node, single endpoint describing the output port.
+
+ properties:
+ endpoint:
+ unevaluatedProperties: false
+ $ref: video-interfaces.yaml#
+ description: Endpoint connected to output device
+
+ properties:
+ bus-type:
+ enum: [5, 6]
+ default: 5
+
+ bus-width:
+ enum: [8, 9, 10, 11, 12, 13, 14]
+ default: 14
+
+ remote-endpoint: true
+
+ required:
+ - port@0
+ - port@1
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - ports
+
+examples:
+ # Example for connecting to a parallel sensor controller block (video pipe)
+ # and the input is received from Synopsys IDI interface
+ - |
+ csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pclk>, <&scck>;
+ clock-names = "pclk", "scck";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>; /* must be 0, first child port */
+ csi2dc_in: endpoint { /* input from IDI interface */
+ bus-type = <4>; /* MIPI CSI2 D-PHY */
+ remote-endpoint = <&csi2host_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>; /* must be 1, second child port */
+ csi2dc_out: endpoint {
+ remote-endpoint = <&xisc_in>; /* output to sensor controller */
+ };
+ };
+ };
+ };
+
+ # Example for connecting to a DMA master as an AHB slave
+ # and the input is received from Synopsys IDI interface
+ - |
+ #include <dt-bindings/dma/at91.h>
+ csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pclk>, <&scck>;
+ clock-names = "pclk", "scck";
+ dmas = <&dma0 AT91_XDMAC_DT_PERID(34)>;
+ dma-names = "rx";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>; /* must be 0, first child port */
+ csi2dc_input: endpoint { /* input from IDI interface */
+ remote-endpoint = <&csi2host_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ };
+ };
+ };
+
+...
--
2.25.1


2021-12-13 13:51:01

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 03/23] media: atmel: introduce microchip csi2dc driver

Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
to a pixel stream that can be captured by a sensor controller.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v3:
- on set format, disable setting the source sink.
- removed useless request to pm_request_idle
- fixed YUV DT
- added last frame debug info
- removed writing read only VPCOL and VPROW registers
- minor cosmetic changes

Changes in v2:
- implement try formats
- added parallel mode
- moved to fwlink endpoints
- many other minor corrections from Jacopo's review

Changes in this revision:
- addressed comments by Jacopo and Laurent as in this thread:
https://www.spinics.net/lists/linux-media/msg181044.html

Previous change log :
Changes in v5:
- only in bindings

Changes in v4:
- now using get_mbus_config ops to get data from the subdevice, like the
virtual channel id, and the clock type.
- now having possibility to select any of the RAW10 data modes
- at completion time, select which formats are also available in the subdevice,
and move to the dynamic list accordingly
- changed the pipeline integration, do not advertise subdev ready at probe time.
wait until completion is done, and then start a workqueue that will register
this device as a subdevice for the next element in pipeline.
- moved the s_power code into a different function called now csi2dc_power
that is called with CONFIG_PM functions. This is also called at completion,
to have the device ready in case CONFIG_PM is not selected on the platform.
- merged try_fmt into set_fmt
- driver cleanup, wrapped lines over 80 characters

Changes in v2:
- moved driver to platform/atmel
- fixed minor things as per Sakari's review
- still some things from v2 review are not yet addressed, to be followed up



drivers/media/platform/Makefile | 1 +
drivers/media/platform/atmel/Kconfig | 15 +
drivers/media/platform/atmel/Makefile | 1 +
.../media/platform/atmel/microchip-csi2dc.c | 806 ++++++++++++++++++
4 files changed, 823 insertions(+)
create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c

diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 832357240e89..b18e5f704145 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
+obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/

obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/

diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
index dda2f27da317..f83bee373d82 100644
--- a/drivers/media/platform/atmel/Kconfig
+++ b/drivers/media/platform/atmel/Kconfig
@@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
help
This module makes the ATMEL Image Sensor Interface available
as a v4l2 device.
+
+config VIDEO_MICROCHIP_CSI2DC
+ tristate "Microchip CSI2 Demux Controller"
+ depends on VIDEO_V4L2 && COMMON_CLK && OF
+ depends on ARCH_AT91 || COMPILE_TEST
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ CSI2 Demux Controller driver. CSI2DC is a helper chip
+ that converts IDI interface byte stream to a parallel pixel stream.
+ It supports various RAW formats as input.
+
+ To compile this driver as a module, choose M here: the
+ module will be called microchip-csi2dc.
diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 46d264ab7948..39f0a7eba702 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
+obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
new file mode 100644
index 000000000000..6bc549c28e05
--- /dev/null
+++ b/drivers/media/platform/atmel/microchip-csi2dc.c
@@ -0,0 +1,806 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip CSI2 Demux Controller (CSI2DC) driver
+ *
+ * Copyright (C) 2018 Microchip Technology, Inc.
+ *
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Global configuration register */
+#define CSI2DC_GCFG 0x0
+
+/* MIPI sensor pixel clock is free running */
+#define CSI2DC_GCFG_MIPIFRN BIT(0)
+/* GPIO parallel interface selection */
+#define CSI2DC_GCFG_GPIOSEL BIT(1)
+/* Output waveform inter-line minimum delay */
+#define CSI2DC_GCFG_HLC(v) ((v) << 4)
+#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
+/* SAMA7G5 requires a HLC delay of 15 */
+#define SAMA7G5_HLC (15)
+
+/* Global control register */
+#define CSI2DC_GCTLR 0x04
+#define CSI2DC_GCTLR_SWRST BIT(0)
+
+/* Global status register */
+#define CSI2DC_GS 0x08
+
+/* SSP interrupt status register */
+#define CSI2DC_SSPIS 0x28
+/* Pipe update register */
+#define CSI2DC_PU 0xc0
+/* Video pipe attributes update */
+#define CSI2DC_PU_VP BIT(0)
+
+/* Pipe update status register */
+#define CSI2DC_PUS 0xc4
+
+/* Video pipeline Interrupt Status Register */
+#define CSI2DC_VPISR 0xf4
+
+/* Video pipeline enable register */
+#define CSI2DC_VPE 0xf8
+#define CSI2DC_VPE_ENABLE BIT(0)
+
+/* Video pipeline configuration register */
+#define CSI2DC_VPCFG 0xfc
+/* Data type */
+#define CSI2DC_VPCFG_DT(v) ((v) << 0)
+#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
+/* Virtual channel identifier */
+#define CSI2DC_VPCFG_VC(v) ((v) << 6)
+#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
+/* Decompression enable */
+#define CSI2DC_VPCFG_DE BIT(8)
+/* Decoder mode */
+#define CSI2DC_VPCFG_DM(v) ((v) << 9)
+#define CSI2DC_VPCFG_DM_DECODER8TO12 0
+/* Decoder predictor 2 selection */
+#define CSI2DC_VPCFG_DP2 BIT(12)
+/* Recommended memory storage */
+#define CSI2DC_VPCFG_RMS BIT(13)
+/* Post adjustment */
+#define CSI2DC_VPCFG_PA BIT(14)
+
+/* Video pipeline column register */
+#define CSI2DC_VPCOL 0x100
+/* Column number */
+#define CSI2DC_VPCOL_COL(v) ((v) << 0)
+#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
+
+/* Video pipeline row register */
+#define CSI2DC_VPROW 0x104
+/* Row number */
+#define CSI2DC_VPROW_ROW(v) ((v) << 0)
+#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
+
+/* Version register */
+#define CSI2DC_VERSION 0x1fc
+
+/* register read/write helpers */
+#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
+#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
+ (st)->base + (reg))
+
+/* supported RAW data types */
+#define CSI2DC_DT_RAW6 0x28
+#define CSI2DC_DT_RAW7 0x29
+#define CSI2DC_DT_RAW8 0x2a
+#define CSI2DC_DT_RAW10 0x2b
+#define CSI2DC_DT_RAW12 0x2c
+#define CSI2DC_DT_RAW14 0x2d
+/* YUV data types */
+#define CSI2DC_DT_YUV422_8B 0x1e
+
+/*
+ * struct csi2dc_format - CSI2DC format type struct
+ * @mbus_code: Media bus code for the format
+ * @dt: Data type constant for this format
+ */
+struct csi2dc_format {
+ u32 mbus_code;
+ u32 dt;
+};
+
+static const struct csi2dc_format csi2dc_formats[] = {
+ {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .dt = CSI2DC_DT_YUV422_8B,
+ },
+};
+
+enum mipi_csi_pads {
+ CSI2DC_PAD_SINK = 0,
+ CSI2DC_PAD_SOURCE = 1,
+ CSI2DC_PADS_NUM = 2,
+};
+
+/*
+ * struct csi2dc_device - CSI2DC device driver data/config struct
+ * @base: Register map base address
+ * @csi2dc_sd: v4l2 subdevice for the csi2dc device
+ * This is the subdevice that the csi2dc device itself
+ * registers in v4l2 subsystem
+ * @dev: struct device for this csi2dc device
+ * @pclk: Peripheral clock reference
+ * Input clock that clocks the hardware block internal
+ * logic
+ * @scck: Sensor Controller clock reference
+ * Input clock that is used to generate the pixel clock
+ * @format: Current saved format used in g/s fmt
+ * @cur_fmt: Current state format
+ * @try_fmt: Try format that is being tried
+ * @pads: Media entity pads for the csi2dc subdevice
+ * @clk_gated: Whether the clock is gated or free running
+ * @video_pipe: Whether video pipeline is configured
+ * @parallel_mode: The underlying subdevice is connected on a parallel bus
+ * @vc: Current set virtual channel
+ * @notifier: Async notifier that is used to bound the underlying
+ * subdevice to the csi2dc subdevice
+ * @input_sd: Reference to the underlying subdevice bound to the
+ * csi2dc subdevice
+ * @remote_pad: Pad number of the underlying subdevice that is linked
+ * to the csi2dc subdevice sink pad.
+ */
+struct csi2dc_device {
+ void __iomem *base;
+ struct v4l2_subdev csi2dc_sd;
+ struct device *dev;
+ struct clk *pclk;
+ struct clk *scck;
+
+ struct v4l2_mbus_framefmt format;
+
+ const struct csi2dc_format *cur_fmt;
+ const struct csi2dc_format *try_fmt;
+
+ struct media_pad pads[CSI2DC_PADS_NUM];
+
+ bool clk_gated;
+ bool video_pipe;
+ bool parallel_mode;
+ u32 vc;
+
+ struct v4l2_async_notifier notifier;
+
+ struct v4l2_subdev *input_sd;
+
+ u32 remote_pad;
+};
+
+static inline struct csi2dc_device *
+csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
+{
+ return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
+}
+
+static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(csi2dc_formats))
+ return -EINVAL;
+
+ code->code = csi2dc_formats[code->index].mbus_code;
+
+ return 0;
+}
+
+static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+ format->pad);
+ format->format = *v4l2_try_fmt;
+
+ return 0;
+ }
+
+ format->format = csi2dc->format;
+
+ return 0;
+}
+
+static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *req_fmt)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ const struct csi2dc_format *fmt, *try_fmt = NULL;
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+ unsigned int i;
+
+ /*
+ * Setting the source pad is disabled.
+ * The same format is being propagated from the sink to source.
+ */
+ if (req_fmt->pad == CSI2DC_PAD_SOURCE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
+ fmt = &csi2dc_formats[i];
+ if (req_fmt->format.code == fmt->mbus_code)
+ try_fmt = fmt;
+ fmt++;
+ }
+
+ /* in case we could not find the desired format, default to something */
+ if (!try_fmt) {
+ try_fmt = &csi2dc_formats[0];
+
+ dev_dbg(csi2dc->dev,
+ "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
+ req_fmt->format.code, csi2dc_formats[0].mbus_code);
+ }
+
+ req_fmt->format.code = try_fmt->mbus_code;
+ req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+ req_fmt->format.field = V4L2_FIELD_NONE;
+
+ if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+ req_fmt->pad);
+ *v4l2_try_fmt = req_fmt->format;
+ /* Trying on the sink pad makes the source pad change too */
+ v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd,
+ sd_state,
+ CSI2DC_PAD_SOURCE);
+ *v4l2_try_fmt = req_fmt->format;
+
+ /* if we are just trying, we are done */
+ return 0;
+ }
+
+ /* save the format for later requests */
+ csi2dc->format = req_fmt->format;
+
+ /* update config */
+ csi2dc->cur_fmt = try_fmt;
+
+ dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
+ csi2dc->format.code, csi2dc->format.width,
+ csi2dc->format.height);
+
+ return 0;
+}
+
+static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
+{
+ int ret = 0;
+
+ if (on) {
+ ret = clk_prepare_enable(csi2dc->pclk);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(csi2dc->scck);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
+ clk_disable_unprepare(csi2dc->pclk);
+ return ret;
+ }
+
+ /* if powering up, deassert reset line */
+ csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
+ } else {
+ /* if powering down, assert reset line */
+ csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
+
+ clk_disable_unprepare(csi2dc->scck);
+ clk_disable_unprepare(csi2dc->pclk);
+ }
+
+ return ret;
+}
+
+static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
+{
+ struct v4l2_mbus_config mbus_config = { 0 };
+ int ret;
+
+ ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
+ csi2dc->remote_pad, &mbus_config);
+ if (ret == -ENOIOCTLCMD) {
+ dev_dbg(csi2dc->dev,
+ "no remote mbus configuration available\n");
+ goto csi2dc_get_mbus_config_defaults;
+ }
+
+ if (ret) {
+ dev_err(csi2dc->dev,
+ "failed to get remote mbus configuration\n");
+ goto csi2dc_get_mbus_config_defaults;
+ }
+
+ if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
+ csi2dc->vc = 0;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
+ csi2dc->vc = 1;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
+ csi2dc->vc = 2;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
+ csi2dc->vc = 3;
+
+ dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
+
+ csi2dc->clk_gated = mbus_config.flags &
+ V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+ dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
+ csi2dc->clk_gated ? "gated" : "free running");
+
+ return 0;
+
+csi2dc_get_mbus_config_defaults:
+ csi2dc->vc = 0; /* Virtual ID 0 by default */
+
+ return 0;
+}
+
+static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
+{
+ u32 vp, gcfg;
+
+ if (!csi2dc->video_pipe) {
+ dev_err(csi2dc->dev, "video pipeline unavailable\n");
+ return;
+ }
+
+ if (csi2dc->parallel_mode) {
+ /* In parallel mode, GPIO parallel interface must be selected */
+ gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
+ gcfg |= CSI2DC_GCFG_GPIOSEL;
+ csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
+ return;
+ }
+
+ /* serial video pipeline */
+
+ csi2dc_writel(csi2dc, CSI2DC_GCFG,
+ (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
+ (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
+
+ vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
+ vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
+ vp &= ~CSI2DC_VPCFG_DE;
+ vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
+ vp &= ~CSI2DC_VPCFG_DP2;
+ vp &= ~CSI2DC_VPCFG_RMS;
+ vp |= CSI2DC_VPCFG_PA;
+
+ csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
+ csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
+ csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
+}
+
+static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ int ret;
+
+ if (enable) {
+ ret = pm_runtime_resume_and_get(csi2dc->dev);
+ if (ret < 0)
+ return ret;
+
+ csi2dc_get_mbus_config(csi2dc);
+
+ csi2dc_vp_update(csi2dc);
+
+ return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
+ true);
+ }
+
+ dev_dbg(csi2dc->dev,
+ "Last frame received: VPCOLR = %u, VPROWR= %u, VPISR = %x\n",
+ csi2dc_readl(csi2dc, CSI2DC_VPCOL),
+ csi2dc_readl(csi2dc, CSI2DC_VPROW),
+ csi2dc_readl(csi2dc, CSI2DC_VPISR));
+
+ /* stop streaming scenario */
+ ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
+
+ pm_runtime_put_sync(csi2dc->dev);
+
+ return ret;
+}
+
+static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_mbus_framefmt *v4l2_try_fmt =
+ v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
+
+ v4l2_try_fmt->height = 480;
+ v4l2_try_fmt->width = 640;
+ v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
+ v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ v4l2_try_fmt->field = V4L2_FIELD_NONE;
+ v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+ v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
+ .enum_mbus_code = csi2dc_enum_mbus_code,
+ .set_fmt = csi2dc_set_fmt,
+ .get_fmt = csi2dc_get_fmt,
+ .init_cfg = csi2dc_init_cfg,
+};
+
+static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
+ .s_stream = csi2dc_s_stream,
+};
+
+static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
+ .pad = &csi2dc_pad_ops,
+ .video = &csi2dc_video_ops,
+};
+
+static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct csi2dc_device *csi2dc = container_of(notifier,
+ struct csi2dc_device, notifier);
+ int pad;
+ int ret;
+
+ csi2dc->input_sd = subdev;
+
+ pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0) {
+ dev_err(csi2dc->dev, "Failed to find pad for %s\n",
+ subdev->name);
+ return pad;
+ }
+
+ csi2dc->remote_pad = pad;
+
+ ret = media_create_pad_link(&csi2dc->input_sd->entity,
+ csi2dc->remote_pad,
+ &csi2dc->csi2dc_sd.entity, 0,
+ MEDIA_LNK_FL_ENABLED);
+ if (ret) {
+ dev_err(csi2dc->dev,
+ "Failed to create pad link: %s to %s\n",
+ csi2dc->input_sd->entity.name,
+ csi2dc->csi2dc_sd.entity.name);
+ return ret;
+ }
+
+ dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
+ csi2dc->input_sd->name, csi2dc->remote_pad);
+
+ return ret;
+}
+
+static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
+ .bound = csi2dc_async_bound,
+};
+
+static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
+ struct fwnode_handle *input_fwnode)
+{
+ struct v4l2_async_subdev *asd;
+ int ret = 0;
+
+ v4l2_async_nf_init(&csi2dc->notifier);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
+ input_fwnode,
+ struct v4l2_async_subdev);
+
+ fwnode_handle_put(input_fwnode);
+
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ dev_err(csi2dc->dev,
+ "failed to add async notifier for node %pOF: %d\n",
+ to_of_node(input_fwnode), ret);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ return ret;
+ }
+
+ csi2dc->notifier.ops = &csi2dc_async_ops;
+
+ ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
+ &csi2dc->notifier);
+ if (ret) {
+ dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
+ ret);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ }
+
+ return ret;
+}
+
+static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
+ struct device_node *of_node)
+{
+ struct fwnode_handle *input_fwnode, *output_fwnode;
+ struct v4l2_fwnode_endpoint input_endpoint = { 0 },
+ output_endpoint = { 0 };
+ int ret;
+
+ input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
+ NULL);
+ if (!input_fwnode) {
+ dev_err(csi2dc->dev,
+ "missing port node at %pOF, input node is mandatory.\n",
+ of_node);
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
+ if (ret) {
+ dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
+ goto csi2dc_of_parse_err;
+ }
+
+ if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
+ input_endpoint.bus_type == V4L2_MBUS_BT656) {
+ csi2dc->parallel_mode = true;
+ dev_dbg(csi2dc->dev,
+ "subdevice connected on parallel interface\n");
+ }
+
+ if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
+ csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
+ V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+ dev_dbg(csi2dc->dev,
+ "subdevice connected on serial interface\n");
+ dev_dbg(csi2dc->dev, "DT: %s clock\n",
+ csi2dc->clk_gated ? "gated" : "free running");
+ }
+
+ output_fwnode = fwnode_graph_get_next_endpoint
+ (of_fwnode_handle(of_node), input_fwnode);
+
+ if (output_fwnode)
+ ret = v4l2_fwnode_endpoint_parse(output_fwnode,
+ &output_endpoint);
+
+ fwnode_handle_put(output_fwnode);
+
+ if (!output_fwnode || ret) {
+ dev_info(csi2dc->dev,
+ "missing output node at %pOF, data pipe available only.\n",
+ of_node);
+ } else {
+ if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
+ output_endpoint.bus_type != V4L2_MBUS_BT656) {
+ dev_err(csi2dc->dev,
+ "output port must be parallel/bt656.\n");
+ ret = -EINVAL;
+ goto csi2dc_of_parse_err;
+ }
+
+ csi2dc->video_pipe = true;
+
+ dev_dbg(csi2dc->dev,
+ "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
+ of_node, input_endpoint.base.port,
+ input_endpoint.base.id, output_endpoint.base.port,
+ output_endpoint.base.id);
+ }
+
+ /* prepare async notifier for subdevice completion */
+ return csi2dc_prepare_notifier(csi2dc, input_fwnode);
+
+csi2dc_of_parse_err:
+ fwnode_handle_put(input_fwnode);
+ return ret;
+}
+
+static void csi2dc_default_format(struct csi2dc_device *csi2dc)
+{
+ csi2dc->cur_fmt = &csi2dc_formats[0];
+
+ csi2dc->format.height = 480;
+ csi2dc->format.width = 640;
+ csi2dc->format.code = csi2dc_formats[0].mbus_code;
+ csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
+ csi2dc->format.field = V4L2_FIELD_NONE;
+ csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+}
+
+static int csi2dc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct csi2dc_device *csi2dc;
+ int ret = 0;
+ u32 ver;
+
+ csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
+ if (!csi2dc)
+ return -ENOMEM;
+
+ csi2dc->dev = dev;
+
+ csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(csi2dc->base)) {
+ dev_err(dev, "base address not set\n");
+ return PTR_ERR(csi2dc->base);
+ }
+
+ csi2dc->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(csi2dc->pclk)) {
+ ret = PTR_ERR(csi2dc->pclk);
+ dev_err(dev, "failed to get pclk: %d\n", ret);
+ return ret;
+ }
+
+ csi2dc->scck = devm_clk_get(dev, "scck");
+ if (IS_ERR(csi2dc->scck)) {
+ ret = PTR_ERR(csi2dc->scck);
+ dev_err(dev, "failed to get scck: %d\n", ret);
+ return ret;
+ }
+
+ v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
+
+ csi2dc->csi2dc_sd.owner = THIS_MODULE;
+ csi2dc->csi2dc_sd.dev = dev;
+ snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
+ "csi2dc");
+
+ csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+
+ platform_set_drvdata(pdev, csi2dc);
+
+ ret = csi2dc_of_parse(csi2dc, dev->of_node);
+ if (ret)
+ goto csi2dc_probe_cleanup_entity;
+
+ csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ if (csi2dc->video_pipe)
+ csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
+ csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
+ csi2dc->pads);
+ if (ret < 0) {
+ dev_err(dev, "media entity init failed\n");
+ goto csi2dc_probe_cleanup_notifier;
+ }
+
+ csi2dc_default_format(csi2dc);
+
+ /* turn power on to validate capabilities */
+ ret = csi2dc_power(csi2dc, true);
+ if (ret < 0)
+ goto csi2dc_probe_cleanup_notifier;
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
+
+ /*
+ * we must register the subdev after PM runtime has been requested,
+ * otherwise we might bound immediately and request pm_runtime_resume
+ * before runtime_enable.
+ */
+ ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to register the subdevice\n");
+ goto csi2dc_probe_cleanup_notifier;
+ }
+
+ dev_info(dev, "Microchip CSI2DC version %x\n", ver);
+
+ return 0;
+
+csi2dc_probe_cleanup_notifier:
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+csi2dc_probe_cleanup_entity:
+ media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+ return ret;
+}
+
+static int csi2dc_remove(struct platform_device *pdev)
+{
+ struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
+ v4l2_async_nf_unregister(&csi2dc->notifier);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+ return 0;
+}
+
+static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
+{
+ struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+ return csi2dc_power(csi2dc, false);
+}
+
+static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
+{
+ struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+ return csi2dc_power(csi2dc, true);
+}
+
+static const struct dev_pm_ops csi2dc_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
+};
+
+static const struct of_device_id csi2dc_of_match[] = {
+ { .compatible = "microchip,sama7g5-csi2dc" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, csi2dc_of_match);
+
+static struct platform_driver csi2dc_driver = {
+ .probe = csi2dc_probe,
+ .remove = csi2dc_remove,
+ .driver = {
+ .name = "microchip-csi2dc",
+ .pm = &csi2dc_dev_pm_ops,
+ .of_match_table = of_match_ptr(csi2dc_of_match),
+ },
+};
+
+module_platform_driver(csi2dc_driver);
+
+MODULE_AUTHOR("Eugen Hristev <[email protected]>");
+MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1


2021-12-13 13:51:19

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 04/23] media: atmel: atmel-isc: split the clock code into separate source file

The atmel-isc-base is getting crowded. Split the clock functions into
atmel-isc-clk.c.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v3:
- squash with maintainers, add patterns in maintainers

MAINTAINERS | 7 +-
drivers/media/platform/atmel/Makefile | 3 +-
drivers/media/platform/atmel/atmel-isc-base.c | 295 -----------------
drivers/media/platform/atmel/atmel-isc-clk.c | 311 ++++++++++++++++++
drivers/media/platform/atmel/atmel-isc.h | 7 +
5 files changed, 322 insertions(+), 301 deletions(-)
create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c

diff --git a/MAINTAINERS b/MAINTAINERS
index afb74460e5c1..6aa274129751 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12500,11 +12500,8 @@ L: [email protected]
S: Supported
F: Documentation/devicetree/bindings/media/atmel,isc.yaml
F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
-F: drivers/media/platform/atmel/atmel-isc-base.c
-F: drivers/media/platform/atmel/atmel-isc-regs.h
-F: drivers/media/platform/atmel/atmel-isc.h
-F: drivers/media/platform/atmel/atmel-sama5d2-isc.c
-F: drivers/media/platform/atmel/atmel-sama7g5-isc.c
+F: drivers/media/platform/atmel/atmel-isc*
+F: drivers/media/platform/atmel/atmel-sama*-isc*
F: include/linux/atmel-isc-media.h

MICROCHIP ISI DRIVER
diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 39f0a7eba702..794e8f739287 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -1,9 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
atmel-isc-objs = atmel-sama5d2-isc.o
atmel-xisc-objs = atmel-sama7g5-isc.o
+atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o

obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
-obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
+obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 660cd0ab6749..58f468d32613 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -8,10 +8,6 @@
* Author: Eugen Hristev <[email protected]>
*
*/
-
-#include <linux/clk.h>
-#include <linux/clkdev.h>
-#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/math64.h>
@@ -100,297 +96,6 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc)
}
}

-static int isc_wait_clk_stable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- struct regmap *regmap = isc_clk->regmap;
- unsigned long timeout = jiffies + usecs_to_jiffies(1000);
- unsigned int status;
-
- while (time_before(jiffies, timeout)) {
- regmap_read(regmap, ISC_CLKSR, &status);
- if (!(status & ISC_CLKSR_SIP))
- return 0;
-
- usleep_range(10, 250);
- }
-
- return -ETIMEDOUT;
-}
-
-static int isc_clk_prepare(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- int ret;
-
- ret = pm_runtime_resume_and_get(isc_clk->dev);
- if (ret < 0)
- return ret;
-
- return isc_wait_clk_stable(hw);
-}
-
-static void isc_clk_unprepare(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- isc_wait_clk_stable(hw);
-
- pm_runtime_put_sync(isc_clk->dev);
-}
-
-static int isc_clk_enable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 id = isc_clk->id;
- struct regmap *regmap = isc_clk->regmap;
- unsigned long flags;
- unsigned int status;
-
- dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
- __func__, id, isc_clk->div, isc_clk->parent_id);
-
- spin_lock_irqsave(&isc_clk->lock, flags);
- regmap_update_bits(regmap, ISC_CLKCFG,
- ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
- (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
- (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
-
- regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
- spin_unlock_irqrestore(&isc_clk->lock, flags);
-
- regmap_read(regmap, ISC_CLKSR, &status);
- if (status & ISC_CLK(id))
- return 0;
- else
- return -EINVAL;
-}
-
-static void isc_clk_disable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 id = isc_clk->id;
- unsigned long flags;
-
- spin_lock_irqsave(&isc_clk->lock, flags);
- regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
- spin_unlock_irqrestore(&isc_clk->lock, flags);
-}
-
-static int isc_clk_is_enabled(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 status;
- int ret;
-
- ret = pm_runtime_resume_and_get(isc_clk->dev);
- if (ret < 0)
- return 0;
-
- regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
-
- pm_runtime_put_sync(isc_clk->dev);
-
- return status & ISC_CLK(isc_clk->id) ? 1 : 0;
-}
-
-static unsigned long
-isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
-}
-
-static int isc_clk_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- long best_rate = -EINVAL;
- int best_diff = -1;
- unsigned int i, div;
-
- for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
- struct clk_hw *parent;
- unsigned long parent_rate;
-
- parent = clk_hw_get_parent_by_index(hw, i);
- if (!parent)
- continue;
-
- parent_rate = clk_hw_get_rate(parent);
- if (!parent_rate)
- continue;
-
- for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
- unsigned long rate;
- int diff;
-
- rate = DIV_ROUND_CLOSEST(parent_rate, div);
- diff = abs(req->rate - rate);
-
- if (best_diff < 0 || best_diff > diff) {
- best_rate = rate;
- best_diff = diff;
- req->best_parent_rate = parent_rate;
- req->best_parent_hw = parent;
- }
-
- if (!best_diff || rate < req->rate)
- break;
- }
-
- if (!best_diff)
- break;
- }
-
- dev_dbg(isc_clk->dev,
- "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
- __func__, best_rate,
- __clk_get_name((req->best_parent_hw)->clk),
- req->best_parent_rate);
-
- if (best_rate < 0)
- return best_rate;
-
- req->rate = best_rate;
-
- return 0;
-}
-
-static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- if (index >= clk_hw_get_num_parents(hw))
- return -EINVAL;
-
- isc_clk->parent_id = index;
-
- return 0;
-}
-
-static u8 isc_clk_get_parent(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- return isc_clk->parent_id;
-}
-
-static int isc_clk_set_rate(struct clk_hw *hw,
- unsigned long rate,
- unsigned long parent_rate)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 div;
-
- if (!rate)
- return -EINVAL;
-
- div = DIV_ROUND_CLOSEST(parent_rate, rate);
- if (div > (ISC_CLK_MAX_DIV + 1) || !div)
- return -EINVAL;
-
- isc_clk->div = div - 1;
-
- return 0;
-}
-
-static const struct clk_ops isc_clk_ops = {
- .prepare = isc_clk_prepare,
- .unprepare = isc_clk_unprepare,
- .enable = isc_clk_enable,
- .disable = isc_clk_disable,
- .is_enabled = isc_clk_is_enabled,
- .recalc_rate = isc_clk_recalc_rate,
- .determine_rate = isc_clk_determine_rate,
- .set_parent = isc_clk_set_parent,
- .get_parent = isc_clk_get_parent,
- .set_rate = isc_clk_set_rate,
-};
-
-static int isc_clk_register(struct isc_device *isc, unsigned int id)
-{
- struct regmap *regmap = isc->regmap;
- struct device_node *np = isc->dev->of_node;
- struct isc_clk *isc_clk;
- struct clk_init_data init;
- const char *clk_name = np->name;
- const char *parent_names[3];
- int num_parents;
-
- if (id == ISC_ISPCK && !isc->ispck_required)
- return 0;
-
- num_parents = of_clk_get_parent_count(np);
- if (num_parents < 1 || num_parents > 3)
- return -EINVAL;
-
- if (num_parents > 2 && id == ISC_ISPCK)
- num_parents = 2;
-
- of_clk_parent_fill(np, parent_names, num_parents);
-
- if (id == ISC_MCK)
- of_property_read_string(np, "clock-output-names", &clk_name);
- else
- clk_name = "isc-ispck";
-
- init.parent_names = parent_names;
- init.num_parents = num_parents;
- init.name = clk_name;
- init.ops = &isc_clk_ops;
- init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
-
- isc_clk = &isc->isc_clks[id];
- isc_clk->hw.init = &init;
- isc_clk->regmap = regmap;
- isc_clk->id = id;
- isc_clk->dev = isc->dev;
- spin_lock_init(&isc_clk->lock);
-
- isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
- if (IS_ERR(isc_clk->clk)) {
- dev_err(isc->dev, "%s: clock register fail\n", clk_name);
- return PTR_ERR(isc_clk->clk);
- } else if (id == ISC_MCK)
- of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
-
- return 0;
-}
-
-int isc_clk_init(struct isc_device *isc)
-{
- unsigned int i;
- int ret;
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
- isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
- ret = isc_clk_register(isc, i);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(isc_clk_init);
-
-void isc_clk_cleanup(struct isc_device *isc)
-{
- unsigned int i;
-
- of_clk_del_provider(isc->dev->of_node);
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
- struct isc_clk *isc_clk = &isc->isc_clks[i];
-
- if (!IS_ERR(isc_clk->clk))
- clk_unregister(isc_clk->clk);
- }
-}
-EXPORT_SYMBOL_GPL(isc_clk_cleanup);

static int isc_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/media/platform/atmel/atmel-isc-clk.c
new file mode 100644
index 000000000000..2059fe376b00
--- /dev/null
+++ b/drivers/media/platform/atmel/atmel-isc-clk.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip Image Sensor Controller (ISC) common clock driver setup
+ *
+ * Copyright (C) 2016 Microchip Technology, Inc.
+ *
+ * Author: Songjun Wu
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "atmel-isc-regs.h"
+#include "atmel-isc.h"
+
+static int isc_wait_clk_stable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ struct regmap *regmap = isc_clk->regmap;
+ unsigned long timeout = jiffies + usecs_to_jiffies(1000);
+ unsigned int status;
+
+ while (time_before(jiffies, timeout)) {
+ regmap_read(regmap, ISC_CLKSR, &status);
+ if (!(status & ISC_CLKSR_SIP))
+ return 0;
+
+ usleep_range(10, 250);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int isc_clk_prepare(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(isc_clk->dev);
+ if (ret < 0)
+ return ret;
+
+ return isc_wait_clk_stable(hw);
+}
+
+static void isc_clk_unprepare(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ isc_wait_clk_stable(hw);
+
+ pm_runtime_put_sync(isc_clk->dev);
+}
+
+static int isc_clk_enable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 id = isc_clk->id;
+ struct regmap *regmap = isc_clk->regmap;
+ unsigned long flags;
+ unsigned int status;
+
+ dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
+ __func__, id, isc_clk->div, isc_clk->parent_id);
+
+ spin_lock_irqsave(&isc_clk->lock, flags);
+ regmap_update_bits(regmap, ISC_CLKCFG,
+ ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
+ (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
+ (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
+
+ regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
+ spin_unlock_irqrestore(&isc_clk->lock, flags);
+
+ regmap_read(regmap, ISC_CLKSR, &status);
+ if (status & ISC_CLK(id))
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static void isc_clk_disable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 id = isc_clk->id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&isc_clk->lock, flags);
+ regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
+ spin_unlock_irqrestore(&isc_clk->lock, flags);
+}
+
+static int isc_clk_is_enabled(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 status;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(isc_clk->dev);
+ if (ret < 0)
+ return 0;
+
+ regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
+
+ pm_runtime_put_sync(isc_clk->dev);
+
+ return status & ISC_CLK(isc_clk->id) ? 1 : 0;
+}
+
+static unsigned long
+isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
+}
+
+static int isc_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ long best_rate = -EINVAL;
+ int best_diff = -1;
+ unsigned int i, div;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ struct clk_hw *parent;
+ unsigned long parent_rate;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ if (!parent_rate)
+ continue;
+
+ for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
+ unsigned long rate;
+ int diff;
+
+ rate = DIV_ROUND_CLOSEST(parent_rate, div);
+ diff = abs(req->rate - rate);
+
+ if (best_diff < 0 || best_diff > diff) {
+ best_rate = rate;
+ best_diff = diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+
+ if (!best_diff || rate < req->rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ dev_dbg(isc_clk->dev,
+ "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+
+ return 0;
+}
+
+static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ if (index >= clk_hw_get_num_parents(hw))
+ return -EINVAL;
+
+ isc_clk->parent_id = index;
+
+ return 0;
+}
+
+static u8 isc_clk_get_parent(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ return isc_clk->parent_id;
+}
+
+static int isc_clk_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > (ISC_CLK_MAX_DIV + 1) || !div)
+ return -EINVAL;
+
+ isc_clk->div = div - 1;
+
+ return 0;
+}
+
+static const struct clk_ops isc_clk_ops = {
+ .prepare = isc_clk_prepare,
+ .unprepare = isc_clk_unprepare,
+ .enable = isc_clk_enable,
+ .disable = isc_clk_disable,
+ .is_enabled = isc_clk_is_enabled,
+ .recalc_rate = isc_clk_recalc_rate,
+ .determine_rate = isc_clk_determine_rate,
+ .set_parent = isc_clk_set_parent,
+ .get_parent = isc_clk_get_parent,
+ .set_rate = isc_clk_set_rate,
+};
+
+static int isc_clk_register(struct isc_device *isc, unsigned int id)
+{
+ struct regmap *regmap = isc->regmap;
+ struct device_node *np = isc->dev->of_node;
+ struct isc_clk *isc_clk;
+ struct clk_init_data init;
+ const char *clk_name = np->name;
+ const char *parent_names[3];
+ int num_parents;
+
+ if (id == ISC_ISPCK && !isc->ispck_required)
+ return 0;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents < 1 || num_parents > 3)
+ return -EINVAL;
+
+ if (num_parents > 2 && id == ISC_ISPCK)
+ num_parents = 2;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ if (id == ISC_MCK)
+ of_property_read_string(np, "clock-output-names", &clk_name);
+ else
+ clk_name = "isc-ispck";
+
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.name = clk_name;
+ init.ops = &isc_clk_ops;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ isc_clk = &isc->isc_clks[id];
+ isc_clk->hw.init = &init;
+ isc_clk->regmap = regmap;
+ isc_clk->id = id;
+ isc_clk->dev = isc->dev;
+ spin_lock_init(&isc_clk->lock);
+
+ isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
+ if (IS_ERR(isc_clk->clk)) {
+ dev_err(isc->dev, "%s: clock register fail\n", clk_name);
+ return PTR_ERR(isc_clk->clk);
+ } else if (id == ISC_MCK) {
+ of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
+ }
+
+ return 0;
+}
+
+int isc_clk_init(struct isc_device *isc)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
+ isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
+ ret = isc_clk_register(isc, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(isc_clk_init);
+
+void isc_clk_cleanup(struct isc_device *isc)
+{
+ unsigned int i;
+
+ of_clk_del_provider(isc->dev->of_node);
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
+ struct isc_clk *isc_clk = &isc->isc_clks[i];
+
+ if (!IS_ERR(isc_clk->clk))
+ clk_unregister(isc_clk->clk);
+ }
+}
+EXPORT_SYMBOL_GPL(isc_clk_cleanup);
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 2bfcb135ef13..32448ccfc636 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -10,6 +10,13 @@
*/
#ifndef _ATMEL_ISC_H_

+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-dma-contig.h>
+
#define ISC_CLK_MAX_DIV 255

enum isc_clk_id {
--
2.25.1


2021-12-13 13:51:32

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 05/23] media: atmel: atmel-isc: replace video device name with module name

To have consistency with future media controller development,
replace the video device name with KBUILD_MODNAME.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 58f468d32613..26a6090f056c 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1886,7 +1886,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
}

/* Register video device */
- strscpy(vdev->name, "microchip-isc", sizeof(vdev->name));
+ strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->fops = &isc_fops;
vdev->ioctl_ops = &isc_ioctl_ops;
--
2.25.1


2021-12-13 13:51:34

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 06/23] media: atmel: atmel-sama7g5-isc: fix ispck leftover

The ispck is not used for sama7g5 variant of the ISC.
Calls to ispck have to be removed also from module insert/removal.

Fixes: d7f26849ed7c ("media: atmel: fix the ispck initialization")
Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-sama7g5-isc.c | 6 ------
1 file changed, 6 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 5d1c76f680f3..2b1082295c13 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -556,7 +556,6 @@ static int microchip_xisc_remove(struct platform_device *pdev)

v4l2_device_unregister(&isc->v4l2_dev);

- clk_disable_unprepare(isc->ispck);
clk_disable_unprepare(isc->hclock);

isc_clk_cleanup(isc);
@@ -568,7 +567,6 @@ static int __maybe_unused xisc_runtime_suspend(struct device *dev)
{
struct isc_device *isc = dev_get_drvdata(dev);

- clk_disable_unprepare(isc->ispck);
clk_disable_unprepare(isc->hclock);

return 0;
@@ -583,10 +581,6 @@ static int __maybe_unused xisc_runtime_resume(struct device *dev)
if (ret)
return ret;

- ret = clk_prepare_enable(isc->ispck);
- if (ret)
- clk_disable_unprepare(isc->hclock);
-
return ret;
}

--
2.25.1


2021-12-13 13:51:48

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 07/23] media: atmel: atmel-isc-base: use streaming status when queueing buffers

During experiments with libcamera, it looks like vb2_is_streaming returns
true before our start streaming is called.
Order of operations is streamon -> queue -> start_streaming
ISC would have started the DMA immediately when a buffer is being added
to the vbqueue if the queue is streaming.
It is more safe to start the DMA after the start streaming of the driver is
called.
Thus, even if vb2queue is streaming, add the buffer to the dma queue of the
driver instead of actually starting the DMA process, if the start streaming
has not been called yet.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 26a6090f056c..e6c9071c04f0 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -441,12 +441,14 @@ static void isc_buffer_queue(struct vb2_buffer *vb)
unsigned long flags;

spin_lock_irqsave(&isc->dma_queue_lock, flags);
- if (!isc->cur_frm && list_empty(&isc->dma_queue) &&
- vb2_is_streaming(vb->vb2_queue)) {
+
+ if (!isc->cur_frm && list_empty(&isc->dma_queue) && !isc->stop) {
isc->cur_frm = buf;
isc_start_dma(isc);
- } else
+ } else {
list_add_tail(&buf->list, &isc->dma_queue);
+ }
+
spin_unlock_irqrestore(&isc->dma_queue_lock, flags);
}

@@ -1014,7 +1016,7 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv,
{
struct isc_device *isc = video_drvdata(file);

- if (vb2_is_streaming(&isc->vb2_vidq))
+ if (!isc->stop)
return -EBUSY;

return isc_set_fmt(isc, f);
@@ -1536,7 +1538,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)

isc_update_awb_ctrls(isc);

- if (vb2_is_streaming(&isc->vb2_vidq)) {
+ if (!isc->stop) {
/*
* If we are streaming, we can update profile to
* have the new settings in place.
@@ -1552,8 +1554,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
}

/* if we have autowhitebalance on, start histogram procedure */
- if (ctrls->awb == ISC_WB_AUTO &&
- vb2_is_streaming(&isc->vb2_vidq) &&
+ if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
isc_set_histogram(isc, true);

@@ -1829,6 +1830,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
struct vb2_queue *q = &isc->vb2_vidq;
int ret = 0;

+ isc->stop = true;
+
INIT_WORK(&isc->awb_work, isc_awb_work);

ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev);
--
2.25.1


2021-12-13 13:52:14

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 08/23] media: atmel: atmel-isc-base: remove frameintervals VIDIOC

VIDIOC_ENUM_FRAMEINTERVALS is not recommended for a top video driver.
The frame rate is defined by the sensor subdevice, thus it can be queried
directly by anyone interested in the frame intervals.

Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 37 -------------------
1 file changed, 37 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index e6c9071c04f0..3389671fbfb9 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1107,42 +1107,6 @@ static int isc_enum_framesizes(struct file *file, void *fh,
return 0;
}

-static int isc_enum_frameintervals(struct file *file, void *fh,
- struct v4l2_frmivalenum *fival)
-{
- struct isc_device *isc = video_drvdata(file);
- struct v4l2_subdev_frame_interval_enum fie = {
- .code = isc->config.sd_format->mbus_code,
- .index = fival->index,
- .width = fival->width,
- .height = fival->height,
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
- int ret = -EINVAL;
- unsigned int i;
-
- for (i = 0; i < isc->num_user_formats; i++)
- if (isc->user_formats[i]->fourcc == fival->pixel_format)
- ret = 0;
-
- for (i = 0; i < isc->controller_formats_size; i++)
- if (isc->controller_formats[i].fourcc == fival->pixel_format)
- ret = 0;
-
- if (ret)
- return ret;
-
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
- enum_frame_interval, NULL, &fie);
- if (ret)
- return ret;
-
- fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
- fival->discrete = fie.interval;
-
- return 0;
-}
-
static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.vidioc_querycap = isc_querycap,
.vidioc_enum_fmt_vid_cap = isc_enum_fmt_vid_cap,
@@ -1167,7 +1131,6 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.vidioc_g_parm = isc_g_parm,
.vidioc_s_parm = isc_s_parm,
.vidioc_enum_framesizes = isc_enum_framesizes,
- .vidioc_enum_frameintervals = isc_enum_frameintervals,

.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
--
2.25.1


2021-12-13 13:52:22

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 09/23] media: atmel: atmel-isc-base: report frame sizes as full supported range

The ISC supports a full broad range of frame sizes.
Until now, the subdevice was queried for possible frame sizes and these
were reported to the user space.
However, the ISC should not care about which frame sizes the subdev supports,
as long as this frame size is supported.
Thus, report a continuous range from smallest frame size up to the max
resolution.

Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 22 +++++++++----------
1 file changed, 10 insertions(+), 12 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 3389671fbfb9..bf638d201b29 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1076,14 +1076,12 @@ static int isc_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct isc_device *isc = video_drvdata(file);
- struct v4l2_subdev_frame_size_enum fse = {
- .code = isc->config.sd_format->mbus_code,
- .index = fsize->index,
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
int ret = -EINVAL;
int i;

+ if (fsize->index)
+ return -EINVAL;
+
for (i = 0; i < isc->num_user_formats; i++)
if (isc->user_formats[i]->fourcc == fsize->pixel_format)
ret = 0;
@@ -1095,14 +1093,14 @@ static int isc_enum_framesizes(struct file *file, void *fh,
if (ret)
return ret;

- ret = v4l2_subdev_call(isc->current_subdev->sd, pad, enum_frame_size,
- NULL, &fse);
- if (ret)
- return ret;
+ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;

- fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
- fsize->discrete.width = fse.max_width;
- fsize->discrete.height = fse.max_height;
+ fsize->stepwise.min_width = 16;
+ fsize->stepwise.max_width = isc->max_width;
+ fsize->stepwise.min_height = 16;
+ fsize->stepwise.max_height = isc->max_height;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.step_height = 1;

return 0;
}
--
2.25.1


2021-12-13 13:52:33

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 10/23] media: atmel: atmel-isc-base: implement mbus_code support in enumfmt

If enumfmt is called with an mbus_code, the enumfmt handler should only
return the formats that are supported for this mbus_code.
To make it more easy to understand the formats, changed the report order
to report first the native formats, and after that the formats that the ISC
can convert to.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 51 ++++++++++++++++---
1 file changed, 43 insertions(+), 8 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index bf638d201b29..2cb8446ff90c 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -498,21 +498,56 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
u32 index = f->index;
u32 i, supported_index;

- if (index < isc->controller_formats_size) {
- f->pixelformat = isc->controller_formats[index].fourcc;
- return 0;
+ supported_index = 0;
+
+ for (i = 0; i < isc->formats_list_size; i++) {
+ if (!isc->formats_list[i].sd_support)
+ continue;
+ /*
+ * If specific mbus_code is requested, provide only
+ * supported formats with this mbus code
+ */
+ if (f->mbus_code && f->mbus_code !=
+ isc->formats_list[i].mbus_code)
+ continue;
+ if (supported_index == index) {
+ f->pixelformat = isc->formats_list[i].fourcc;
+ return 0;
+ }
+ supported_index++;
}

- index -= isc->controller_formats_size;
+ /*
+ * If the sensor does not support this mbus_code whatsoever,
+ * there is no reason to advertise any of our output formats
+ */
+ if (supported_index == 0)
+ return -EINVAL;
+
+ /*
+ * If the sensor uses a format that is not raw, then we cannot
+ * convert it to any of the formats that we usually can with a
+ * RAW sensor. Thus, do not advertise them.
+ */
+ if (!isc->config.sd_format ||
+ !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
+ return -EINVAL;

+ /*
+ * Iterate again through the formats that we can convert to.
+ * However, to avoid duplicates, skip the formats that
+ * the sensor already supports directly
+ */
+ index -= supported_index;
supported_index = 0;

- for (i = 0; i < isc->formats_list_size; i++) {
- if (!ISC_IS_FORMAT_RAW(isc->formats_list[i].mbus_code) ||
- !isc->formats_list[i].sd_support)
+ for (i = 0; i < isc->controller_formats_size; i++) {
+ /* if this format is already supported by sensor, skip it */
+ if (find_format_by_fourcc(isc, isc->controller_formats[i].fourcc))
continue;
if (supported_index == index) {
- f->pixelformat = isc->formats_list[i].fourcc;
+ f->pixelformat =
+ isc->controller_formats[i].fourcc;
return 0;
}
supported_index++;
--
2.25.1


2021-12-13 13:53:04

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 11/23] media: atmel: atmel-isc-base: fix bytesperline value for planar formats

The bytesperline field of the pixfmt should be only for the first plane
in case of planar formats like YUV420 or YUV422.
The bytesperline is used by the driver to compute the framesize.
We have to report a different bpp (bytes per pixel) to v4l2 in bytesperline
than the actual bpp.
For example for YUV420, the real bpp is 12, but the first plane has only
8 bpp. Thus we report a bytesperline 8*width instead of 12*width.
However, for real framezise we have to compute 12*width*height.
Hence added a new variable to hold this information and to correctly
compute the frame size.

Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 19 +++++++++++++++++--
drivers/media/platform/atmel/atmel-isc.h | 4 ++++
2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 2cb8446ff90c..d0542b97a391 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -654,6 +654,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 8;
+ isc->try_config.bpp_v4l2 = 8;
break;
case V4L2_PIX_FMT_SBGGR10:
case V4L2_PIX_FMT_SGBRG10:
@@ -663,6 +664,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_SBGGR12:
case V4L2_PIX_FMT_SGBRG12:
@@ -672,24 +674,28 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_RGB565:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ARGB444:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ARGB555:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ABGR32:
case V4L2_PIX_FMT_XBGR32:
@@ -697,42 +703,49 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 32;
+ isc->try_config.bpp_v4l2 = 32;
break;
case V4L2_PIX_FMT_YUV420:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
isc->try_config.bpp = 12;
+ isc->try_config.bpp_v4l2 = 8; /* only first plane */
break;
case V4L2_PIX_FMT_YUV422P:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 8; /* only first plane */
break;
case V4L2_PIX_FMT_YUYV:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_UYVY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_VYUY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_GREY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 8;
+ isc->try_config.bpp_v4l2 = 8;
break;
case V4L2_PIX_FMT_Y16:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH;
@@ -742,6 +755,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
default:
return -EINVAL;
@@ -990,8 +1004,9 @@ static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
pixfmt->height = isc->max_height;

pixfmt->field = V4L2_FIELD_NONE;
- pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp) >> 3;
- pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
+ pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
+ pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
+ pixfmt->height;

if (code)
*code = mbus_code;
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 32448ccfc636..07fa6dbf8460 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -102,6 +102,9 @@ struct isc_format {
configuration.
* @fourcc: Fourcc code for this format.
* @bpp: Bytes per pixel in the current format.
+ * @bpp_v4l2: Bytes per pixel in the current format, for v4l2.
+ This differs from 'bpp' in the sense that in planar
+ formats, it refers only to the first plane.
* @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging)
* @dcfg_imode: Configuration of the input of the DMA module
* @dctrl_dview: Configuration of the output of the DMA module
@@ -112,6 +115,7 @@ struct fmt_config {

u32 fourcc;
u8 bpp;
+ u8 bpp_v4l2;

u32 rlp_cfg_mode;
u32 dcfg_imode;
--
2.25.1


2021-12-13 13:53:17

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 12/23] media: atmel: atmel-isc: implement media controller

Implement the support for media-controller.
This means that the capabilities of the driver have changed and now
it also advertises the IO_MC .
The driver will register it's media device, and add the video entity to this
media device. The subdevices are registered to the same media device.
The ISC will have a base entity which is auto-detected as atmel_isc_base.
It will also register a subdevice that allows cropping of the incoming frame
to the maximum frame size supported by the ISC.
The ISC will create a link between the subdevice that is asynchronously
registered and the atmel_isc_scaler entity.
Then, the atmel_isc_scaler and atmel_isc_base are connected through another
link.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v2:
- implement try formats

drivers/media/platform/atmel/Makefile | 2 +-
drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
drivers/media/platform/atmel/atmel-isc.h | 36 +++
.../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
.../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
6 files changed, 370 insertions(+), 8 deletions(-)
create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c

diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 794e8f739287..aa7a18b428f6 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
atmel-isc-objs = atmel-sama5d2-isc.o
atmel-xisc-objs = atmel-sama7g5-isc.o
-atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
+atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o

obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index d0542b97a391..b0c3ed21f372 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
struct isc_device, v4l2_dev);
struct isc_subdev_entity *subdev_entity =
container_of(notifier, struct isc_subdev_entity, notifier);
+ int pad;

if (video_is_registered(&isc->video_dev)) {
v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
@@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,

subdev_entity->sd = subdev;

+ pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0) {
+ v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
+ subdev->name);
+ return pad;
+ }
+
+ isc->remote_pad = pad;
+
return 0;
}

@@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
v4l2_ctrl_handler_free(&isc->ctrls.handler);
}

-static struct isc_format *find_format_by_code(struct isc_device *isc,
- unsigned int code, int *index)
+struct isc_format *isc_find_format_by_code(struct isc_device *isc,
+ unsigned int code, int *index)
{
struct isc_format *fmt = &isc->formats_list[0];
unsigned int i;
@@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,

return NULL;
}
+EXPORT_SYMBOL_GPL(isc_find_format_by_code);

static int isc_formats_init(struct isc_device *isc)
{
@@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
NULL, &mbus_code)) {
mbus_code.index++;

- fmt = find_format_by_code(isc, mbus_code.code, &i);
+ fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
if (!fmt) {
v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
mbus_code.code);
@@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
vdev->queue = q;
vdev->lock = &isc->lock;
vdev->ctrl_handler = &isc->ctrls.handler;
- vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_IO_MC;
video_set_drvdata(vdev, isc);

ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
@@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
goto isc_async_complete_err;
}

+ ret = isc_mc_register(isc);
+ if (ret < 0)
+ goto isc_async_complete_unregister_device;
return 0;

+isc_async_complete_unregister_device:
+ video_unregister_device(vdev);
+
isc_async_complete_err:
mutex_destroy(&isc->lock);
return ret;
diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
new file mode 100644
index 000000000000..2aea81d77b2f
--- /dev/null
+++ b/drivers/media/platform/atmel/atmel-isc-mc.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip Image Sensor Controller (ISC) Media Controller support
+ *
+ * Copyright (C) 2021 Microchip Technology, Inc.
+ *
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "atmel-isc-regs.h"
+#include "atmel-isc.h"
+
+static const struct media_device_ops isc_media_ops = {
+};
+
+static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
+ format->pad);
+ format->format = *v4l2_try_fmt;
+
+ return 0;
+ }
+
+ format->format = isc->scaler_format;
+
+ return 0;
+}
+
+static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *req_fmt)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+ struct isc_format *fmt;
+ unsigned int i;
+
+ if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
+ v4l_bound_align_image
+ (&req_fmt->format.width, 16, isc->max_width, 0,
+ &req_fmt->format.height, 16, isc->max_height, 0, 0);
+ else
+ v4l_bound_align_image
+ (&req_fmt->format.width, 16, 10000, 0,
+ &req_fmt->format.height, 16, 10000, 0, 0);
+
+ req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+ req_fmt->format.field = V4L2_FIELD_NONE;
+ req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
+
+ if (!fmt)
+ fmt = &isc->formats_list[0];
+
+ req_fmt->format.code = fmt->mbus_code;
+
+ if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
+ req_fmt->pad);
+ *v4l2_try_fmt = req_fmt->format;
+ /* Trying on the pad sink makes the source sink change too */
+ if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
+ v4l2_try_fmt =
+ v4l2_subdev_get_try_format(sd, sd_state,
+ ISC_SCALER_PAD_SOURCE);
+ *v4l2_try_fmt = req_fmt->format;
+
+ v4l_bound_align_image(&v4l2_try_fmt->width,
+ 16, isc->max_width, 0,
+ &v4l2_try_fmt->height,
+ 16, isc->max_height, 0, 0);
+ }
+ /* if we are just trying, we are done */
+ return 0;
+ }
+
+ isc->scaler_format = req_fmt->format;
+
+ return 0;
+}
+
+static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ int supported_index = 0;
+ int i;
+
+ for (i = 0; i < isc->formats_list_size; i++) {
+ if (!isc->formats_list[i].sd_support)
+ continue;
+ if (supported_index == code->index) {
+ code->code = isc->formats_list[i].mbus_code;
+ return 0;
+ }
+ supported_index++;
+ }
+
+ return -EINVAL;
+}
+
+static int isc_scaler_g_sel(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+
+ if (sel->pad == ISC_SCALER_PAD_SOURCE)
+ return -EINVAL;
+
+ if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
+ sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ sel->r.height = isc->max_height;
+ sel->r.width = isc->max_width;
+
+ sel->r.left = 0;
+ sel->r.top = 0;
+
+ return 0;
+}
+
+static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_mbus_framefmt *v4l2_try_fmt =
+ v4l2_subdev_get_try_format(sd, sd_state, 0);
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+
+ *v4l2_try_fmt = isc->scaler_format;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
+ .enum_mbus_code = isc_scaler_enum_mbus_code,
+ .set_fmt = isc_scaler_set_fmt,
+ .get_fmt = isc_scaler_get_fmt,
+ .get_selection = isc_scaler_g_sel,
+ .init_cfg = isc_scaler_init_cfg,
+};
+
+static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
+ .pad = &isc_scaler_pad_ops,
+};
+
+static int isc_init_own_sd(struct isc_device *isc)
+{
+ int ret;
+
+ v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
+
+ isc->scaler_sd.owner = THIS_MODULE;
+ isc->scaler_sd.dev = isc->dev;
+ snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
+ "atmel_isc_scaler");
+
+ isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ isc->scaler_format.height = isc->max_height;
+ isc->scaler_format.width = isc->max_width;
+ isc->scaler_format.code = isc->formats_list[0].mbus_code;
+ isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
+ isc->scaler_format.field = V4L2_FIELD_NONE;
+ isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ ret = media_entity_pads_init(&isc->scaler_sd.entity,
+ ISC_SCALER_PADS_NUM,
+ isc->scaler_pads);
+ if (ret < 0) {
+ dev_err(isc->dev, "scaler sd media entity init failed\n");
+ return ret;
+ }
+ ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
+ if (ret < 0) {
+ dev_err(isc->dev, "scaler sd failed to register subdev\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+int isc_mc_init(struct isc_device *isc, u32 ver)
+{
+ const struct of_device_id *match;
+ int ret;
+
+ isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
+ isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
+ isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+
+ ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
+ isc->pads);
+ if (ret < 0) {
+ dev_err(isc->dev, "media entity init failed\n");
+ return ret;
+ }
+
+ isc->mdev.dev = isc->dev;
+ isc->mdev.ops = &isc_media_ops;
+
+ match = of_match_node(isc->dev->driver->of_match_table,
+ isc->dev->of_node);
+
+ strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
+ sizeof(isc->mdev.driver_name));
+ strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
+ snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
+ isc->v4l2_dev.name);
+ isc->mdev.hw_revision = ver;
+
+ media_device_init(&isc->mdev);
+
+ isc->v4l2_dev.mdev = &isc->mdev;
+
+ return isc_init_own_sd(isc);
+}
+EXPORT_SYMBOL_GPL(isc_mc_init);
+
+int isc_mc_register(struct isc_device *isc)
+{
+ int ret;
+
+ ret = media_create_pad_link(&isc->current_subdev->sd->entity,
+ isc->remote_pad, &isc->scaler_sd.entity,
+ ISC_SCALER_PAD_SINK,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+
+ if (ret < 0) {
+ v4l2_err(&isc->v4l2_dev,
+ "Failed to create pad link: %s to %s\n",
+ isc->current_subdev->sd->entity.name,
+ isc->scaler_sd.entity.name);
+ return ret;
+ }
+
+ dev_dbg(isc->dev, "link with %s pad: %d\n",
+ isc->current_subdev->sd->name, isc->remote_pad);
+
+ ret = media_create_pad_link(&isc->scaler_sd.entity,
+ ISC_SCALER_PAD_SOURCE,
+ &isc->video_dev.entity, ISC_PAD_SINK,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+
+ if (ret < 0) {
+ v4l2_err(&isc->v4l2_dev,
+ "Failed to create pad link: %s to %s\n",
+ isc->scaler_sd.entity.name,
+ isc->video_dev.entity.name);
+ return ret;
+ }
+
+ dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
+ ISC_SCALER_PAD_SOURCE);
+
+ return media_device_register(&isc->mdev);
+}
+EXPORT_SYMBOL_GPL(isc_mc_register);
+
+void isc_mc_cleanup(struct isc_device *isc)
+{
+ media_entity_cleanup(&isc->video_dev.entity);
+}
+EXPORT_SYMBOL_GPL(isc_mc_cleanup);
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 07fa6dbf8460..0b6370d7775f 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -183,6 +183,17 @@ struct isc_reg_offsets {
u32 his_entry;
};

+enum isc_mc_pads {
+ ISC_PAD_SINK = 0,
+ ISC_PADS_NUM = 1,
+};
+
+enum isc_scaler_pads {
+ ISC_SCALER_PAD_SINK = 0,
+ ISC_SCALER_PAD_SOURCE = 1,
+ ISC_SCALER_PADS_NUM = 2,
+};
+
/*
* struct isc_device - ISC device driver data/config struct
* @regmap: Register map
@@ -258,6 +269,12 @@ struct isc_reg_offsets {
* be used as an input to the controller
* @controller_formats_size: size of controller_formats array
* @formats_list_size: size of formats_list array
+ * @pads: media controller pads for isc video entity
+ * @mdev: media device that is registered by the isc
+ * @remote_pad: remote pad on the connected subdevice
+ * @scaler_sd: subdevice for the scaler that isc registers
+ * @scaler_pads: media controller pads for the scaler subdevice
+ * @scaler_format: current format for the scaler subdevice
*/
struct isc_device {
struct regmap *regmap;
@@ -346,6 +363,19 @@ struct isc_device {
struct isc_format *formats_list;
u32 controller_formats_size;
u32 formats_list_size;
+
+ struct {
+ struct media_pad pads[ISC_PADS_NUM];
+ struct media_device mdev;
+
+ u32 remote_pad;
+ };
+
+ struct {
+ struct v4l2_subdev scaler_sd;
+ struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
+ struct v4l2_mbus_framefmt scaler_format;
+ };
};

extern const struct regmap_config isc_regmap_config;
@@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
void isc_subdev_cleanup(struct isc_device *isc);
void isc_clk_cleanup(struct isc_device *isc);

+int isc_mc_init(struct isc_device *isc, u32 ver);
+int isc_mc_register(struct isc_device *isc);
+void isc_mc_cleanup(struct isc_device *isc);
+
+struct isc_format *isc_find_format_by_code(struct isc_device *isc,
+ unsigned int code, int *index);
#endif
diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index 1b2063cce0f7..a3e4c32a66c4 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
break;
}

+ regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
+
+ ret = isc_mc_init(isc, ver);
+ if (ret < 0)
+ goto isc_probe_mc_init_err;
+
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_request_idle(dev);
@@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
ret = clk_prepare_enable(isc->ispck);
if (ret) {
dev_err(dev, "failed to enable ispck: %d\n", ret);
- goto cleanup_subdev;
+ goto isc_probe_mc_init_err;
}

/* ispck should be greater or equal to hclock */
@@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
goto unprepare_clk;
}

- regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
dev_info(dev, "Microchip ISC version %x\n", ver);

return 0;
@@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
unprepare_clk:
clk_disable_unprepare(isc->ispck);

+isc_probe_mc_init_err:
+ isc_mc_cleanup(isc);
+
cleanup_subdev:
isc_subdev_cleanup(isc);

@@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)

pm_runtime_disable(&pdev->dev);

+ isc_mc_cleanup(isc);
+
isc_subdev_cleanup(isc);

v4l2_device_unregister(&isc->v4l2_dev);
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 2b1082295c13..d2ac80b1ccc3 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
break;
}

+ regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
+
+ ret = isc_mc_init(isc, ver);
+ if (ret < 0)
+ goto isc_probe_mc_init_err;
+
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_request_idle(dev);

- regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
dev_info(dev, "Microchip XISC version %x\n", ver);

return 0;

+isc_probe_mc_init_err:
+ isc_mc_cleanup(isc);
+
cleanup_subdev:
isc_subdev_cleanup(isc);

@@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)

pm_runtime_disable(&pdev->dev);

+ isc_mc_cleanup(isc);
+
isc_subdev_cleanup(isc);

v4l2_device_unregister(&isc->v4l2_dev);
--
2.25.1


2021-12-13 13:53:26

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 13/23] ARM: dts: at91: sama7g5: add nodes for video capture

Add node for the XISC (eXtended Image Sensor Controller) and CSI2DC
(csi2 demux controller).
These nodes represent the top level of the video capture hardware pipeline
and are directly connected in hardware.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v3:
- change bus width for endpoints to the default 14

arch/arm/boot/dts/sama7g5.dtsi | 48 ++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)

diff --git a/arch/arm/boot/dts/sama7g5.dtsi b/arch/arm/boot/dts/sama7g5.dtsi
index 7039311bf678..4d0a93541d5f 100644
--- a/arch/arm/boot/dts/sama7g5.dtsi
+++ b/arch/arm/boot/dts/sama7g5.dtsi
@@ -236,6 +236,54 @@ sdmmc2: mmc@e120c000 {
status = "disabled";
};

+ csi2dc: csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 34>, <&xisc>;
+ clock-names = "pclk", "scck";
+ assigned-clocks = <&xisc>;
+ assigned-clock-rates = <266000000>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ csi2dc_in: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ csi2dc_out: endpoint {
+ bus-width = <14>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ remote-endpoint = <&xisc_in>;
+ };
+ };
+ };
+ };
+
+ xisc: xisc@e1408000 {
+ compatible = "microchip,sama7g5-isc";
+ reg = <0xe1408000 0x2000>;
+ interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 56>;
+ clock-names = "hclock";
+ #clock-cells = <0>;
+ clock-output-names = "isc-mck";
+
+ port {
+ xisc_in: endpoint {
+ bus-width = <14>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ remote-endpoint = <&csi2dc_out>;
+ };
+ };
+ };
+
pwm: pwm@e1604000 {
compatible = "microchip,sama7g5-pwm", "atmel,sama5d2-pwm";
reg = <0xe1604000 0x4000>;
--
2.25.1


2021-12-13 13:53:45

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 14/23] ARM: configs: at91: sama7: add xisc and csi2dc

Enable XISC and CSI2DC drivers.

Signed-off-by: Eugen Hristev <[email protected]>
---
arch/arm/configs/sama7_defconfig | 2 ++
1 file changed, 2 insertions(+)

diff --git a/arch/arm/configs/sama7_defconfig b/arch/arm/configs/sama7_defconfig
index 938aae4bd80b..15978f2ab4ea 100644
--- a/arch/arm/configs/sama7_defconfig
+++ b/arch/arm/configs/sama7_defconfig
@@ -126,6 +126,8 @@ CONFIG_MEDIA_SUPPORT_FILTER=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_MEDIA_PLATFORM_SUPPORT=y
CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_ATMEL_XISC=y
+CONFIG_VIDEO_MICROCHIP_CSI2DC=y
CONFIG_VIDEO_IMX219=m
CONFIG_VIDEO_IMX274=m
CONFIG_VIDEO_OV5647=m
--
2.25.1


2021-12-13 13:54:14

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 15/23] ARM: multi_v7_defconfig: add atmel video pipeline modules

Add drivers for the atmel video capture pipeline: atmel isc, xisc and
microchip csi2dc.

Signed-off-by: Eugen Hristev <[email protected]>
---
arch/arm/configs/multi_v7_defconfig | 3 +++
1 file changed, 3 insertions(+)

diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index c951aeed2138..92b7749a6df8 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -638,7 +638,10 @@ CONFIG_VIDEO_S5P_MIPI_CSIS=m
CONFIG_VIDEO_EXYNOS_FIMC_LITE=m
CONFIG_VIDEO_EXYNOS4_FIMC_IS=m
CONFIG_VIDEO_RCAR_VIN=m
+CONFIG_VIDEO_ATMEL_ISC=m
+CONFIG_VIDEO_ATMEL_XISC=m
CONFIG_VIDEO_ATMEL_ISI=m
+CONFIG_VIDEO_MICROCHIP_CSI2DC=m
CONFIG_V4L_MEM2MEM_DRIVERS=y
CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m
CONFIG_VIDEO_SAMSUNG_S5P_MFC=m
--
2.25.1


2021-12-13 13:54:26

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 16/23] media: atmel: atmel-sama5d2-isc: fix wrong mask in YUYV format check

While this does not happen in production, this check should be done
versus the mask, as checking with the YCYC value may not include
some bits that may be set.
Is it correct and safe to check the whole mask.

Fixes: 123aaf816b95 ("media: atmel: atmel-sama5d2-isc: fix YUYV format")
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-sama5d2-isc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index a3e4c32a66c4..a8d4ba60d3ac 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -267,7 +267,7 @@ static void isc_sama5d2_config_rlp(struct isc_device *isc)
* Thus, if the YCYC mode is selected, replace it with the
* sama5d2-compliant mode which is YYCC .
*/
- if ((rlp_mode & ISC_RLP_CFG_MODE_YCYC) == ISC_RLP_CFG_MODE_YCYC) {
+ if ((rlp_mode & ISC_RLP_CFG_MODE_MASK) == ISC_RLP_CFG_MODE_YCYC) {
rlp_mode &= ~ISC_RLP_CFG_MODE_MASK;
rlp_mode |= ISC_RLP_CFG_MODE_YYCC;
}
--
2.25.1


2021-12-13 13:54:54

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 17/23] media: atmel: atmel-isc-base: use mutex to lock awb workqueue from streaming

The AWB workqueue runs in a kernel thread and needs to be synchronized
w.r.t. the streaming status.
It is possible that streaming is stopped while the AWB workq is running.
In this case it is likely that the check for isc->stop is done at one point
in time, but the AWB computations are done later, including a call to
isc_update_profile, which requires streaming to be started.
Thus , isc_update_profile will fail if during this operation sequence the
streaming was stopped.
To solve this issue, a mutex is added, that will serialize the awb work and
streaming stopping, with the mention that either streaming is stopped
completely including termination of the last frame is done, and after that
the AWB work can check stream status and stop; either first AWB work is
completed and after that the streaming can stop correctly.
The awb spin lock cannot be used since this spinlock is taken in the same
context and using it in the stop streaming will result in a recursion BUG.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 31 ++++++++++++++++---
drivers/media/platform/atmel/atmel-isc.h | 1 +
2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index b0c3ed21f372..53cac1aac0fd 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -401,6 +401,7 @@ static void isc_stop_streaming(struct vb2_queue *vq)
struct isc_buffer *buf;
int ret;

+ mutex_lock(&isc->awb_mutex);
v4l2_ctrl_activate(isc->do_wb_ctrl, false);

isc->stop = true;
@@ -410,6 +411,8 @@ static void isc_stop_streaming(struct vb2_queue *vq)
v4l2_err(&isc->v4l2_dev,
"Timeout waiting for end of the capture\n");

+ mutex_unlock(&isc->awb_mutex);
+
/* Disable DMA interrupt */
regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE);

@@ -1416,10 +1419,6 @@ static void isc_awb_work(struct work_struct *w)
u32 min, max;
int ret;

- /* streaming is not active anymore */
- if (isc->stop)
- return;
-
if (ctrls->hist_stat != HIST_ENABLED)
return;

@@ -1470,7 +1469,24 @@ static void isc_awb_work(struct work_struct *w)
}
regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his,
hist_id | baysel | ISC_HIS_CFG_RAR);
+
+ /*
+ * We have to make sure the streaming has not stopped meanwhile.
+ * ISC requires a frame to clock the internal profile update.
+ * To avoid issues, lock the sequence with a mutex
+ */
+ mutex_lock(&isc->awb_mutex);
+
+ /* streaming is not active anymore */
+ if (isc->stop) {
+ mutex_unlock(&isc->awb_mutex);
+ return;
+ };
+
isc_update_profile(isc);
+
+ mutex_unlock(&isc->awb_mutex);
+
/* if awb has been disabled, we don't need to start another histogram */
if (ctrls->awb)
regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ);
@@ -1549,6 +1565,8 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)

isc_update_awb_ctrls(isc);

+ mutex_lock(&isc->awb_mutex);
+
if (!isc->stop) {
/*
* If we are streaming, we can update profile to
@@ -1563,6 +1581,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
*/
v4l2_ctrl_activate(isc->do_wb_ctrl, false);
}
+ mutex_unlock(&isc->awb_mutex);

/* if we have autowhitebalance on, start histogram procedure */
if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
@@ -1754,6 +1773,7 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
{
struct isc_device *isc = container_of(notifier->v4l2_dev,
struct isc_device, v4l2_dev);
+ mutex_destroy(&isc->awb_mutex);
cancel_work_sync(&isc->awb_work);
video_unregister_device(&isc->video_dev);
v4l2_ctrl_handler_free(&isc->ctrls.handler);
@@ -1866,6 +1886,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
isc->current_subdev = container_of(notifier,
struct isc_subdev_entity, notifier);
mutex_init(&isc->lock);
+ mutex_init(&isc->awb_mutex);
+
init_completion(&isc->comp);

/* Initialize videobuf2 queue */
@@ -1941,6 +1963,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
video_unregister_device(vdev);

isc_async_complete_err:
+ mutex_destroy(&isc->awb_mutex);
mutex_destroy(&isc->lock);
return ret;
}
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 0b6370d7775f..c2cb805faff3 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -307,6 +307,7 @@ struct isc_device {
struct work_struct awb_work;

struct mutex lock; /* serialize access to file operations */
+ struct mutex awb_mutex; /* serialize access to streaming status from awb work queue */
spinlock_t awb_lock; /* serialize access to DMA buffers from awb work queue */

struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM];
--
2.25.1


2021-12-13 13:55:23

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 18/23] media: atmel: atmel-isc-base: add wb debug messages

Add debug messages that make it easier to debug white balance algorithm.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 53cac1aac0fd..f1f1019f9d82 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1329,10 +1329,15 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)

if (!*min)
*min = 1;
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: hist_id %u, hist_count %u",
+ ctrls->hist_id, *hist_count);
}

static void isc_wb_update(struct isc_ctrls *ctrls)
{
+ struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls);
u32 *hist_count = &ctrls->hist_count[0];
u32 c, offset[4];
u64 avg = 0;
@@ -1349,6 +1354,9 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
(u64)hist_count[ISC_HIS_CFG_MODE_GB];
avg >>= 1;

+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: green components average %llu\n", avg);
+
/* Green histogram is null, nothing to do */
if (!avg)
return;
@@ -1401,9 +1409,15 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
else
gw_gain[c] = 1 << 9;

+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: component %d, s_gain %u, gw_gain %u\n",
+ c, s_gain[c], gw_gain[c]);
/* multiply both gains and adjust for decimals */
ctrls->gain[c] = s_gain[c] * gw_gain[c];
ctrls->gain[c] >>= 9;
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: component %d, final gain %u\n",
+ c, ctrls->gain[c]);
}
}

@@ -1423,6 +1437,10 @@ static void isc_awb_work(struct work_struct *w)
return;

isc_hist_count(isc, &min, &max);
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max);
+
ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min;
ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max;

--
2.25.1


2021-12-13 13:55:46

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 19/23] media: atmel: atmel-isc-base: clamp wb gain coefficients

White balance computed gains can overflow above the 13 bits hardware
coefficient that can be used, in some specific scenarios like a subexposure
from the sensor when the image is mostly black.
In this case the computed gain has to be clamped to the maximum value
allowed by the hardware.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index f1f1019f9d82..31c8e3029eee 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1415,6 +1415,10 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
/* multiply both gains and adjust for decimals */
ctrls->gain[c] = s_gain[c] * gw_gain[c];
ctrls->gain[c] >>= 9;
+
+ /* make sure we are not out of range */
+ ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0));
+
v4l2_dbg(1, debug, &isc->v4l2_dev,
"isc wb: component %d, final gain %u\n",
c, ctrls->gain[c]);
--
2.25.1


2021-12-13 13:55:55

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 20/23] media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo

Fix the mbus code for UYVY input format type to the right color rotation.

Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-sama7g5-isc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index d2ac80b1ccc3..38721bd902e2 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -188,7 +188,7 @@ static struct isc_format sama7g5_formats_list[] = {
},
{
.fourcc = V4L2_PIX_FMT_UYVY,
- .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
.pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT,
},
{
--
2.25.1


2021-12-13 13:56:14

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 21/23] media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats

The ISC can dump the 8 bit and 10 bit raw bayer formats directly to
the memory.
Thus, add them to the supported output format list.

Signed-off-by: Eugen Hristev <[email protected]>
---
.../media/platform/atmel/atmel-sama5d2-isc.c | 24 +++++++++++++++++++
.../media/platform/atmel/atmel-sama7g5-isc.c | 24 +++++++++++++++++++
2 files changed, 48 insertions(+)

diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index a8d4ba60d3ac..025c3e8a7e95 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -88,6 +88,30 @@ static const struct isc_format sama5d2_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_Y10,
},
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+ },
};

/* This is a list of formats that the ISC can receive as *input* */
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 38721bd902e2..9dc75eed0098 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -100,6 +100,30 @@ static const struct isc_format sama7g5_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_Y16,
},
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+ },
};

/* This is a list of formats that the ISC can receive as *input* */
--
2.25.1


2021-12-13 13:56:30

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 22/23] media: atmel: atmel-isc: compact the controller formats list

Compact the list array to be more readable.
No other changes, only cosmetic.

Signed-off-by: Eugen Hristev <[email protected]>
---
.../media/platform/atmel/atmel-sama5d2-isc.c | 51 ++++++----------
.../media/platform/atmel/atmel-sama7g5-isc.c | 60 +++++++------------
2 files changed, 37 insertions(+), 74 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index 025c3e8a7e95..d96ee3373889 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -60,56 +60,39 @@
static const struct isc_format sama5d2_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB444,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ARGB555,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_RGB565,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ABGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_XBGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV420,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUYV,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV422P,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_GREY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB10,
},
};
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 9dc75eed0098..e07ae188c15f 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -63,65 +63,45 @@
static const struct isc_format sama7g5_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB444,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ARGB555,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_RGB565,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ABGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_XBGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV420,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_UYVY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_VYUY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUYV,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV422P,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_GREY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y16,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB10,
},
};
--
2.25.1


2021-12-13 13:56:36

by Eugen Hristev

[permalink] [raw]
Subject: [PATCH v3 23/23] media: atmel: atmel-isc: change format propagation to subdev into only verification

As a top MC video driver, the atmel-isc should not propagate the format to the
subdevice.
It should rather check at streamon() time if the subdev is properly configured
with a compatible format.
Removed the whole format finding logic, and reworked the format verification
at streamon time, such that the ISC will return an error if the subdevice
is not properly configured.
With this being done, the module parameter 'sensor_prefered' makes no sense
anymore. The ISC should not decide which format the sensor is using. The
ISC should only cope with the situation and inform userspace if the streaming
is possible in the current configuration.

Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v3:
- clamp to maximum resolution once the frame size from the subdev is found

drivers/media/platform/atmel/atmel-isc-base.c | 271 ++++++++----------
drivers/media/platform/atmel/atmel-isc.h | 1 +
2 files changed, 126 insertions(+), 146 deletions(-)

diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 31c8e3029eee..00c8c9588a78 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -36,11 +36,6 @@ static unsigned int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");

-static unsigned int sensor_preferred = 1;
-module_param(sensor_preferred, uint, 0644);
-MODULE_PARM_DESC(sensor_preferred,
- "Sensor is preferred to output the specified format (1-on 0-off), default 1");
-
#define ISC_IS_FORMAT_RAW(mbus_code) \
(((mbus_code) & 0xf000) == 0x3000)

@@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
* convert it to any of the formats that we usually can with a
* RAW sensor. Thus, do not advertise them.
*/
- if (!isc->config.sd_format ||
+ if (isc->config.sd_format &&
!ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
return -EINVAL;

@@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
break;
default:
/* any other different formats are not supported */
+ v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
ret = -EINVAL;
}
v4l2_dbg(1, debug, &isc->v4l2_dev,
"Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
rgb, yuv, grey, bayer);

- /* we cannot output RAW if we do not receive RAW */
- if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
+ if ((bayer) &&
+ !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
return -EINVAL;
+ }

- /* we cannot output GREY if we do not receive RAW/GREY */
if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
- !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
+ !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
return -EINVAL;
+ }
+
+ if ((rgb || bayer || yuv) &&
+ ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
+ return -EINVAL;
+ }

return ret;
}
@@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
* If we do not know yet which format the subdev is using, we cannot
* do anything.
*/
- if (!isc->try_config.sd_format)
+ if (!isc->config.sd_format)
return;

fse.code = isc->try_config.sd_format->mbus_code;
@@ -883,180 +888,141 @@ static void isc_try_fse(struct isc_device *isc,
}
}

-static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
- u32 *code)
+static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
{
- int i;
- struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
struct v4l2_pix_format *pixfmt = &f->fmt.pix;
- struct v4l2_subdev_pad_config pad_cfg = {};
- struct v4l2_subdev_state pad_state = {
- .pads = &pad_cfg
- };
- struct v4l2_subdev_format format = {
- .which = V4L2_SUBDEV_FORMAT_TRY,
- };
- u32 mbus_code;
- int ret;
- bool rlp_dma_direct_dump = false;
+ unsigned int i;

if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;

- /* Step 1: find a RAW format that is supported */
- for (i = 0; i < isc->num_user_formats; i++) {
- if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
- sd_fmt = isc->user_formats[i];
+ isc->try_config.fourcc = isc->user_formats[0]->fourcc;
+
+ /* find if the format requested is supported */
+ for (i = 0; i < isc->controller_formats_size; i++)
+ if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
+ isc->try_config.fourcc = pixfmt->pixelformat;
break;
}
- }
- /* Step 2: We can continue with this RAW format, or we can look
- * for better: maybe sensor supports directly what we need.
- */
- direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
-
- /* Step 3: We have both. We decide given the module parameter which
- * one to use.
- */
- if (direct_fmt && sd_fmt && sensor_preferred)
- sd_fmt = direct_fmt;
-
- /* Step 4: we do not have RAW but we have a direct format. Use it. */
- if (direct_fmt && !sd_fmt)
- sd_fmt = direct_fmt;
-
- /* Step 5: if we are using a direct format, we need to package
- * everything as 8 bit data and just dump it
- */
- if (sd_fmt == direct_fmt)
- rlp_dma_direct_dump = true;
-
- /* Step 6: We have no format. This can happen if the userspace
- * requests some weird/invalid format.
- * In this case, default to whatever we have
- */
- if (!sd_fmt && !direct_fmt) {
- sd_fmt = isc->user_formats[isc->num_user_formats - 1];
- v4l2_dbg(1, debug, &isc->v4l2_dev,
- "Sensor not supporting %.4s, using %.4s\n",
- (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
- }
-
- if (!sd_fmt) {
- ret = -EINVAL;
- goto isc_try_fmt_err;
- }
-
- /* Step 7: Print out what we decided for debugging */
- v4l2_dbg(1, debug, &isc->v4l2_dev,
- "Preferring to have sensor using format %.4s\n",
- (char *)&sd_fmt->fourcc);
-
- /* Step 8: at this moment we decided which format the subdev will use */
- isc->try_config.sd_format = sd_fmt;
-
- /* Limit to Atmel ISC hardware capabilities */
- if (pixfmt->width > isc->max_width)
- pixfmt->width = isc->max_width;
- if (pixfmt->height > isc->max_height)
- pixfmt->height = isc->max_height;
-
- /*
- * The mbus format is the one the subdev outputs.
- * The pixels will be transferred in this format Sensor -> ISC
- */
- mbus_code = sd_fmt->mbus_code;
-
- /*
- * Validate formats. If the required format is not OK, default to raw.
- */
-
- isc->try_config.fourcc = pixfmt->pixelformat;
-
- if (isc_try_validate_formats(isc)) {
- pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
- /* Re-try to validate the new format */
- ret = isc_try_validate_formats(isc);
- if (ret)
- goto isc_try_fmt_err;
- }
-
- ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
- if (ret)
- goto isc_try_fmt_err;
-
- ret = isc_try_configure_pipeline(isc);
- if (ret)
- goto isc_try_fmt_err;

- /* Obtain frame sizes if possible to have crop requirements ready */
- isc_try_fse(isc, &pad_state);
-
- v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
- &pad_state, &format);
- if (ret < 0)
- goto isc_try_fmt_subdev_err;
+ /* If we did not find the requested format, we will fallback here */
+ pixfmt->pixelformat = isc->try_config.fourcc;
+ pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
+ pixfmt->field = V4L2_FIELD_NONE;

- v4l2_fill_pix_format(pixfmt, &format.format);
+ isc_try_configure_rlp_dma(isc, false);

/* Limit to Atmel ISC hardware capabilities */
- if (pixfmt->width > isc->max_width)
- pixfmt->width = isc->max_width;
- if (pixfmt->height > isc->max_height)
- pixfmt->height = isc->max_height;
+ v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
+ &pixfmt->height, 16, isc->max_height, 0, 0);

pixfmt->field = V4L2_FIELD_NONE;
pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
pixfmt->height;

- if (code)
- *code = mbus_code;
+ isc->try_fmt = *f;

return 0;
+}

-isc_try_fmt_err:
- v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
-isc_try_fmt_subdev_err:
- memset(&isc->try_config, 0, sizeof(isc->try_config));
+static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
+{
+ isc_try_fmt(isc, f);

- return ret;
+ /* make the try configuration active */
+ isc->config = isc->try_config;
+ isc->fmt = isc->try_fmt;
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
+ (char *)&f->fmt.pix.pixelformat,
+ f->fmt.pix.width, f->fmt.pix.height);
+
+ return 0;
}

-static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
+static int isc_validate(struct isc_device *isc)
{
+ int ret;
+ int i;
+ struct isc_format *sd_fmt = NULL;
+ struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
struct v4l2_subdev_format format = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .pad = isc->remote_pad,
+ };
+ struct v4l2_subdev_pad_config pad_cfg = {};
+ struct v4l2_subdev_state pad_state = {
+ .pads = &pad_cfg,
};
- u32 mbus_code = 0;
- int ret;

- ret = isc_try_fmt(isc, f, &mbus_code);
+ /* Get current format from subdev */
+ ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
+ &format);
if (ret)
return ret;

- v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
- set_fmt, NULL, &format);
- if (ret < 0)
- return ret;
+ /* Identify the subdev's format configuration */
+ for (i = 0; i < isc->num_user_formats; i++)
+ if (isc->user_formats[i]->mbus_code == format.format.code) {
+ sd_fmt = isc->user_formats[i];
+ break;
+ }
+
+ /* Check if the format is not supported */
+ if (!sd_fmt) {
+ v4l2_err(&isc->v4l2_dev,
+ "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
+ format.format.code);
+ return -EPIPE;
+ }
+
+ /* At this moment we know which format the subdev will use */
+ isc->try_config.sd_format = sd_fmt;
+
+ /* If the sensor is not RAW, we can only do a direct dump */
+ if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
+ isc_try_configure_rlp_dma(isc, true);

/* Limit to Atmel ISC hardware capabilities */
- if (f->fmt.pix.width > isc->max_width)
- f->fmt.pix.width = isc->max_width;
- if (f->fmt.pix.height > isc->max_height)
- f->fmt.pix.height = isc->max_height;
+ v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0,
+ &format.format.height, 16, isc->max_height, 0, 0);

- isc->fmt = *f;
+ /* Check if the frame size is the same. Otherwise we may overflow */
+ if (pixfmt->height != format.format.height ||
+ pixfmt->width != format.format.width) {
+ v4l2_err(&isc->v4l2_dev,
+ "ISC not configured with the proper frame size: %dx%d\n",
+ format.format.width, format.format.height);
+ return -EPIPE;
+ }

+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "Identified subdev using format %.4s with %dx%d %d bpp\n",
+ (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
+ isc->try_config.bpp);
+
+ /* Reset and restart AWB if the subdevice changed the format */
if (isc->try_config.sd_format && isc->config.sd_format &&
isc->try_config.sd_format != isc->config.sd_format) {
isc->ctrls.hist_stat = HIST_INIT;
isc_reset_awb_ctrls(isc);
isc_update_v4l2_ctrls(isc);
}
- /* make the try configuration active */
+
+ /* Validate formats */
+ ret = isc_try_validate_formats(isc);
+ if (ret)
+ return ret;
+
+ /* Obtain frame sizes if possible to have crop requirements ready */
+ isc_try_fse(isc, &pad_state);
+
+ /* Configure ISC pipeline for the config */
+ ret = isc_try_configure_pipeline(isc);
+ if (ret)
+ return ret;
+
isc->config = isc->try_config;

v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
@@ -1064,6 +1030,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
return 0;
}

+static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
+{
+ struct isc_device *isc = video_drvdata(file);
+ int ret;
+
+ ret = isc_validate(isc);
+
+ if (ret)
+ return ret;
+
+ return vb2_ioctl_streamon(file, priv, bt);
+}
+
static int isc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
@@ -1080,7 +1059,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
{
struct isc_device *isc = video_drvdata(file);

- return isc_try_fmt(isc, f, NULL);
+ return isc_try_fmt(isc, f);
}

static int isc_enum_input(struct file *file, void *priv,
@@ -1176,7 +1155,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
- .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamon = isc_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,

.vidioc_g_parm = isc_g_parm,
@@ -1879,7 +1858,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
};
int ret;

- ret = isc_try_fmt(isc, &f, NULL);
+ ret = isc_try_fmt(isc, &f);
if (ret)
return ret;

diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index c2cb805faff3..7081698adddd 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -297,6 +297,7 @@ struct isc_device {
struct completion comp;

struct v4l2_format fmt;
+ struct v4l2_format try_fmt;
struct isc_format **user_formats;
unsigned int num_user_formats;

--
2.25.1


2021-12-15 20:16:23

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v3 02/23] dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc

On Mon, 13 Dec 2021 15:49:19 +0200, Eugen Hristev wrote:
> Add bindings documentation for Microchip CSI2 Demultiplexer controller.
>
> CSI2DC is a demultiplexer from Synopsys IDI interface specification to
> parallel interface connection or direct memory access.
> CSI2DC can also act as a parallel bypass from a parallel sensor to the
> image sensor controller/interface.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v3:
> - Changed to port base as suggested by Rob
> - Added properties from video-interfaces which are being used by the csi2dc
> - added mention to the parallel port type as input
> - did some rephrasing and rewording
>
> Changes in v2:
> - changed the endpoint/port references as suggested by Rob
>
> Changes in this version :
> - fixed 'sink' name to be actually source.
> - added dma properties and example with dma
>
> Previous change log:
> Changes in v5:
> - modified bindings as per Rob Herring review
>
> Changes in v4:
> - Removed property for inter-line-delay and for clock continuous/non-continuous
> - Removed virtual channel by reg for second endpoint
>
> Changes in v3:
> - Removed some text from description, as it was explained in the schema
> - fixed other things as per Rob's review
> - moved some text inside the schema, like the clock description
>
> Changes in v2:
> - fixed warnings reported by dt_binding_check
>
>
> .../bindings/media/microchip,csi2dc.yaml | 197 ++++++++++++++++++
> 1 file changed, 197 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
>

Reviewed-by: Rob Herring <[email protected]>

2022-01-11 15:42:09

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 03/23] media: atmel: introduce microchip csi2dc driver

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:20PM +0200, Eugen Hristev wrote:
> Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
> that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
> to a pixel stream that can be captured by a sensor controller.
>
> Signed-off-by: Eugen Hristev <[email protected]>

Thanks for addressing my comments on previous versions
Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j


> ---
> Changes in v3:
> - on set format, disable setting the source sink.
> - removed useless request to pm_request_idle
> - fixed YUV DT
> - added last frame debug info
> - removed writing read only VPCOL and VPROW registers
> - minor cosmetic changes
>
> Changes in v2:
> - implement try formats
> - added parallel mode
> - moved to fwlink endpoints
> - many other minor corrections from Jacopo's review
>
> Changes in this revision:
> - addressed comments by Jacopo and Laurent as in this thread:
> https://www.spinics.net/lists/linux-media/msg181044.html
>
> Previous change log :
> Changes in v5:
> - only in bindings
>
> Changes in v4:
> - now using get_mbus_config ops to get data from the subdevice, like the
> virtual channel id, and the clock type.
> - now having possibility to select any of the RAW10 data modes
> - at completion time, select which formats are also available in the subdevice,
> and move to the dynamic list accordingly
> - changed the pipeline integration, do not advertise subdev ready at probe time.
> wait until completion is done, and then start a workqueue that will register
> this device as a subdevice for the next element in pipeline.
> - moved the s_power code into a different function called now csi2dc_power
> that is called with CONFIG_PM functions. This is also called at completion,
> to have the device ready in case CONFIG_PM is not selected on the platform.
> - merged try_fmt into set_fmt
> - driver cleanup, wrapped lines over 80 characters
>
> Changes in v2:
> - moved driver to platform/atmel
> - fixed minor things as per Sakari's review
> - still some things from v2 review are not yet addressed, to be followed up
>
>
>
> drivers/media/platform/Makefile | 1 +
> drivers/media/platform/atmel/Kconfig | 15 +
> drivers/media/platform/atmel/Makefile | 1 +
> .../media/platform/atmel/microchip-csi2dc.c | 806 ++++++++++++++++++
> 4 files changed, 823 insertions(+)
> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 832357240e89..b18e5f704145 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
>
> obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
>
> diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
> index dda2f27da317..f83bee373d82 100644
> --- a/drivers/media/platform/atmel/Kconfig
> +++ b/drivers/media/platform/atmel/Kconfig
> @@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
> help
> This module makes the ATMEL Image Sensor Interface available
> as a v4l2 device.
> +
> +config VIDEO_MICROCHIP_CSI2DC
> + tristate "Microchip CSI2 Demux Controller"
> + depends on VIDEO_V4L2 && COMMON_CLK && OF
> + depends on ARCH_AT91 || COMPILE_TEST
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + help
> + CSI2 Demux Controller driver. CSI2DC is a helper chip
> + that converts IDI interface byte stream to a parallel pixel stream.
> + It supports various RAW formats as input.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called microchip-csi2dc.
> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> index 46d264ab7948..39f0a7eba702 100644
> --- a/drivers/media/platform/atmel/Makefile
> +++ b/drivers/media/platform/atmel/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
> diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
> new file mode 100644
> index 000000000000..6bc549c28e05
> --- /dev/null
> +++ b/drivers/media/platform/atmel/microchip-csi2dc.c
> @@ -0,0 +1,806 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Microchip CSI2 Demux Controller (CSI2DC) driver
> + *
> + * Copyright (C) 2018 Microchip Technology, Inc.
> + *
> + * Author: Eugen Hristev <[email protected]>
> + *
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* Global configuration register */
> +#define CSI2DC_GCFG 0x0
> +
> +/* MIPI sensor pixel clock is free running */
> +#define CSI2DC_GCFG_MIPIFRN BIT(0)
> +/* GPIO parallel interface selection */
> +#define CSI2DC_GCFG_GPIOSEL BIT(1)
> +/* Output waveform inter-line minimum delay */
> +#define CSI2DC_GCFG_HLC(v) ((v) << 4)
> +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
> +/* SAMA7G5 requires a HLC delay of 15 */
> +#define SAMA7G5_HLC (15)
> +
> +/* Global control register */
> +#define CSI2DC_GCTLR 0x04
> +#define CSI2DC_GCTLR_SWRST BIT(0)
> +
> +/* Global status register */
> +#define CSI2DC_GS 0x08
> +
> +/* SSP interrupt status register */
> +#define CSI2DC_SSPIS 0x28
> +/* Pipe update register */
> +#define CSI2DC_PU 0xc0
> +/* Video pipe attributes update */
> +#define CSI2DC_PU_VP BIT(0)
> +
> +/* Pipe update status register */
> +#define CSI2DC_PUS 0xc4
> +
> +/* Video pipeline Interrupt Status Register */
> +#define CSI2DC_VPISR 0xf4
> +
> +/* Video pipeline enable register */
> +#define CSI2DC_VPE 0xf8
> +#define CSI2DC_VPE_ENABLE BIT(0)
> +
> +/* Video pipeline configuration register */
> +#define CSI2DC_VPCFG 0xfc
> +/* Data type */
> +#define CSI2DC_VPCFG_DT(v) ((v) << 0)
> +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
> +/* Virtual channel identifier */
> +#define CSI2DC_VPCFG_VC(v) ((v) << 6)
> +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
> +/* Decompression enable */
> +#define CSI2DC_VPCFG_DE BIT(8)
> +/* Decoder mode */
> +#define CSI2DC_VPCFG_DM(v) ((v) << 9)
> +#define CSI2DC_VPCFG_DM_DECODER8TO12 0
> +/* Decoder predictor 2 selection */
> +#define CSI2DC_VPCFG_DP2 BIT(12)
> +/* Recommended memory storage */
> +#define CSI2DC_VPCFG_RMS BIT(13)
> +/* Post adjustment */
> +#define CSI2DC_VPCFG_PA BIT(14)
> +
> +/* Video pipeline column register */
> +#define CSI2DC_VPCOL 0x100
> +/* Column number */
> +#define CSI2DC_VPCOL_COL(v) ((v) << 0)
> +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
> +
> +/* Video pipeline row register */
> +#define CSI2DC_VPROW 0x104
> +/* Row number */
> +#define CSI2DC_VPROW_ROW(v) ((v) << 0)
> +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
> +
> +/* Version register */
> +#define CSI2DC_VERSION 0x1fc
> +
> +/* register read/write helpers */
> +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
> +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
> + (st)->base + (reg))
> +
> +/* supported RAW data types */
> +#define CSI2DC_DT_RAW6 0x28
> +#define CSI2DC_DT_RAW7 0x29
> +#define CSI2DC_DT_RAW8 0x2a
> +#define CSI2DC_DT_RAW10 0x2b
> +#define CSI2DC_DT_RAW12 0x2c
> +#define CSI2DC_DT_RAW14 0x2d
> +/* YUV data types */
> +#define CSI2DC_DT_YUV422_8B 0x1e
> +
> +/*
> + * struct csi2dc_format - CSI2DC format type struct
> + * @mbus_code: Media bus code for the format
> + * @dt: Data type constant for this format
> + */
> +struct csi2dc_format {
> + u32 mbus_code;
> + u32 dt;
> +};
> +
> +static const struct csi2dc_format csi2dc_formats[] = {
> + {
> + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> + .dt = CSI2DC_DT_YUV422_8B,
> + },
> +};
> +
> +enum mipi_csi_pads {
> + CSI2DC_PAD_SINK = 0,
> + CSI2DC_PAD_SOURCE = 1,
> + CSI2DC_PADS_NUM = 2,
> +};
> +
> +/*
> + * struct csi2dc_device - CSI2DC device driver data/config struct
> + * @base: Register map base address
> + * @csi2dc_sd: v4l2 subdevice for the csi2dc device
> + * This is the subdevice that the csi2dc device itself
> + * registers in v4l2 subsystem
> + * @dev: struct device for this csi2dc device
> + * @pclk: Peripheral clock reference
> + * Input clock that clocks the hardware block internal
> + * logic
> + * @scck: Sensor Controller clock reference
> + * Input clock that is used to generate the pixel clock
> + * @format: Current saved format used in g/s fmt
> + * @cur_fmt: Current state format
> + * @try_fmt: Try format that is being tried
> + * @pads: Media entity pads for the csi2dc subdevice
> + * @clk_gated: Whether the clock is gated or free running
> + * @video_pipe: Whether video pipeline is configured
> + * @parallel_mode: The underlying subdevice is connected on a parallel bus
> + * @vc: Current set virtual channel
> + * @notifier: Async notifier that is used to bound the underlying
> + * subdevice to the csi2dc subdevice
> + * @input_sd: Reference to the underlying subdevice bound to the
> + * csi2dc subdevice
> + * @remote_pad: Pad number of the underlying subdevice that is linked
> + * to the csi2dc subdevice sink pad.
> + */
> +struct csi2dc_device {
> + void __iomem *base;
> + struct v4l2_subdev csi2dc_sd;
> + struct device *dev;
> + struct clk *pclk;
> + struct clk *scck;
> +
> + struct v4l2_mbus_framefmt format;
> +
> + const struct csi2dc_format *cur_fmt;
> + const struct csi2dc_format *try_fmt;
> +
> + struct media_pad pads[CSI2DC_PADS_NUM];
> +
> + bool clk_gated;
> + bool video_pipe;
> + bool parallel_mode;
> + u32 vc;
> +
> + struct v4l2_async_notifier notifier;
> +
> + struct v4l2_subdev *input_sd;
> +
> + u32 remote_pad;
> +};
> +
> +static inline struct csi2dc_device *
> +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
> +{
> + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
> +}
> +
> +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(csi2dc_formats))
> + return -EINVAL;
> +
> + code->code = csi2dc_formats[code->index].mbus_code;
> +
> + return 0;
> +}
> +
> +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *format)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> + format->pad);
> + format->format = *v4l2_try_fmt;
> +
> + return 0;
> + }
> +
> + format->format = csi2dc->format;
> +
> + return 0;
> +}
> +
> +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *req_fmt)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + const struct csi2dc_format *fmt, *try_fmt = NULL;
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> + unsigned int i;
> +
> + /*
> + * Setting the source pad is disabled.
> + * The same format is being propagated from the sink to source.
> + */
> + if (req_fmt->pad == CSI2DC_PAD_SOURCE)
> + return -EINVAL;
> +
> + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
> + fmt = &csi2dc_formats[i];
> + if (req_fmt->format.code == fmt->mbus_code)
> + try_fmt = fmt;
> + fmt++;
> + }
> +
> + /* in case we could not find the desired format, default to something */
> + if (!try_fmt) {
> + try_fmt = &csi2dc_formats[0];
> +
> + dev_dbg(csi2dc->dev,
> + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
> + req_fmt->format.code, csi2dc_formats[0].mbus_code);
> + }
> +
> + req_fmt->format.code = try_fmt->mbus_code;
> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> + req_fmt->format.field = V4L2_FIELD_NONE;
> +
> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> + req_fmt->pad);
> + *v4l2_try_fmt = req_fmt->format;
> + /* Trying on the sink pad makes the source pad change too */
> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd,
> + sd_state,
> + CSI2DC_PAD_SOURCE);
> + *v4l2_try_fmt = req_fmt->format;
> +
> + /* if we are just trying, we are done */
> + return 0;
> + }
> +
> + /* save the format for later requests */
> + csi2dc->format = req_fmt->format;
> +
> + /* update config */
> + csi2dc->cur_fmt = try_fmt;
> +
> + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
> + csi2dc->format.code, csi2dc->format.width,
> + csi2dc->format.height);
> +
> + return 0;
> +}
> +
> +static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
> +{
> + int ret = 0;
> +
> + if (on) {
> + ret = clk_prepare_enable(csi2dc->pclk);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(csi2dc->scck);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
> + clk_disable_unprepare(csi2dc->pclk);
> + return ret;
> + }
> +
> + /* if powering up, deassert reset line */
> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
> + } else {
> + /* if powering down, assert reset line */
> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
> +
> + clk_disable_unprepare(csi2dc->scck);
> + clk_disable_unprepare(csi2dc->pclk);
> + }
> +
> + return ret;
> +}
> +
> +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
> +{
> + struct v4l2_mbus_config mbus_config = { 0 };
> + int ret;
> +
> + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
> + csi2dc->remote_pad, &mbus_config);
> + if (ret == -ENOIOCTLCMD) {
> + dev_dbg(csi2dc->dev,
> + "no remote mbus configuration available\n");
> + goto csi2dc_get_mbus_config_defaults;
> + }
> +
> + if (ret) {
> + dev_err(csi2dc->dev,
> + "failed to get remote mbus configuration\n");
> + goto csi2dc_get_mbus_config_defaults;
> + }
> +
> + if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
> + csi2dc->vc = 0;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
> + csi2dc->vc = 1;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
> + csi2dc->vc = 2;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
> + csi2dc->vc = 3;
> +
> + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
> +
> + csi2dc->clk_gated = mbus_config.flags &
> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> +
> + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
> + csi2dc->clk_gated ? "gated" : "free running");
> +
> + return 0;
> +
> +csi2dc_get_mbus_config_defaults:
> + csi2dc->vc = 0; /* Virtual ID 0 by default */
> +
> + return 0;
> +}
> +
> +static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
> +{
> + u32 vp, gcfg;
> +
> + if (!csi2dc->video_pipe) {
> + dev_err(csi2dc->dev, "video pipeline unavailable\n");
> + return;
> + }
> +
> + if (csi2dc->parallel_mode) {
> + /* In parallel mode, GPIO parallel interface must be selected */
> + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
> + gcfg |= CSI2DC_GCFG_GPIOSEL;
> + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
> + return;
> + }
> +
> + /* serial video pipeline */
> +
> + csi2dc_writel(csi2dc, CSI2DC_GCFG,
> + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
> + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
> +
> + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
> + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
> + vp &= ~CSI2DC_VPCFG_DE;
> + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
> + vp &= ~CSI2DC_VPCFG_DP2;
> + vp &= ~CSI2DC_VPCFG_RMS;
> + vp |= CSI2DC_VPCFG_PA;
> +
> + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
> + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
> + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
> +}
> +
> +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + int ret;
> +
> + if (enable) {
> + ret = pm_runtime_resume_and_get(csi2dc->dev);
> + if (ret < 0)
> + return ret;
> +
> + csi2dc_get_mbus_config(csi2dc);
> +
> + csi2dc_vp_update(csi2dc);
> +
> + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
> + true);
> + }
> +
> + dev_dbg(csi2dc->dev,
> + "Last frame received: VPCOLR = %u, VPROWR= %u, VPISR = %x\n",
> + csi2dc_readl(csi2dc, CSI2DC_VPCOL),
> + csi2dc_readl(csi2dc, CSI2DC_VPROW),
> + csi2dc_readl(csi2dc, CSI2DC_VPISR));
> +
> + /* stop streaming scenario */
> + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
> +
> + pm_runtime_put_sync(csi2dc->dev);
> +
> + return ret;
> +}
> +
> +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
> + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
> +
> + v4l2_try_fmt->height = 480;
> + v4l2_try_fmt->width = 640;
> + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
> + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> + v4l2_try_fmt->field = V4L2_FIELD_NONE;
> + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
> + .enum_mbus_code = csi2dc_enum_mbus_code,
> + .set_fmt = csi2dc_set_fmt,
> + .get_fmt = csi2dc_get_fmt,
> + .init_cfg = csi2dc_init_cfg,
> +};
> +
> +static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
> + .s_stream = csi2dc_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
> + .pad = &csi2dc_pad_ops,
> + .video = &csi2dc_video_ops,
> +};
> +
> +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_subdev *asd)
> +{
> + struct csi2dc_device *csi2dc = container_of(notifier,
> + struct csi2dc_device, notifier);
> + int pad;
> + int ret;
> +
> + csi2dc->input_sd = subdev;
> +
> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (pad < 0) {
> + dev_err(csi2dc->dev, "Failed to find pad for %s\n",
> + subdev->name);
> + return pad;
> + }
> +
> + csi2dc->remote_pad = pad;
> +
> + ret = media_create_pad_link(&csi2dc->input_sd->entity,
> + csi2dc->remote_pad,
> + &csi2dc->csi2dc_sd.entity, 0,
> + MEDIA_LNK_FL_ENABLED);
> + if (ret) {
> + dev_err(csi2dc->dev,
> + "Failed to create pad link: %s to %s\n",
> + csi2dc->input_sd->entity.name,
> + csi2dc->csi2dc_sd.entity.name);
> + return ret;
> + }
> +
> + dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
> + csi2dc->input_sd->name, csi2dc->remote_pad);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
> + .bound = csi2dc_async_bound,
> +};
> +
> +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
> + struct fwnode_handle *input_fwnode)
> +{
> + struct v4l2_async_subdev *asd;
> + int ret = 0;
> +
> + v4l2_async_nf_init(&csi2dc->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
> + input_fwnode,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(input_fwnode);
> +
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + dev_err(csi2dc->dev,
> + "failed to add async notifier for node %pOF: %d\n",
> + to_of_node(input_fwnode), ret);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + return ret;
> + }
> +
> + csi2dc->notifier.ops = &csi2dc_async_ops;
> +
> + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
> + &csi2dc->notifier);
> + if (ret) {
> + dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
> + ret);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + }
> +
> + return ret;
> +}
> +
> +static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
> + struct device_node *of_node)
> +{
> + struct fwnode_handle *input_fwnode, *output_fwnode;
> + struct v4l2_fwnode_endpoint input_endpoint = { 0 },
> + output_endpoint = { 0 };
> + int ret;
> +
> + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
> + NULL);
> + if (!input_fwnode) {
> + dev_err(csi2dc->dev,
> + "missing port node at %pOF, input node is mandatory.\n",
> + of_node);
> + return -EINVAL;
> + }
> +
> + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
> + if (ret) {
> + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
> + goto csi2dc_of_parse_err;
> + }
> +
> + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
> + input_endpoint.bus_type == V4L2_MBUS_BT656) {
> + csi2dc->parallel_mode = true;
> + dev_dbg(csi2dc->dev,
> + "subdevice connected on parallel interface\n");
> + }
> +
> + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
> + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> + dev_dbg(csi2dc->dev,
> + "subdevice connected on serial interface\n");
> + dev_dbg(csi2dc->dev, "DT: %s clock\n",
> + csi2dc->clk_gated ? "gated" : "free running");
> + }
> +
> + output_fwnode = fwnode_graph_get_next_endpoint
> + (of_fwnode_handle(of_node), input_fwnode);
> +
> + if (output_fwnode)
> + ret = v4l2_fwnode_endpoint_parse(output_fwnode,
> + &output_endpoint);
> +
> + fwnode_handle_put(output_fwnode);
> +
> + if (!output_fwnode || ret) {
> + dev_info(csi2dc->dev,
> + "missing output node at %pOF, data pipe available only.\n",
> + of_node);
> + } else {
> + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
> + output_endpoint.bus_type != V4L2_MBUS_BT656) {
> + dev_err(csi2dc->dev,
> + "output port must be parallel/bt656.\n");
> + ret = -EINVAL;
> + goto csi2dc_of_parse_err;
> + }
> +
> + csi2dc->video_pipe = true;
> +
> + dev_dbg(csi2dc->dev,
> + "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
> + of_node, input_endpoint.base.port,
> + input_endpoint.base.id, output_endpoint.base.port,
> + output_endpoint.base.id);
> + }
> +
> + /* prepare async notifier for subdevice completion */
> + return csi2dc_prepare_notifier(csi2dc, input_fwnode);
> +
> +csi2dc_of_parse_err:
> + fwnode_handle_put(input_fwnode);
> + return ret;
> +}
> +
> +static void csi2dc_default_format(struct csi2dc_device *csi2dc)
> +{
> + csi2dc->cur_fmt = &csi2dc_formats[0];
> +
> + csi2dc->format.height = 480;
> + csi2dc->format.width = 640;
> + csi2dc->format.code = csi2dc_formats[0].mbus_code;
> + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
> + csi2dc->format.field = V4L2_FIELD_NONE;
> + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
> + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +}
> +
> +static int csi2dc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct csi2dc_device *csi2dc;
> + int ret = 0;
> + u32 ver;
> +
> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
> + if (!csi2dc)
> + return -ENOMEM;
> +
> + csi2dc->dev = dev;
> +
> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(csi2dc->base)) {
> + dev_err(dev, "base address not set\n");
> + return PTR_ERR(csi2dc->base);
> + }
> +
> + csi2dc->pclk = devm_clk_get(dev, "pclk");
> + if (IS_ERR(csi2dc->pclk)) {
> + ret = PTR_ERR(csi2dc->pclk);
> + dev_err(dev, "failed to get pclk: %d\n", ret);
> + return ret;
> + }
> +
> + csi2dc->scck = devm_clk_get(dev, "scck");
> + if (IS_ERR(csi2dc->scck)) {
> + ret = PTR_ERR(csi2dc->scck);
> + dev_err(dev, "failed to get scck: %d\n", ret);
> + return ret;
> + }
> +
> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
> +
> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
> + csi2dc->csi2dc_sd.dev = dev;
> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
> + "csi2dc");
> +
> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +
> + platform_set_drvdata(pdev, csi2dc);
> +
> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
> + if (ret)
> + goto csi2dc_probe_cleanup_entity;
> +
> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + if (csi2dc->video_pipe)
> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
> + csi2dc->pads);
> + if (ret < 0) {
> + dev_err(dev, "media entity init failed\n");
> + goto csi2dc_probe_cleanup_notifier;
> + }
> +
> + csi2dc_default_format(csi2dc);
> +
> + /* turn power on to validate capabilities */
> + ret = csi2dc_power(csi2dc, true);
> + if (ret < 0)
> + goto csi2dc_probe_cleanup_notifier;
> +
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
> +
> + /*
> + * we must register the subdev after PM runtime has been requested,
> + * otherwise we might bound immediately and request pm_runtime_resume
> + * before runtime_enable.
> + */
> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
> + goto csi2dc_probe_cleanup_notifier;
> + }
> +
> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
> +
> + return 0;
> +
> +csi2dc_probe_cleanup_notifier:
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> +csi2dc_probe_cleanup_entity:
> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> +
> + return ret;
> +}
> +
> +static int csi2dc_remove(struct platform_device *pdev)
> +{
> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
> + v4l2_async_nf_unregister(&csi2dc->notifier);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
> +{
> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> +
> + return csi2dc_power(csi2dc, false);
> +}
> +
> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
> +{
> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> +
> + return csi2dc_power(csi2dc, true);
> +}
> +
> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> +};
> +
> +static const struct of_device_id csi2dc_of_match[] = {
> + { .compatible = "microchip,sama7g5-csi2dc" },
> + { }
> +};
> +
> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
> +
> +static struct platform_driver csi2dc_driver = {
> + .probe = csi2dc_probe,
> + .remove = csi2dc_remove,
> + .driver = {
> + .name = "microchip-csi2dc",
> + .pm = &csi2dc_dev_pm_ops,
> + .of_match_table = of_match_ptr(csi2dc_of_match),
> + },
> +};
> +
> +module_platform_driver(csi2dc_driver);
> +
> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.25.1
>

2022-01-11 15:42:34

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 04/23] media: atmel: atmel-isc: split the clock code into separate source file

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:21PM +0200, Eugen Hristev wrote:
> The atmel-isc-base is getting crowded. Split the clock functions into
> atmel-isc-clk.c.
>
> Signed-off-by: Eugen Hristev <[email protected]>

Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j

> ---
> Changes in v3:
> - squash with maintainers, add patterns in maintainers
>
> MAINTAINERS | 7 +-
> drivers/media/platform/atmel/Makefile | 3 +-
> drivers/media/platform/atmel/atmel-isc-base.c | 295 -----------------
> drivers/media/platform/atmel/atmel-isc-clk.c | 311 ++++++++++++++++++
> drivers/media/platform/atmel/atmel-isc.h | 7 +
> 5 files changed, 322 insertions(+), 301 deletions(-)
> create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index afb74460e5c1..6aa274129751 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12500,11 +12500,8 @@ L: [email protected]
> S: Supported
> F: Documentation/devicetree/bindings/media/atmel,isc.yaml
> F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
> -F: drivers/media/platform/atmel/atmel-isc-base.c
> -F: drivers/media/platform/atmel/atmel-isc-regs.h
> -F: drivers/media/platform/atmel/atmel-isc.h
> -F: drivers/media/platform/atmel/atmel-sama5d2-isc.c
> -F: drivers/media/platform/atmel/atmel-sama7g5-isc.c
> +F: drivers/media/platform/atmel/atmel-isc*
> +F: drivers/media/platform/atmel/atmel-sama*-isc*
> F: include/linux/atmel-isc-media.h
>
> MICROCHIP ISI DRIVER
> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> index 39f0a7eba702..794e8f739287 100644
> --- a/drivers/media/platform/atmel/Makefile
> +++ b/drivers/media/platform/atmel/Makefile
> @@ -1,9 +1,10 @@
> # SPDX-License-Identifier: GPL-2.0-only
> atmel-isc-objs = atmel-sama5d2-isc.o
> atmel-xisc-objs = atmel-sama7g5-isc.o
> +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
>
> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> -obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
> +obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
> obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index 660cd0ab6749..58f468d32613 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -8,10 +8,6 @@
> * Author: Eugen Hristev <[email protected]>
> *
> */
> -
> -#include <linux/clk.h>
> -#include <linux/clkdev.h>
> -#include <linux/clk-provider.h>
> #include <linux/delay.h>
> #include <linux/interrupt.h>
> #include <linux/math64.h>
> @@ -100,297 +96,6 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc)
> }
> }
>
> -static int isc_wait_clk_stable(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - struct regmap *regmap = isc_clk->regmap;
> - unsigned long timeout = jiffies + usecs_to_jiffies(1000);
> - unsigned int status;
> -
> - while (time_before(jiffies, timeout)) {
> - regmap_read(regmap, ISC_CLKSR, &status);
> - if (!(status & ISC_CLKSR_SIP))
> - return 0;
> -
> - usleep_range(10, 250);
> - }
> -
> - return -ETIMEDOUT;
> -}
> -
> -static int isc_clk_prepare(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - int ret;
> -
> - ret = pm_runtime_resume_and_get(isc_clk->dev);
> - if (ret < 0)
> - return ret;
> -
> - return isc_wait_clk_stable(hw);
> -}
> -
> -static void isc_clk_unprepare(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> -
> - isc_wait_clk_stable(hw);
> -
> - pm_runtime_put_sync(isc_clk->dev);
> -}
> -
> -static int isc_clk_enable(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - u32 id = isc_clk->id;
> - struct regmap *regmap = isc_clk->regmap;
> - unsigned long flags;
> - unsigned int status;
> -
> - dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
> - __func__, id, isc_clk->div, isc_clk->parent_id);
> -
> - spin_lock_irqsave(&isc_clk->lock, flags);
> - regmap_update_bits(regmap, ISC_CLKCFG,
> - ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
> - (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
> - (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
> -
> - regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
> - spin_unlock_irqrestore(&isc_clk->lock, flags);
> -
> - regmap_read(regmap, ISC_CLKSR, &status);
> - if (status & ISC_CLK(id))
> - return 0;
> - else
> - return -EINVAL;
> -}
> -
> -static void isc_clk_disable(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - u32 id = isc_clk->id;
> - unsigned long flags;
> -
> - spin_lock_irqsave(&isc_clk->lock, flags);
> - regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
> - spin_unlock_irqrestore(&isc_clk->lock, flags);
> -}
> -
> -static int isc_clk_is_enabled(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - u32 status;
> - int ret;
> -
> - ret = pm_runtime_resume_and_get(isc_clk->dev);
> - if (ret < 0)
> - return 0;
> -
> - regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
> -
> - pm_runtime_put_sync(isc_clk->dev);
> -
> - return status & ISC_CLK(isc_clk->id) ? 1 : 0;
> -}
> -
> -static unsigned long
> -isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> -
> - return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
> -}
> -
> -static int isc_clk_determine_rate(struct clk_hw *hw,
> - struct clk_rate_request *req)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - long best_rate = -EINVAL;
> - int best_diff = -1;
> - unsigned int i, div;
> -
> - for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
> - struct clk_hw *parent;
> - unsigned long parent_rate;
> -
> - parent = clk_hw_get_parent_by_index(hw, i);
> - if (!parent)
> - continue;
> -
> - parent_rate = clk_hw_get_rate(parent);
> - if (!parent_rate)
> - continue;
> -
> - for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
> - unsigned long rate;
> - int diff;
> -
> - rate = DIV_ROUND_CLOSEST(parent_rate, div);
> - diff = abs(req->rate - rate);
> -
> - if (best_diff < 0 || best_diff > diff) {
> - best_rate = rate;
> - best_diff = diff;
> - req->best_parent_rate = parent_rate;
> - req->best_parent_hw = parent;
> - }
> -
> - if (!best_diff || rate < req->rate)
> - break;
> - }
> -
> - if (!best_diff)
> - break;
> - }
> -
> - dev_dbg(isc_clk->dev,
> - "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
> - __func__, best_rate,
> - __clk_get_name((req->best_parent_hw)->clk),
> - req->best_parent_rate);
> -
> - if (best_rate < 0)
> - return best_rate;
> -
> - req->rate = best_rate;
> -
> - return 0;
> -}
> -
> -static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> -
> - if (index >= clk_hw_get_num_parents(hw))
> - return -EINVAL;
> -
> - isc_clk->parent_id = index;
> -
> - return 0;
> -}
> -
> -static u8 isc_clk_get_parent(struct clk_hw *hw)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> -
> - return isc_clk->parent_id;
> -}
> -
> -static int isc_clk_set_rate(struct clk_hw *hw,
> - unsigned long rate,
> - unsigned long parent_rate)
> -{
> - struct isc_clk *isc_clk = to_isc_clk(hw);
> - u32 div;
> -
> - if (!rate)
> - return -EINVAL;
> -
> - div = DIV_ROUND_CLOSEST(parent_rate, rate);
> - if (div > (ISC_CLK_MAX_DIV + 1) || !div)
> - return -EINVAL;
> -
> - isc_clk->div = div - 1;
> -
> - return 0;
> -}
> -
> -static const struct clk_ops isc_clk_ops = {
> - .prepare = isc_clk_prepare,
> - .unprepare = isc_clk_unprepare,
> - .enable = isc_clk_enable,
> - .disable = isc_clk_disable,
> - .is_enabled = isc_clk_is_enabled,
> - .recalc_rate = isc_clk_recalc_rate,
> - .determine_rate = isc_clk_determine_rate,
> - .set_parent = isc_clk_set_parent,
> - .get_parent = isc_clk_get_parent,
> - .set_rate = isc_clk_set_rate,
> -};
> -
> -static int isc_clk_register(struct isc_device *isc, unsigned int id)
> -{
> - struct regmap *regmap = isc->regmap;
> - struct device_node *np = isc->dev->of_node;
> - struct isc_clk *isc_clk;
> - struct clk_init_data init;
> - const char *clk_name = np->name;
> - const char *parent_names[3];
> - int num_parents;
> -
> - if (id == ISC_ISPCK && !isc->ispck_required)
> - return 0;
> -
> - num_parents = of_clk_get_parent_count(np);
> - if (num_parents < 1 || num_parents > 3)
> - return -EINVAL;
> -
> - if (num_parents > 2 && id == ISC_ISPCK)
> - num_parents = 2;
> -
> - of_clk_parent_fill(np, parent_names, num_parents);
> -
> - if (id == ISC_MCK)
> - of_property_read_string(np, "clock-output-names", &clk_name);
> - else
> - clk_name = "isc-ispck";
> -
> - init.parent_names = parent_names;
> - init.num_parents = num_parents;
> - init.name = clk_name;
> - init.ops = &isc_clk_ops;
> - init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
> -
> - isc_clk = &isc->isc_clks[id];
> - isc_clk->hw.init = &init;
> - isc_clk->regmap = regmap;
> - isc_clk->id = id;
> - isc_clk->dev = isc->dev;
> - spin_lock_init(&isc_clk->lock);
> -
> - isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
> - if (IS_ERR(isc_clk->clk)) {
> - dev_err(isc->dev, "%s: clock register fail\n", clk_name);
> - return PTR_ERR(isc_clk->clk);
> - } else if (id == ISC_MCK)
> - of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
> -
> - return 0;
> -}
> -
> -int isc_clk_init(struct isc_device *isc)
> -{
> - unsigned int i;
> - int ret;
> -
> - for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
> - isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
> -
> - for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
> - ret = isc_clk_register(isc, i);
> - if (ret)
> - return ret;
> - }
> -
> - return 0;
> -}
> -EXPORT_SYMBOL_GPL(isc_clk_init);
> -
> -void isc_clk_cleanup(struct isc_device *isc)
> -{
> - unsigned int i;
> -
> - of_clk_del_provider(isc->dev->of_node);
> -
> - for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
> - struct isc_clk *isc_clk = &isc->isc_clks[i];
> -
> - if (!IS_ERR(isc_clk->clk))
> - clk_unregister(isc_clk->clk);
> - }
> -}
> -EXPORT_SYMBOL_GPL(isc_clk_cleanup);
>
> static int isc_queue_setup(struct vb2_queue *vq,
> unsigned int *nbuffers, unsigned int *nplanes,
> diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/media/platform/atmel/atmel-isc-clk.c
> new file mode 100644
> index 000000000000..2059fe376b00
> --- /dev/null
> +++ b/drivers/media/platform/atmel/atmel-isc-clk.c
> @@ -0,0 +1,311 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Microchip Image Sensor Controller (ISC) common clock driver setup
> + *
> + * Copyright (C) 2016 Microchip Technology, Inc.
> + *
> + * Author: Songjun Wu
> + * Author: Eugen Hristev <[email protected]>
> + *
> + */
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +
> +#include "atmel-isc-regs.h"
> +#include "atmel-isc.h"
> +
> +static int isc_wait_clk_stable(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + struct regmap *regmap = isc_clk->regmap;
> + unsigned long timeout = jiffies + usecs_to_jiffies(1000);
> + unsigned int status;
> +
> + while (time_before(jiffies, timeout)) {
> + regmap_read(regmap, ISC_CLKSR, &status);
> + if (!(status & ISC_CLKSR_SIP))
> + return 0;
> +
> + usleep_range(10, 250);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +static int isc_clk_prepare(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(isc_clk->dev);
> + if (ret < 0)
> + return ret;
> +
> + return isc_wait_clk_stable(hw);
> +}
> +
> +static void isc_clk_unprepare(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> +
> + isc_wait_clk_stable(hw);
> +
> + pm_runtime_put_sync(isc_clk->dev);
> +}
> +
> +static int isc_clk_enable(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + u32 id = isc_clk->id;
> + struct regmap *regmap = isc_clk->regmap;
> + unsigned long flags;
> + unsigned int status;
> +
> + dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
> + __func__, id, isc_clk->div, isc_clk->parent_id);
> +
> + spin_lock_irqsave(&isc_clk->lock, flags);
> + regmap_update_bits(regmap, ISC_CLKCFG,
> + ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
> + (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
> + (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
> +
> + regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
> + spin_unlock_irqrestore(&isc_clk->lock, flags);
> +
> + regmap_read(regmap, ISC_CLKSR, &status);
> + if (status & ISC_CLK(id))
> + return 0;
> + else
> + return -EINVAL;
> +}
> +
> +static void isc_clk_disable(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + u32 id = isc_clk->id;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&isc_clk->lock, flags);
> + regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
> + spin_unlock_irqrestore(&isc_clk->lock, flags);
> +}
> +
> +static int isc_clk_is_enabled(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + u32 status;
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(isc_clk->dev);
> + if (ret < 0)
> + return 0;
> +
> + regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
> +
> + pm_runtime_put_sync(isc_clk->dev);
> +
> + return status & ISC_CLK(isc_clk->id) ? 1 : 0;
> +}
> +
> +static unsigned long
> +isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> +
> + return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
> +}
> +
> +static int isc_clk_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + long best_rate = -EINVAL;
> + int best_diff = -1;
> + unsigned int i, div;
> +
> + for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
> + struct clk_hw *parent;
> + unsigned long parent_rate;
> +
> + parent = clk_hw_get_parent_by_index(hw, i);
> + if (!parent)
> + continue;
> +
> + parent_rate = clk_hw_get_rate(parent);
> + if (!parent_rate)
> + continue;
> +
> + for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
> + unsigned long rate;
> + int diff;
> +
> + rate = DIV_ROUND_CLOSEST(parent_rate, div);
> + diff = abs(req->rate - rate);
> +
> + if (best_diff < 0 || best_diff > diff) {
> + best_rate = rate;
> + best_diff = diff;
> + req->best_parent_rate = parent_rate;
> + req->best_parent_hw = parent;
> + }
> +
> + if (!best_diff || rate < req->rate)
> + break;
> + }
> +
> + if (!best_diff)
> + break;
> + }
> +
> + dev_dbg(isc_clk->dev,
> + "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
> + __func__, best_rate,
> + __clk_get_name((req->best_parent_hw)->clk),
> + req->best_parent_rate);
> +
> + if (best_rate < 0)
> + return best_rate;
> +
> + req->rate = best_rate;
> +
> + return 0;
> +}
> +
> +static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> +
> + if (index >= clk_hw_get_num_parents(hw))
> + return -EINVAL;
> +
> + isc_clk->parent_id = index;
> +
> + return 0;
> +}
> +
> +static u8 isc_clk_get_parent(struct clk_hw *hw)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> +
> + return isc_clk->parent_id;
> +}
> +
> +static int isc_clk_set_rate(struct clk_hw *hw,
> + unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct isc_clk *isc_clk = to_isc_clk(hw);
> + u32 div;
> +
> + if (!rate)
> + return -EINVAL;
> +
> + div = DIV_ROUND_CLOSEST(parent_rate, rate);
> + if (div > (ISC_CLK_MAX_DIV + 1) || !div)
> + return -EINVAL;
> +
> + isc_clk->div = div - 1;
> +
> + return 0;
> +}
> +
> +static const struct clk_ops isc_clk_ops = {
> + .prepare = isc_clk_prepare,
> + .unprepare = isc_clk_unprepare,
> + .enable = isc_clk_enable,
> + .disable = isc_clk_disable,
> + .is_enabled = isc_clk_is_enabled,
> + .recalc_rate = isc_clk_recalc_rate,
> + .determine_rate = isc_clk_determine_rate,
> + .set_parent = isc_clk_set_parent,
> + .get_parent = isc_clk_get_parent,
> + .set_rate = isc_clk_set_rate,
> +};
> +
> +static int isc_clk_register(struct isc_device *isc, unsigned int id)
> +{
> + struct regmap *regmap = isc->regmap;
> + struct device_node *np = isc->dev->of_node;
> + struct isc_clk *isc_clk;
> + struct clk_init_data init;
> + const char *clk_name = np->name;
> + const char *parent_names[3];
> + int num_parents;
> +
> + if (id == ISC_ISPCK && !isc->ispck_required)
> + return 0;
> +
> + num_parents = of_clk_get_parent_count(np);
> + if (num_parents < 1 || num_parents > 3)
> + return -EINVAL;
> +
> + if (num_parents > 2 && id == ISC_ISPCK)
> + num_parents = 2;
> +
> + of_clk_parent_fill(np, parent_names, num_parents);
> +
> + if (id == ISC_MCK)
> + of_property_read_string(np, "clock-output-names", &clk_name);
> + else
> + clk_name = "isc-ispck";
> +
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> + init.name = clk_name;
> + init.ops = &isc_clk_ops;
> + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
> +
> + isc_clk = &isc->isc_clks[id];
> + isc_clk->hw.init = &init;
> + isc_clk->regmap = regmap;
> + isc_clk->id = id;
> + isc_clk->dev = isc->dev;
> + spin_lock_init(&isc_clk->lock);
> +
> + isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
> + if (IS_ERR(isc_clk->clk)) {
> + dev_err(isc->dev, "%s: clock register fail\n", clk_name);
> + return PTR_ERR(isc_clk->clk);
> + } else if (id == ISC_MCK) {
> + of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
> + }
> +
> + return 0;
> +}
> +
> +int isc_clk_init(struct isc_device *isc)
> +{
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
> + isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
> +
> + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
> + ret = isc_clk_register(isc, i);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(isc_clk_init);
> +
> +void isc_clk_cleanup(struct isc_device *isc)
> +{
> + unsigned int i;
> +
> + of_clk_del_provider(isc->dev->of_node);
> +
> + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
> + struct isc_clk *isc_clk = &isc->isc_clks[i];
> +
> + if (!IS_ERR(isc_clk->clk))
> + clk_unregister(isc_clk->clk);
> + }
> +}
> +EXPORT_SYMBOL_GPL(isc_clk_cleanup);
> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> index 2bfcb135ef13..32448ccfc636 100644
> --- a/drivers/media/platform/atmel/atmel-isc.h
> +++ b/drivers/media/platform/atmel/atmel-isc.h
> @@ -10,6 +10,13 @@
> */
> #ifndef _ATMEL_ISC_H_
>
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> #define ISC_CLK_MAX_DIV 255
>
> enum isc_clk_id {
> --
> 2.25.1
>

2022-01-11 15:53:45

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v3 07/23] media: atmel: atmel-isc-base: use streaming status when queueing buffers

On 13/12/2021 14:49, Eugen Hristev wrote:
> During experiments with libcamera, it looks like vb2_is_streaming returns
> true before our start streaming is called.
> Order of operations is streamon -> queue -> start_streaming
> ISC would have started the DMA immediately when a buffer is being added
> to the vbqueue if the queue is streaming.
> It is more safe to start the DMA after the start streaming of the driver is
> called.
> Thus, even if vb2queue is streaming, add the buffer to the dma queue of the
> driver instead of actually starting the DMA process, if the start streaming
> has not been called yet.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> drivers/media/platform/atmel/atmel-isc-base.c | 17 ++++++++++-------
> 1 file changed, 10 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index 26a6090f056c..e6c9071c04f0 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -441,12 +441,14 @@ static void isc_buffer_queue(struct vb2_buffer *vb)
> unsigned long flags;
>
> spin_lock_irqsave(&isc->dma_queue_lock, flags);
> - if (!isc->cur_frm && list_empty(&isc->dma_queue) &&
> - vb2_is_streaming(vb->vb2_queue)) {
> +
> + if (!isc->cur_frm && list_empty(&isc->dma_queue) && !isc->stop) {
> isc->cur_frm = buf;
> isc_start_dma(isc);
> - } else
> + } else {
> list_add_tail(&buf->list, &isc->dma_queue);
> + }
> +
> spin_unlock_irqrestore(&isc->dma_queue_lock, flags);
> }

Both the old and new code doesn't make a lot of sense.

buf_queue is only called by vb2 if start_streaming has already been called or is
about to be called.

Typically all that the buf_queue op does is to call list_add_tail(&buf->list, &isc->dma_queue);
inside the spinlock.

>
> @@ -1014,7 +1016,7 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv,
> {
> struct isc_device *isc = video_drvdata(file);
>
> - if (vb2_is_streaming(&isc->vb2_vidq))
> + if (!isc->stop)

This is weird as well. Normally this calls vb2_is_busy to check if the
queue is busy (that really means that buffers are already allocated, so
changing the format isn't allowed anymore).

> return -EBUSY;
>
> return isc_set_fmt(isc, f);
> @@ -1536,7 +1538,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>
> isc_update_awb_ctrls(isc);
>
> - if (vb2_is_streaming(&isc->vb2_vidq)) {
> + if (!isc->stop) {

Ditto.

> /*
> * If we are streaming, we can update profile to
> * have the new settings in place.
> @@ -1552,8 +1554,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
> }
>
> /* if we have autowhitebalance on, start histogram procedure */
> - if (ctrls->awb == ISC_WB_AUTO &&
> - vb2_is_streaming(&isc->vb2_vidq) &&
> + if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
> ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
> isc_set_histogram(isc, true);
>
> @@ -1829,6 +1830,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> struct vb2_queue *q = &isc->vb2_vidq;
> int ret = 0;
>
> + isc->stop = true;
> +

I'm really not sure that you need the stop bool at all.

> INIT_WORK(&isc->awb_work, isc_awb_work);
>
> ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev);

Regards,

Hans

2022-01-11 16:05:10

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 12/23] media: atmel: atmel-isc: implement media controller

Hi Eugen,

On Mon, Dec 13, 2021 at 03:49:29PM +0200, Eugen Hristev wrote:
> Implement the support for media-controller.
> This means that the capabilities of the driver have changed and now
> it also advertises the IO_MC .
> The driver will register it's media device, and add the video entity to this
> media device. The subdevices are registered to the same media device.
> The ISC will have a base entity which is auto-detected as atmel_isc_base.
> It will also register a subdevice that allows cropping of the incoming frame
> to the maximum frame size supported by the ISC.
> The ISC will create a link between the subdevice that is asynchronously
> registered and the atmel_isc_scaler entity.
> Then, the atmel_isc_scaler and atmel_isc_base are connected through another
> link.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v2:
> - implement try formats
>
> drivers/media/platform/atmel/Makefile | 2 +-
> drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
> drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
> drivers/media/platform/atmel/atmel-isc.h | 36 +++
> .../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
> .../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
> 6 files changed, 370 insertions(+), 8 deletions(-)
> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
>
> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> index 794e8f739287..aa7a18b428f6 100644
> --- a/drivers/media/platform/atmel/Makefile
> +++ b/drivers/media/platform/atmel/Makefile
> @@ -1,7 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0-only
> atmel-isc-objs = atmel-sama5d2-isc.o
> atmel-xisc-objs = atmel-sama7g5-isc.o
> -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
> +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o
>
> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index d0542b97a391..b0c3ed21f372 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
> struct isc_device, v4l2_dev);
> struct isc_subdev_entity *subdev_entity =
> container_of(notifier, struct isc_subdev_entity, notifier);
> + int pad;
>
> if (video_is_registered(&isc->video_dev)) {
> v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
> @@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
>
> subdev_entity->sd = subdev;
>
> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (pad < 0) {
> + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
> + subdev->name);
> + return pad;
> + }
> +
> + isc->remote_pad = pad;
> +
> return 0;
> }
>
> @@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
> v4l2_ctrl_handler_free(&isc->ctrls.handler);
> }
>
> -static struct isc_format *find_format_by_code(struct isc_device *isc,
> - unsigned int code, int *index)
> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
> + unsigned int code, int *index)
> {
> struct isc_format *fmt = &isc->formats_list[0];
> unsigned int i;
> @@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,
>
> return NULL;
> }
> +EXPORT_SYMBOL_GPL(isc_find_format_by_code);
>
> static int isc_formats_init(struct isc_device *isc)
> {
> @@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
> NULL, &mbus_code)) {
> mbus_code.index++;
>
> - fmt = find_format_by_code(isc, mbus_code.code, &i);
> + fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
> if (!fmt) {
> v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
> mbus_code.code);
> @@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> vdev->queue = q;
> vdev->lock = &isc->lock;
> vdev->ctrl_handler = &isc->ctrls.handler;
> - vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
> + V4L2_CAP_IO_MC;
> video_set_drvdata(vdev, isc);
>
> ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> @@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> goto isc_async_complete_err;
> }
>
> + ret = isc_mc_register(isc);
> + if (ret < 0)
> + goto isc_async_complete_unregister_device;
> return 0;
>
> +isc_async_complete_unregister_device:
> + video_unregister_device(vdev);
> +
> isc_async_complete_err:
> mutex_destroy(&isc->lock);
> return ret;
> diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
> new file mode 100644
> index 000000000000..2aea81d77b2f
> --- /dev/null
> +++ b/drivers/media/platform/atmel/atmel-isc-mc.c
> @@ -0,0 +1,287 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Microchip Image Sensor Controller (ISC) Media Controller support
> + *
> + * Copyright (C) 2021 Microchip Technology, Inc.
> + *
> + * Author: Eugen Hristev <[email protected]>
> + *
> + */
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>

Should you include <media/media-entity.h> and <media/media-device.h> ?

Also, should you select MEDIA_CONTROLLER and V4L2_SUBDEV_API for all
the Kconfig symbols ?

> +
> +#include "atmel-isc-regs.h"
> +#include "atmel-isc.h"
> +
> +static const struct media_device_ops isc_media_ops = {
> +};

Do you need this even if it's empty ?

> +
> +static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *format)
> +{
> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
> + format->pad);
> + format->format = *v4l2_try_fmt;
> +
> + return 0;
> + }
> +
> + format->format = isc->scaler_format;
> +
> + return 0;
> +}
> +
> +static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *req_fmt)
> +{
> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> + struct isc_format *fmt;
> + unsigned int i;
> +
> + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
> + v4l_bound_align_image
> + (&req_fmt->format.width, 16, isc->max_width, 0,
> + &req_fmt->format.height, 16, isc->max_height, 0, 0);
> + else
> + v4l_bound_align_image
> + (&req_fmt->format.width, 16, 10000, 0,
> + &req_fmt->format.height, 16, 10000, 0, 0);
> +
> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> + req_fmt->format.field = V4L2_FIELD_NONE;
> + req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
> + req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
> +
> + if (!fmt)
> + fmt = &isc->formats_list[0];
> +
> + req_fmt->format.code = fmt->mbus_code;
> +
> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
> + req_fmt->pad);
> + *v4l2_try_fmt = req_fmt->format;
> + /* Trying on the pad sink makes the source sink change too */
> + if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
> + v4l2_try_fmt =
> + v4l2_subdev_get_try_format(sd, sd_state,
> + ISC_SCALER_PAD_SOURCE);
> + *v4l2_try_fmt = req_fmt->format;
> +
> + v4l_bound_align_image(&v4l2_try_fmt->width,
> + 16, isc->max_width, 0,
> + &v4l2_try_fmt->height,
> + 16, isc->max_height, 0, 0);
> + }
> + /* if we are just trying, we are done */
> + return 0;
> + }
> +
> + isc->scaler_format = req_fmt->format;
> +
> + return 0;
> +}
> +
> +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> + int supported_index = 0;
> + int i;
> +
> + for (i = 0; i < isc->formats_list_size; i++) {
> + if (!isc->formats_list[i].sd_support)
> + continue;
> + if (supported_index == code->index) {
> + code->code = isc->formats_list[i].mbus_code;
> + return 0;
> + }
> + supported_index++;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int isc_scaler_g_sel(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_selection *sel)
> +{
> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> +
> + if (sel->pad == ISC_SCALER_PAD_SOURCE)
> + return -EINVAL;
> +
> + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
> + sel->target != V4L2_SEL_TGT_CROP)
> + return -EINVAL;
> +
> + sel->r.height = isc->max_height;
> + sel->r.width = isc->max_width;
> +
> + sel->r.left = 0;
> + sel->r.top = 0;
> +
> + return 0;
> +}
> +
> +static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
> + v4l2_subdev_get_try_format(sd, sd_state, 0);
> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> +
> + *v4l2_try_fmt = isc->scaler_format;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
> + .enum_mbus_code = isc_scaler_enum_mbus_code,
> + .set_fmt = isc_scaler_set_fmt,
> + .get_fmt = isc_scaler_get_fmt,
> + .get_selection = isc_scaler_g_sel,
> + .init_cfg = isc_scaler_init_cfg,
> +};
> +
> +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
> + .pad = &isc_scaler_pad_ops,
> +};
> +
> +static int isc_init_own_sd(struct isc_device *isc)
> +{
> + int ret;
> +
> + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
> +
> + isc->scaler_sd.owner = THIS_MODULE;
> + isc->scaler_sd.dev = isc->dev;
> + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
> + "atmel_isc_scaler");
> +
> + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> + isc->scaler_format.height = isc->max_height;
> + isc->scaler_format.width = isc->max_width;
> + isc->scaler_format.code = isc->formats_list[0].mbus_code;
> + isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
> + isc->scaler_format.field = V4L2_FIELD_NONE;
> + isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
> + isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + ret = media_entity_pads_init(&isc->scaler_sd.entity,
> + ISC_SCALER_PADS_NUM,
> + isc->scaler_pads);
> + if (ret < 0) {
> + dev_err(isc->dev, "scaler sd media entity init failed\n");
> + return ret;
> + }
> + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
> + if (ret < 0) {
> + dev_err(isc->dev, "scaler sd failed to register subdev\n");
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +int isc_mc_init(struct isc_device *isc, u32 ver)
> +{
> + const struct of_device_id *match;
> + int ret;
> +
> + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
> + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
> + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
> + isc->pads);
> + if (ret < 0) {
> + dev_err(isc->dev, "media entity init failed\n");
> + return ret;
> + }
> +
> + isc->mdev.dev = isc->dev;
> + isc->mdev.ops = &isc_media_ops;
> +
> + match = of_match_node(isc->dev->driver->of_match_table,
> + isc->dev->of_node);
> +
> + strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
> + sizeof(isc->mdev.driver_name));
> + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
> + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
> + isc->v4l2_dev.name);
> + isc->mdev.hw_revision = ver;
> +
> + media_device_init(&isc->mdev);
> +
> + isc->v4l2_dev.mdev = &isc->mdev;
> +
> + return isc_init_own_sd(isc);
> +}
> +EXPORT_SYMBOL_GPL(isc_mc_init);
> +
> +int isc_mc_register(struct isc_device *isc)
> +{
> + int ret;
> +
> + ret = media_create_pad_link(&isc->current_subdev->sd->entity,
> + isc->remote_pad, &isc->scaler_sd.entity,
> + ISC_SCALER_PAD_SINK,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> +
> + if (ret < 0) {
> + v4l2_err(&isc->v4l2_dev,
> + "Failed to create pad link: %s to %s\n",
> + isc->current_subdev->sd->entity.name,
> + isc->scaler_sd.entity.name);
> + return ret;
> + }
> +
> + dev_dbg(isc->dev, "link with %s pad: %d\n",
> + isc->current_subdev->sd->name, isc->remote_pad);
> +
> + ret = media_create_pad_link(&isc->scaler_sd.entity,
> + ISC_SCALER_PAD_SOURCE,
> + &isc->video_dev.entity, ISC_PAD_SINK,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> +
> + if (ret < 0) {
> + v4l2_err(&isc->v4l2_dev,
> + "Failed to create pad link: %s to %s\n",
> + isc->scaler_sd.entity.name,
> + isc->video_dev.entity.name);
> + return ret;
> + }
> +
> + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
> + ISC_SCALER_PAD_SOURCE);
> +
> + return media_device_register(&isc->mdev);

I feel like the isc-base driver shold instead register the
media-controller and this file shuld actually be only about
isc-scaler or something. I might be missing why it is not possible
though. It doesn't seem to me that mc support is conditional, so it
can happen in the base driver directly ?

Links creation can still happen at async_complete() (I would have
suggested _bound() time, but you have no asyn subdev for the scaler,
so I guess the only async subdev here is the sensor).

All in all: can this function be removed and:
1) Base driver registers the media controller device in probe
2) links are created at async_complete time ?

Thanks
j


> +}
> +EXPORT_SYMBOL_GPL(isc_mc_register);
> +
> +void isc_mc_cleanup(struct isc_device *isc)
> +{
> + media_entity_cleanup(&isc->video_dev.entity);
> +}
> +EXPORT_SYMBOL_GPL(isc_mc_cleanup);
> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> index 07fa6dbf8460..0b6370d7775f 100644
> --- a/drivers/media/platform/atmel/atmel-isc.h
> +++ b/drivers/media/platform/atmel/atmel-isc.h
> @@ -183,6 +183,17 @@ struct isc_reg_offsets {
> u32 his_entry;
> };
>
> +enum isc_mc_pads {
> + ISC_PAD_SINK = 0,
> + ISC_PADS_NUM = 1,
> +};
> +
> +enum isc_scaler_pads {
> + ISC_SCALER_PAD_SINK = 0,
> + ISC_SCALER_PAD_SOURCE = 1,
> + ISC_SCALER_PADS_NUM = 2,
> +};
> +
> /*
> * struct isc_device - ISC device driver data/config struct
> * @regmap: Register map
> @@ -258,6 +269,12 @@ struct isc_reg_offsets {
> * be used as an input to the controller
> * @controller_formats_size: size of controller_formats array
> * @formats_list_size: size of formats_list array
> + * @pads: media controller pads for isc video entity
> + * @mdev: media device that is registered by the isc
> + * @remote_pad: remote pad on the connected subdevice
> + * @scaler_sd: subdevice for the scaler that isc registers
> + * @scaler_pads: media controller pads for the scaler subdevice
> + * @scaler_format: current format for the scaler subdevice
> */
> struct isc_device {
> struct regmap *regmap;
> @@ -346,6 +363,19 @@ struct isc_device {
> struct isc_format *formats_list;
> u32 controller_formats_size;
> u32 formats_list_size;
> +
> + struct {
> + struct media_pad pads[ISC_PADS_NUM];
> + struct media_device mdev;
> +
> + u32 remote_pad;
> + };
> +
> + struct {
> + struct v4l2_subdev scaler_sd;
> + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
> + struct v4l2_mbus_framefmt scaler_format;
> + };
> };
>
> extern const struct regmap_config isc_regmap_config;
> @@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
> void isc_subdev_cleanup(struct isc_device *isc);
> void isc_clk_cleanup(struct isc_device *isc);
>
> +int isc_mc_init(struct isc_device *isc, u32 ver);
> +int isc_mc_register(struct isc_device *isc);
> +void isc_mc_cleanup(struct isc_device *isc);
> +
> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
> + unsigned int code, int *index);
> #endif
> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> index 1b2063cce0f7..a3e4c32a66c4 100644
> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> @@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
> break;
> }
>
> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> +
> + ret = isc_mc_init(isc, ver);
> + if (ret < 0)
> + goto isc_probe_mc_init_err;
> +
> pm_runtime_set_active(dev);
> pm_runtime_enable(dev);
> pm_request_idle(dev);
> @@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
> ret = clk_prepare_enable(isc->ispck);
> if (ret) {
> dev_err(dev, "failed to enable ispck: %d\n", ret);
> - goto cleanup_subdev;
> + goto isc_probe_mc_init_err;
> }
>
> /* ispck should be greater or equal to hclock */
> @@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
> goto unprepare_clk;
> }
>
> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> dev_info(dev, "Microchip ISC version %x\n", ver);
>
> return 0;
> @@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
> unprepare_clk:
> clk_disable_unprepare(isc->ispck);
>
> +isc_probe_mc_init_err:
> + isc_mc_cleanup(isc);
> +
> cleanup_subdev:
> isc_subdev_cleanup(isc);
>
> @@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)
>
> pm_runtime_disable(&pdev->dev);
>
> + isc_mc_cleanup(isc);
> +
> isc_subdev_cleanup(isc);
>
> v4l2_device_unregister(&isc->v4l2_dev);
> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> index 2b1082295c13..d2ac80b1ccc3 100644
> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> @@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
> break;
> }
>
> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> +
> + ret = isc_mc_init(isc, ver);
> + if (ret < 0)
> + goto isc_probe_mc_init_err;
> +
> pm_runtime_set_active(dev);
> pm_runtime_enable(dev);
> pm_request_idle(dev);
>
> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> dev_info(dev, "Microchip XISC version %x\n", ver);
>
> return 0;
>
> +isc_probe_mc_init_err:
> + isc_mc_cleanup(isc);
> +
> cleanup_subdev:
> isc_subdev_cleanup(isc);
>
> @@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)
>
> pm_runtime_disable(&pdev->dev);
>
> + isc_mc_cleanup(isc);
> +
> isc_subdev_cleanup(isc);
>
> v4l2_device_unregister(&isc->v4l2_dev);
> --
> 2.25.1
>

2022-01-11 16:30:21

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 12/23] media: atmel: atmel-isc: implement media controller

On 1/11/22 6:06 PM, Jacopo Mondi wrote:
> Hi Eugen,
>
> On Mon, Dec 13, 2021 at 03:49:29PM +0200, Eugen Hristev wrote:
>> Implement the support for media-controller.
>> This means that the capabilities of the driver have changed and now
>> it also advertises the IO_MC .
>> The driver will register it's media device, and add the video entity to this
>> media device. The subdevices are registered to the same media device.
>> The ISC will have a base entity which is auto-detected as atmel_isc_base.
>> It will also register a subdevice that allows cropping of the incoming frame
>> to the maximum frame size supported by the ISC.
>> The ISC will create a link between the subdevice that is asynchronously
>> registered and the atmel_isc_scaler entity.
>> Then, the atmel_isc_scaler and atmel_isc_base are connected through another
>> link.
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> Changes in v2:
>> - implement try formats
>>
>> drivers/media/platform/atmel/Makefile | 2 +-
>> drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
>> drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
>> drivers/media/platform/atmel/atmel-isc.h | 36 +++
>> .../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
>> .../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
>> 6 files changed, 370 insertions(+), 8 deletions(-)
>> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
>>
>> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
>> index 794e8f739287..aa7a18b428f6 100644
>> --- a/drivers/media/platform/atmel/Makefile
>> +++ b/drivers/media/platform/atmel/Makefile
>> @@ -1,7 +1,7 @@
>> # SPDX-License-Identifier: GPL-2.0-only
>> atmel-isc-objs = atmel-sama5d2-isc.o
>> atmel-xisc-objs = atmel-sama7g5-isc.o
>> -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
>> +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o
>>
>> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
>> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>> index d0542b97a391..b0c3ed21f372 100644
>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>> @@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
>> struct isc_device, v4l2_dev);
>> struct isc_subdev_entity *subdev_entity =
>> container_of(notifier, struct isc_subdev_entity, notifier);
>> + int pad;
>>
>> if (video_is_registered(&isc->video_dev)) {
>> v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
>> @@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
>>
>> subdev_entity->sd = subdev;
>>
>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
>> + MEDIA_PAD_FL_SOURCE);
>> + if (pad < 0) {
>> + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
>> + subdev->name);
>> + return pad;
>> + }
>> +
>> + isc->remote_pad = pad;
>> +
>> return 0;
>> }
>>
>> @@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
>> v4l2_ctrl_handler_free(&isc->ctrls.handler);
>> }
>>
>> -static struct isc_format *find_format_by_code(struct isc_device *isc,
>> - unsigned int code, int *index)
>> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
>> + unsigned int code, int *index)
>> {
>> struct isc_format *fmt = &isc->formats_list[0];
>> unsigned int i;
>> @@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,
>>
>> return NULL;
>> }
>> +EXPORT_SYMBOL_GPL(isc_find_format_by_code);
>>
>> static int isc_formats_init(struct isc_device *isc)
>> {
>> @@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
>> NULL, &mbus_code)) {
>> mbus_code.index++;
>>
>> - fmt = find_format_by_code(isc, mbus_code.code, &i);
>> + fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
>> if (!fmt) {
>> v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
>> mbus_code.code);
>> @@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>> vdev->queue = q;
>> vdev->lock = &isc->lock;
>> vdev->ctrl_handler = &isc->ctrls.handler;
>> - vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
>> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
>> + V4L2_CAP_IO_MC;
>> video_set_drvdata(vdev, isc);
>>
>> ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> @@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>> goto isc_async_complete_err;
>> }
>>
>> + ret = isc_mc_register(isc);
>> + if (ret < 0)
>> + goto isc_async_complete_unregister_device;
>> return 0;
>>
>> +isc_async_complete_unregister_device:
>> + video_unregister_device(vdev);
>> +
>> isc_async_complete_err:
>> mutex_destroy(&isc->lock);
>> return ret;
>> diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
>> new file mode 100644
>> index 000000000000..2aea81d77b2f
>> --- /dev/null
>> +++ b/drivers/media/platform/atmel/atmel-isc-mc.c
>> @@ -0,0 +1,287 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Microchip Image Sensor Controller (ISC) Media Controller support
>> + *
>> + * Copyright (C) 2021 Microchip Technology, Inc.
>> + *
>> + * Author: Eugen Hristev <[email protected]>
>> + *
>> + */
>> +
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>
> Should you include <media/media-entity.h> and <media/media-device.h> ?

Hi Jacopo,

They are probably being included from another header. I will add them.

>
> Also, should you select MEDIA_CONTROLLER and V4L2_SUBDEV_API for all
> the Kconfig symbols ?


VIDEO_ATMEL_ISC directly selects:
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API

and VIDEO_ATMEL_XISC
depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API

and VIDEO_V4L2_SUBDEV_API depends on MEDIA_CONTROLLER

>
>> +
>> +#include "atmel-isc-regs.h"
>> +#include "atmel-isc.h"
>> +
>> +static const struct media_device_ops isc_media_ops = {
>> +};
>
> Do you need this even if it's empty ?
>

Not sure. I can remove it and pass NULL, but I haven't checked what the
subsystem is doing if there is no ops. I will try.

>> +
>> +static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_format *format)
>> +{
>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>> +
>> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
>> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
>> + format->pad);
>> + format->format = *v4l2_try_fmt;
>> +
>> + return 0;
>> + }
>> +
>> + format->format = isc->scaler_format;
>> +
>> + return 0;
>> +}
>> +
>> +static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_format *req_fmt)
>> +{
>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>> + struct isc_format *fmt;
>> + unsigned int i;
>> +
>> + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
>> + v4l_bound_align_image
>> + (&req_fmt->format.width, 16, isc->max_width, 0,
>> + &req_fmt->format.height, 16, isc->max_height, 0, 0);
>> + else
>> + v4l_bound_align_image
>> + (&req_fmt->format.width, 16, 10000, 0,
>> + &req_fmt->format.height, 16, 10000, 0, 0);
>> +
>> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
>> + req_fmt->format.field = V4L2_FIELD_NONE;
>> + req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> + req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
>> + req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>> +
>> + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
>> +
>> + if (!fmt)
>> + fmt = &isc->formats_list[0];
>> +
>> + req_fmt->format.code = fmt->mbus_code;
>> +
>> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
>> + req_fmt->pad);
>> + *v4l2_try_fmt = req_fmt->format;
>> + /* Trying on the pad sink makes the source sink change too */
>> + if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
>> + v4l2_try_fmt =
>> + v4l2_subdev_get_try_format(sd, sd_state,
>> + ISC_SCALER_PAD_SOURCE);
>> + *v4l2_try_fmt = req_fmt->format;
>> +
>> + v4l_bound_align_image(&v4l2_try_fmt->width,
>> + 16, isc->max_width, 0,
>> + &v4l2_try_fmt->height,
>> + 16, isc->max_height, 0, 0);
>> + }
>> + /* if we are just trying, we are done */
>> + return 0;
>> + }
>> +
>> + isc->scaler_format = req_fmt->format;
>> +
>> + return 0;
>> +}
>> +
>> +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>> + int supported_index = 0;
>> + int i;
>> +
>> + for (i = 0; i < isc->formats_list_size; i++) {
>> + if (!isc->formats_list[i].sd_support)
>> + continue;
>> + if (supported_index == code->index) {
>> + code->code = isc->formats_list[i].mbus_code;
>> + return 0;
>> + }
>> + supported_index++;
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static int isc_scaler_g_sel(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_selection *sel)
>> +{
>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>> +
>> + if (sel->pad == ISC_SCALER_PAD_SOURCE)
>> + return -EINVAL;
>> +
>> + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
>> + sel->target != V4L2_SEL_TGT_CROP)
>> + return -EINVAL;
>> +
>> + sel->r.height = isc->max_height;
>> + sel->r.width = isc->max_width;
>> +
>> + sel->r.left = 0;
>> + sel->r.top = 0;
>> +
>> + return 0;
>> +}
>> +
>> +static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *sd_state)
>> +{
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
>> + v4l2_subdev_get_try_format(sd, sd_state, 0);
>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>> +
>> + *v4l2_try_fmt = isc->scaler_format;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
>> + .enum_mbus_code = isc_scaler_enum_mbus_code,
>> + .set_fmt = isc_scaler_set_fmt,
>> + .get_fmt = isc_scaler_get_fmt,
>> + .get_selection = isc_scaler_g_sel,
>> + .init_cfg = isc_scaler_init_cfg,
>> +};
>> +
>> +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
>> + .pad = &isc_scaler_pad_ops,
>> +};
>> +
>> +static int isc_init_own_sd(struct isc_device *isc)
>> +{
>> + int ret;
>> +
>> + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
>> +
>> + isc->scaler_sd.owner = THIS_MODULE;
>> + isc->scaler_sd.dev = isc->dev;
>> + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
>> + "atmel_isc_scaler");
>> +
>> + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>> + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> + isc->scaler_format.height = isc->max_height;
>> + isc->scaler_format.width = isc->max_width;
>> + isc->scaler_format.code = isc->formats_list[0].mbus_code;
>> + isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
>> + isc->scaler_format.field = V4L2_FIELD_NONE;
>> + isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> + isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
>> + isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>> +
>> + ret = media_entity_pads_init(&isc->scaler_sd.entity,
>> + ISC_SCALER_PADS_NUM,
>> + isc->scaler_pads);
>> + if (ret < 0) {
>> + dev_err(isc->dev, "scaler sd media entity init failed\n");
>> + return ret;
>> + }
>> + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
>> + if (ret < 0) {
>> + dev_err(isc->dev, "scaler sd failed to register subdev\n");
>> + return ret;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +int isc_mc_init(struct isc_device *isc, u32 ver)
>> +{
>> + const struct of_device_id *match;
>> + int ret;
>> +
>> + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
>> + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
>> + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +
>> + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
>> + isc->pads);
>> + if (ret < 0) {
>> + dev_err(isc->dev, "media entity init failed\n");
>> + return ret;
>> + }
>> +
>> + isc->mdev.dev = isc->dev;
>> + isc->mdev.ops = &isc_media_ops;
>> +
>> + match = of_match_node(isc->dev->driver->of_match_table,
>> + isc->dev->of_node);
>> +
>> + strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
>> + sizeof(isc->mdev.driver_name));
>> + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
>> + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
>> + isc->v4l2_dev.name);
>> + isc->mdev.hw_revision = ver;
>> +
>> + media_device_init(&isc->mdev);
>> +
>> + isc->v4l2_dev.mdev = &isc->mdev;
>> +
>> + return isc_init_own_sd(isc);
>> +}
>> +EXPORT_SYMBOL_GPL(isc_mc_init);
>> +
>> +int isc_mc_register(struct isc_device *isc)
>> +{
>> + int ret;
>> +
>> + ret = media_create_pad_link(&isc->current_subdev->sd->entity,
>> + isc->remote_pad, &isc->scaler_sd.entity,
>> + ISC_SCALER_PAD_SINK,
>> + MEDIA_LNK_FL_ENABLED |
>> + MEDIA_LNK_FL_IMMUTABLE);
>> +
>> + if (ret < 0) {
>> + v4l2_err(&isc->v4l2_dev,
>> + "Failed to create pad link: %s to %s\n",
>> + isc->current_subdev->sd->entity.name,
>> + isc->scaler_sd.entity.name);
>> + return ret;
>> + }
>> +
>> + dev_dbg(isc->dev, "link with %s pad: %d\n",
>> + isc->current_subdev->sd->name, isc->remote_pad);
>> +
>> + ret = media_create_pad_link(&isc->scaler_sd.entity,
>> + ISC_SCALER_PAD_SOURCE,
>> + &isc->video_dev.entity, ISC_PAD_SINK,
>> + MEDIA_LNK_FL_ENABLED |
>> + MEDIA_LNK_FL_IMMUTABLE);
>> +
>> + if (ret < 0) {
>> + v4l2_err(&isc->v4l2_dev,
>> + "Failed to create pad link: %s to %s\n",
>> + isc->scaler_sd.entity.name,
>> + isc->video_dev.entity.name);
>> + return ret;
>> + }
>> +
>> + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
>> + ISC_SCALER_PAD_SOURCE);
>> +
>> + return media_device_register(&isc->mdev);
>
> I feel like the isc-base driver shold instead register the
> media-controller and this file shuld actually be only about
> isc-scaler or something. I might be missing why it is not possible
> though. It doesn't seem to me that mc support is conditional, so it
> can happen in the base driver directly ?

This actually is the base driver. Just that the atmel-isc-base.c is
pretty crowded. So I moved the MC part in this file. But it's the same
base driver.

>
> Links creation can still happen at async_complete() (I would have
> suggested _bound() time, but you have no asyn subdev for the scaler,
> so I guess the only async subdev here is the sensor).

Yes, the sensor or any kind of subdevice (XISC normally is connected to
a bridge, ISC to a sensor directly )

>
> All in all: can this function be removed and:
> 1) Base driver registers the media controller device in probe

Should we register the media device even if it's not operational ? and
no video device has been registered ?
I thought it would be safer to have it registered with all the
links/entities/etc once the video node has been completed.

But if you say this is the proper way, I can do that.
I wanted to keep things separated and not overcrowd the base file /probe
function, but if it's just a media_device_register call, I can do it there.

> 2) links are created at async_complete time ?

That works too. I think I could move them there.

Thanks for reviewing,

Eugen

>
> Thanks
> j
>
>
>> +}
>> +EXPORT_SYMBOL_GPL(isc_mc_register);
>> +
>> +void isc_mc_cleanup(struct isc_device *isc)
>> +{
>> + media_entity_cleanup(&isc->video_dev.entity);
>> +}
>> +EXPORT_SYMBOL_GPL(isc_mc_cleanup);
>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>> index 07fa6dbf8460..0b6370d7775f 100644
>> --- a/drivers/media/platform/atmel/atmel-isc.h
>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>> @@ -183,6 +183,17 @@ struct isc_reg_offsets {
>> u32 his_entry;
>> };
>>
>> +enum isc_mc_pads {
>> + ISC_PAD_SINK = 0,
>> + ISC_PADS_NUM = 1,
>> +};
>> +
>> +enum isc_scaler_pads {
>> + ISC_SCALER_PAD_SINK = 0,
>> + ISC_SCALER_PAD_SOURCE = 1,
>> + ISC_SCALER_PADS_NUM = 2,
>> +};
>> +
>> /*
>> * struct isc_device - ISC device driver data/config struct
>> * @regmap: Register map
>> @@ -258,6 +269,12 @@ struct isc_reg_offsets {
>> * be used as an input to the controller
>> * @controller_formats_size: size of controller_formats array
>> * @formats_list_size: size of formats_list array
>> + * @pads: media controller pads for isc video entity
>> + * @mdev: media device that is registered by the isc
>> + * @remote_pad: remote pad on the connected subdevice
>> + * @scaler_sd: subdevice for the scaler that isc registers
>> + * @scaler_pads: media controller pads for the scaler subdevice
>> + * @scaler_format: current format for the scaler subdevice
>> */
>> struct isc_device {
>> struct regmap *regmap;
>> @@ -346,6 +363,19 @@ struct isc_device {
>> struct isc_format *formats_list;
>> u32 controller_formats_size;
>> u32 formats_list_size;
>> +
>> + struct {
>> + struct media_pad pads[ISC_PADS_NUM];
>> + struct media_device mdev;
>> +
>> + u32 remote_pad;
>> + };
>> +
>> + struct {
>> + struct v4l2_subdev scaler_sd;
>> + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
>> + struct v4l2_mbus_framefmt scaler_format;
>> + };
>> };
>>
>> extern const struct regmap_config isc_regmap_config;
>> @@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
>> void isc_subdev_cleanup(struct isc_device *isc);
>> void isc_clk_cleanup(struct isc_device *isc);
>>
>> +int isc_mc_init(struct isc_device *isc, u32 ver);
>> +int isc_mc_register(struct isc_device *isc);
>> +void isc_mc_cleanup(struct isc_device *isc);
>> +
>> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
>> + unsigned int code, int *index);
>> #endif
>> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>> index 1b2063cce0f7..a3e4c32a66c4 100644
>> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>> @@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
>> break;
>> }
>>
>> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>> +
>> + ret = isc_mc_init(isc, ver);
>> + if (ret < 0)
>> + goto isc_probe_mc_init_err;
>> +
>> pm_runtime_set_active(dev);
>> pm_runtime_enable(dev);
>> pm_request_idle(dev);
>> @@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
>> ret = clk_prepare_enable(isc->ispck);
>> if (ret) {
>> dev_err(dev, "failed to enable ispck: %d\n", ret);
>> - goto cleanup_subdev;
>> + goto isc_probe_mc_init_err;
>> }
>>
>> /* ispck should be greater or equal to hclock */
>> @@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
>> goto unprepare_clk;
>> }
>>
>> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>> dev_info(dev, "Microchip ISC version %x\n", ver);
>>
>> return 0;
>> @@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
>> unprepare_clk:
>> clk_disable_unprepare(isc->ispck);
>>
>> +isc_probe_mc_init_err:
>> + isc_mc_cleanup(isc);
>> +
>> cleanup_subdev:
>> isc_subdev_cleanup(isc);
>>
>> @@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)
>>
>> pm_runtime_disable(&pdev->dev);
>>
>> + isc_mc_cleanup(isc);
>> +
>> isc_subdev_cleanup(isc);
>>
>> v4l2_device_unregister(&isc->v4l2_dev);
>> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>> index 2b1082295c13..d2ac80b1ccc3 100644
>> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>> @@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
>> break;
>> }
>>
>> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>> +
>> + ret = isc_mc_init(isc, ver);
>> + if (ret < 0)
>> + goto isc_probe_mc_init_err;
>> +
>> pm_runtime_set_active(dev);
>> pm_runtime_enable(dev);
>> pm_request_idle(dev);
>>
>> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>> dev_info(dev, "Microchip XISC version %x\n", ver);
>>
>> return 0;
>>
>> +isc_probe_mc_init_err:
>> + isc_mc_cleanup(isc);
>> +
>> cleanup_subdev:
>> isc_subdev_cleanup(isc);
>>
>> @@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)
>>
>> pm_runtime_disable(&pdev->dev);
>>
>> + isc_mc_cleanup(isc);
>> +
>> isc_subdev_cleanup(isc);
>>
>> v4l2_device_unregister(&isc->v4l2_dev);
>> --
>> 2.25.1
>>

2022-01-11 16:38:33

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 07/23] media: atmel: atmel-isc-base: use streaming status when queueing buffers

On 1/11/22 5:53 PM, Hans Verkuil wrote:
> On 13/12/2021 14:49, Eugen Hristev wrote:
>> During experiments with libcamera, it looks like vb2_is_streaming returns
>> true before our start streaming is called.
>> Order of operations is streamon -> queue -> start_streaming
>> ISC would have started the DMA immediately when a buffer is being added
>> to the vbqueue if the queue is streaming.
>> It is more safe to start the DMA after the start streaming of the driver is
>> called.
>> Thus, even if vb2queue is streaming, add the buffer to the dma queue of the
>> driver instead of actually starting the DMA process, if the start streaming
>> has not been called yet.
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> drivers/media/platform/atmel/atmel-isc-base.c | 17 ++++++++++-------
>> 1 file changed, 10 insertions(+), 7 deletions(-)
>>
>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>> index 26a6090f056c..e6c9071c04f0 100644
>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>> @@ -441,12 +441,14 @@ static void isc_buffer_queue(struct vb2_buffer *vb)
>> unsigned long flags;
>>
>> spin_lock_irqsave(&isc->dma_queue_lock, flags);
>> - if (!isc->cur_frm && list_empty(&isc->dma_queue) &&
>> - vb2_is_streaming(vb->vb2_queue)) {
>> +
>> + if (!isc->cur_frm && list_empty(&isc->dma_queue) && !isc->stop) {
>> isc->cur_frm = buf;
>> isc_start_dma(isc);
>> - } else
>> + } else {
>> list_add_tail(&buf->list, &isc->dma_queue);
>> + }
>> +
>> spin_unlock_irqrestore(&isc->dma_queue_lock, flags);
>> }
>
> Both the old and new code doesn't make a lot of sense.

Hello Hans,

I debated this a bit with Laurent when I initially wrote the patch.

The problem with the current code, or the current way things are
working, is the fact that vb2_is_streaming returns true from the moment
of streamon.
The DMA engine of the ISC is being started at the moment of
start_streaming, and if buffers are being sent earlier/later, they are
just added to the queue and DMA engine is restarted if it finished
previously.
However if frames are added beforehand, like, first streamon, then add
some frames, and then later call the start_streaming, ISC will crash.
(as I said, DMA engine is prepared at start_streaming).
To avoid this, I changed the vb2_is_streaming calls to keeping an
internal state of the streaming.
Do you think I should try another different approach to solve this ?
Perhaps do not prepare the ISC dma engine at start_streaming, but at
another place ?

Laurent do you remember the talk we had about this problem a while back ?

>
> buf_queue is only called by vb2 if start_streaming has already been called or is
> about to be called.

That's a huge difference for ISC driver as it is today. start_streaming
being called afterwards, means the ISC DMA engine is not started (not
started streaming), hence a crash.

>
> Typically all that the buf_queue op does is to call list_add_tail(&buf->list, &isc->dma_queue);
> inside the spinlock.
>
>>
>> @@ -1014,7 +1016,7 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv,
>> {
>> struct isc_device *isc = video_drvdata(file);
>>
>> - if (vb2_is_streaming(&isc->vb2_vidq))
>> + if (!isc->stop)
>
> This is weird as well. Normally this calls vb2_is_busy to check if the
> queue is busy (that really means that buffers are already allocated, so
> changing the format isn't allowed anymore).

You think the query to the streaming status makes no sense here hence it
should be removed completely ?
I can do that with a prequel patch if it's the case.

Thanks for reviewing,

Eugen

>
>> return -EBUSY;
>>
>> return isc_set_fmt(isc, f);
>> @@ -1536,7 +1538,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>>
>> isc_update_awb_ctrls(isc);
>>
>> - if (vb2_is_streaming(&isc->vb2_vidq)) {
>> + if (!isc->stop) {
>
> Ditto.
>
>> /*
>> * If we are streaming, we can update profile to
>> * have the new settings in place.
>> @@ -1552,8 +1554,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>> }
>>
>> /* if we have autowhitebalance on, start histogram procedure */
>> - if (ctrls->awb == ISC_WB_AUTO &&
>> - vb2_is_streaming(&isc->vb2_vidq) &&
>> + if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
>> ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
>> isc_set_histogram(isc, true);
>>
>> @@ -1829,6 +1830,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>> struct vb2_queue *q = &isc->vb2_vidq;
>> int ret = 0;
>>
>> + isc->stop = true;
>> +
>
> I'm really not sure that you need the stop bool at all.
>
>> INIT_WORK(&isc->awb_work, isc_awb_work);
>>
>> ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev);
>
> Regards,
>
> Hans
>

2022-01-11 16:50:40

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 12/23] media: atmel: atmel-isc: implement media controller

On Tue, Jan 11, 2022 at 04:30:15PM +0000, [email protected] wrote:
> On 1/11/22 6:06 PM, Jacopo Mondi wrote:
> > Hi Eugen,
> >
> > On Mon, Dec 13, 2021 at 03:49:29PM +0200, Eugen Hristev wrote:
> >> Implement the support for media-controller.
> >> This means that the capabilities of the driver have changed and now
> >> it also advertises the IO_MC .
> >> The driver will register it's media device, and add the video entity to this
> >> media device. The subdevices are registered to the same media device.
> >> The ISC will have a base entity which is auto-detected as atmel_isc_base.
> >> It will also register a subdevice that allows cropping of the incoming frame
> >> to the maximum frame size supported by the ISC.
> >> The ISC will create a link between the subdevice that is asynchronously
> >> registered and the atmel_isc_scaler entity.
> >> Then, the atmel_isc_scaler and atmel_isc_base are connected through another
> >> link.
> >>
> >> Signed-off-by: Eugen Hristev <[email protected]>
> >> ---
> >> Changes in v2:
> >> - implement try formats
> >>
> >> drivers/media/platform/atmel/Makefile | 2 +-
> >> drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
> >> drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
> >> drivers/media/platform/atmel/atmel-isc.h | 36 +++
> >> .../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
> >> .../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
> >> 6 files changed, 370 insertions(+), 8 deletions(-)
> >> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
> >>
> >> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> >> index 794e8f739287..aa7a18b428f6 100644
> >> --- a/drivers/media/platform/atmel/Makefile
> >> +++ b/drivers/media/platform/atmel/Makefile
> >> @@ -1,7 +1,7 @@
> >> # SPDX-License-Identifier: GPL-2.0-only
> >> atmel-isc-objs = atmel-sama5d2-isc.o
> >> atmel-xisc-objs = atmel-sama7g5-isc.o
> >> -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
> >> +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o
> >>
> >> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> >> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
> >> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> >> index d0542b97a391..b0c3ed21f372 100644
> >> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> >> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> >> @@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
> >> struct isc_device, v4l2_dev);
> >> struct isc_subdev_entity *subdev_entity =
> >> container_of(notifier, struct isc_subdev_entity, notifier);
> >> + int pad;
> >>
> >> if (video_is_registered(&isc->video_dev)) {
> >> v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
> >> @@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
> >>
> >> subdev_entity->sd = subdev;
> >>
> >> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
> >> + MEDIA_PAD_FL_SOURCE);
> >> + if (pad < 0) {
> >> + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
> >> + subdev->name);
> >> + return pad;
> >> + }
> >> +
> >> + isc->remote_pad = pad;
> >> +
> >> return 0;
> >> }
> >>
> >> @@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
> >> v4l2_ctrl_handler_free(&isc->ctrls.handler);
> >> }
> >>
> >> -static struct isc_format *find_format_by_code(struct isc_device *isc,
> >> - unsigned int code, int *index)
> >> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
> >> + unsigned int code, int *index)
> >> {
> >> struct isc_format *fmt = &isc->formats_list[0];
> >> unsigned int i;
> >> @@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,
> >>
> >> return NULL;
> >> }
> >> +EXPORT_SYMBOL_GPL(isc_find_format_by_code);
> >>
> >> static int isc_formats_init(struct isc_device *isc)
> >> {
> >> @@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
> >> NULL, &mbus_code)) {
> >> mbus_code.index++;
> >>
> >> - fmt = find_format_by_code(isc, mbus_code.code, &i);
> >> + fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
> >> if (!fmt) {
> >> v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
> >> mbus_code.code);
> >> @@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> >> vdev->queue = q;
> >> vdev->lock = &isc->lock;
> >> vdev->ctrl_handler = &isc->ctrls.handler;
> >> - vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
> >> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
> >> + V4L2_CAP_IO_MC;
> >> video_set_drvdata(vdev, isc);
> >>
> >> ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> >> @@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> >> goto isc_async_complete_err;
> >> }
> >>
> >> + ret = isc_mc_register(isc);
> >> + if (ret < 0)
> >> + goto isc_async_complete_unregister_device;
> >> return 0;
> >>
> >> +isc_async_complete_unregister_device:
> >> + video_unregister_device(vdev);
> >> +
> >> isc_async_complete_err:
> >> mutex_destroy(&isc->lock);
> >> return ret;
> >> diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
> >> new file mode 100644
> >> index 000000000000..2aea81d77b2f
> >> --- /dev/null
> >> +++ b/drivers/media/platform/atmel/atmel-isc-mc.c
> >> @@ -0,0 +1,287 @@
> >> +// SPDX-License-Identifier: GPL-2.0-only
> >> +/*
> >> + * Microchip Image Sensor Controller (ISC) Media Controller support
> >> + *
> >> + * Copyright (C) 2021 Microchip Technology, Inc.
> >> + *
> >> + * Author: Eugen Hristev <[email protected]>
> >> + *
> >> + */
> >> +
> >> +#include <media/v4l2-device.h>
> >> +#include <media/v4l2-subdev.h>
> >
> > Should you include <media/media-entity.h> and <media/media-device.h> ?
>
> Hi Jacopo,
>
> They are probably being included from another header. I will add them.
>

Thank you :)

> >
> > Also, should you select MEDIA_CONTROLLER and V4L2_SUBDEV_API for all
> > the Kconfig symbols ?
>
>
> VIDEO_ATMEL_ISC directly selects:
> select MEDIA_CONTROLLER
> select VIDEO_V4L2_SUBDEV_API
>
> and VIDEO_ATMEL_XISC
> depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API

And selects VIDEO_ATMEL_ISC_BASE so it's fine!

VIDEO_ATMEL_ISI does not. I'm fine if this is intended, I might have
got lost in the several pieces this driver is composed of :)

>
> and VIDEO_V4L2_SUBDEV_API depends on MEDIA_CONTROLLER
>
> >
> >> +
> >> +#include "atmel-isc-regs.h"
> >> +#include "atmel-isc.h"
> >> +
> >> +static const struct media_device_ops isc_media_ops = {
> >> +};
> >
> > Do you need this even if it's empty ?
> >
>
> Not sure. I can remove it and pass NULL, but I haven't checked what the
> subsystem is doing if there is no ops. I will try.
>

Seems like it is safe as the core checks for if (mdev->ops) before
accessing it (except for mc-request for which operations are
mandatory)

> >> +
> >> +static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_format *format)
> >> +{
> >> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> >> +
> >> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> >> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
> >> + format->pad);
> >> + format->format = *v4l2_try_fmt;
> >> +
> >> + return 0;
> >> + }
> >> +
> >> + format->format = isc->scaler_format;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_format *req_fmt)
> >> +{
> >> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> >> + struct isc_format *fmt;
> >> + unsigned int i;
> >> +
> >> + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
> >> + v4l_bound_align_image
> >> + (&req_fmt->format.width, 16, isc->max_width, 0,
> >> + &req_fmt->format.height, 16, isc->max_height, 0, 0);
> >> + else
> >> + v4l_bound_align_image
> >> + (&req_fmt->format.width, 16, 10000, 0,
> >> + &req_fmt->format.height, 16, 10000, 0, 0);
> >> +
> >> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> >> + req_fmt->format.field = V4L2_FIELD_NONE;
> >> + req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> + req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
> >> + req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >> +
> >> + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
> >> +
> >> + if (!fmt)
> >> + fmt = &isc->formats_list[0];
> >> +
> >> + req_fmt->format.code = fmt->mbus_code;
> >> +
> >> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> >> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
> >> + req_fmt->pad);
> >> + *v4l2_try_fmt = req_fmt->format;
> >> + /* Trying on the pad sink makes the source sink change too */
> >> + if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
> >> + v4l2_try_fmt =
> >> + v4l2_subdev_get_try_format(sd, sd_state,
> >> + ISC_SCALER_PAD_SOURCE);
> >> + *v4l2_try_fmt = req_fmt->format;
> >> +
> >> + v4l_bound_align_image(&v4l2_try_fmt->width,
> >> + 16, isc->max_width, 0,
> >> + &v4l2_try_fmt->height,
> >> + 16, isc->max_height, 0, 0);
> >> + }
> >> + /* if we are just trying, we are done */
> >> + return 0;
> >> + }
> >> +
> >> + isc->scaler_format = req_fmt->format;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_mbus_code_enum *code)
> >> +{
> >> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> >> + int supported_index = 0;
> >> + int i;
> >> +
> >> + for (i = 0; i < isc->formats_list_size; i++) {
> >> + if (!isc->formats_list[i].sd_support)
> >> + continue;
> >> + if (supported_index == code->index) {
> >> + code->code = isc->formats_list[i].mbus_code;
> >> + return 0;
> >> + }
> >> + supported_index++;
> >> + }
> >> +
> >> + return -EINVAL;
> >> +}
> >> +
> >> +static int isc_scaler_g_sel(struct v4l2_subdev *sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_selection *sel)
> >> +{
> >> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> >> +
> >> + if (sel->pad == ISC_SCALER_PAD_SOURCE)
> >> + return -EINVAL;
> >> +
> >> + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
> >> + sel->target != V4L2_SEL_TGT_CROP)
> >> + return -EINVAL;
> >> +
> >> + sel->r.height = isc->max_height;
> >> + sel->r.width = isc->max_width;
> >> +
> >> + sel->r.left = 0;
> >> + sel->r.top = 0;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
> >> + struct v4l2_subdev_state *sd_state)
> >> +{
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
> >> + v4l2_subdev_get_try_format(sd, sd_state, 0);
> >> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
> >> +
> >> + *v4l2_try_fmt = isc->scaler_format;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
> >> + .enum_mbus_code = isc_scaler_enum_mbus_code,
> >> + .set_fmt = isc_scaler_set_fmt,
> >> + .get_fmt = isc_scaler_get_fmt,
> >> + .get_selection = isc_scaler_g_sel,
> >> + .init_cfg = isc_scaler_init_cfg,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
> >> + .pad = &isc_scaler_pad_ops,
> >> +};
> >> +
> >> +static int isc_init_own_sd(struct isc_device *isc)
> >> +{
> >> + int ret;
> >> +
> >> + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
> >> +
> >> + isc->scaler_sd.owner = THIS_MODULE;
> >> + isc->scaler_sd.dev = isc->dev;
> >> + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
> >> + "atmel_isc_scaler");
> >> +
> >> + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >> + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> >> + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> >> + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> >> +
> >> + isc->scaler_format.height = isc->max_height;
> >> + isc->scaler_format.width = isc->max_width;
> >> + isc->scaler_format.code = isc->formats_list[0].mbus_code;
> >> + isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
> >> + isc->scaler_format.field = V4L2_FIELD_NONE;
> >> + isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> + isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
> >> + isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >> +
> >> + ret = media_entity_pads_init(&isc->scaler_sd.entity,
> >> + ISC_SCALER_PADS_NUM,
> >> + isc->scaler_pads);
> >> + if (ret < 0) {
> >> + dev_err(isc->dev, "scaler sd media entity init failed\n");
> >> + return ret;
> >> + }
> >> + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
> >> + if (ret < 0) {
> >> + dev_err(isc->dev, "scaler sd failed to register subdev\n");
> >> + return ret;
> >> + }
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +int isc_mc_init(struct isc_device *isc, u32 ver)
> >> +{
> >> + const struct of_device_id *match;
> >> + int ret;
> >> +
> >> + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
> >> + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
> >> + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> >> +
> >> + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
> >> + isc->pads);
> >> + if (ret < 0) {
> >> + dev_err(isc->dev, "media entity init failed\n");
> >> + return ret;
> >> + }
> >> +
> >> + isc->mdev.dev = isc->dev;
> >> + isc->mdev.ops = &isc_media_ops;
> >> +
> >> + match = of_match_node(isc->dev->driver->of_match_table,
> >> + isc->dev->of_node);
> >> +
> >> + strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
> >> + sizeof(isc->mdev.driver_name));
> >> + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
> >> + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
> >> + isc->v4l2_dev.name);
> >> + isc->mdev.hw_revision = ver;
> >> +
> >> + media_device_init(&isc->mdev);
> >> +
> >> + isc->v4l2_dev.mdev = &isc->mdev;
> >> +
> >> + return isc_init_own_sd(isc);
> >> +}
> >> +EXPORT_SYMBOL_GPL(isc_mc_init);
> >> +
> >> +int isc_mc_register(struct isc_device *isc)
> >> +{
> >> + int ret;
> >> +
> >> + ret = media_create_pad_link(&isc->current_subdev->sd->entity,
> >> + isc->remote_pad, &isc->scaler_sd.entity,
> >> + ISC_SCALER_PAD_SINK,
> >> + MEDIA_LNK_FL_ENABLED |
> >> + MEDIA_LNK_FL_IMMUTABLE);
> >> +
> >> + if (ret < 0) {
> >> + v4l2_err(&isc->v4l2_dev,
> >> + "Failed to create pad link: %s to %s\n",
> >> + isc->current_subdev->sd->entity.name,
> >> + isc->scaler_sd.entity.name);
> >> + return ret;
> >> + }
> >> +
> >> + dev_dbg(isc->dev, "link with %s pad: %d\n",
> >> + isc->current_subdev->sd->name, isc->remote_pad);
> >> +
> >> + ret = media_create_pad_link(&isc->scaler_sd.entity,
> >> + ISC_SCALER_PAD_SOURCE,
> >> + &isc->video_dev.entity, ISC_PAD_SINK,
> >> + MEDIA_LNK_FL_ENABLED |
> >> + MEDIA_LNK_FL_IMMUTABLE);
> >> +
> >> + if (ret < 0) {
> >> + v4l2_err(&isc->v4l2_dev,
> >> + "Failed to create pad link: %s to %s\n",
> >> + isc->scaler_sd.entity.name,
> >> + isc->video_dev.entity.name);
> >> + return ret;
> >> + }
> >> +
> >> + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
> >> + ISC_SCALER_PAD_SOURCE);
> >> +
> >> + return media_device_register(&isc->mdev);
> >
> > I feel like the isc-base driver shold instead register the
> > media-controller and this file shuld actually be only about
> > isc-scaler or something. I might be missing why it is not possible
> > though. It doesn't seem to me that mc support is conditional, so it
> > can happen in the base driver directly ?
>
> This actually is the base driver. Just that the atmel-isc-base.c is
> pretty crowded. So I moved the MC part in this file. But it's the same
> base driver.
>

I understand. isc-base is pretty big, you're right.
However breaking mc apart in a file that mixes registering an entity
for the scaler and initializing mc for the whole driver feels weird,
at least to me, as I don't immediately get why the mc part is handled
by the scaler file...

> >
> > Links creation can still happen at async_complete() (I would have
> > suggested _bound() time, but you have no asyn subdev for the scaler,
> > so I guess the only async subdev here is the sensor).
>
> Yes, the sensor or any kind of subdevice (XISC normally is connected to
> a bridge, ISC to a sensor directly )
>
> >
> > All in all: can this function be removed and:
> > 1) Base driver registers the media controller device in probe
>
> Should we register the media device even if it's not operational ? and
> no video device has been registered ?
> I thought it would be safer to have it registered with all the
> links/entities/etc once the video node has been completed.
>
> But if you say this is the proper way, I can do that.
> I wanted to keep things separated and not overcrowd the base file /probe
> function, but if it's just a media_device_register call, I can do it there.

You are right. Calling media_device_register() at async_complete time
is the right thing to do.

Thanks
j


>
> > 2) links are created at async_complete time ?
>
> That works too. I think I could move them there.
>
> Thanks for reviewing,
>
> Eugen
>
> >
> > Thanks
> > j
> >
> >
> >> +}
> >> +EXPORT_SYMBOL_GPL(isc_mc_register);
> >> +
> >> +void isc_mc_cleanup(struct isc_device *isc)
> >> +{
> >> + media_entity_cleanup(&isc->video_dev.entity);
> >> +}
> >> +EXPORT_SYMBOL_GPL(isc_mc_cleanup);
> >> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> >> index 07fa6dbf8460..0b6370d7775f 100644
> >> --- a/drivers/media/platform/atmel/atmel-isc.h
> >> +++ b/drivers/media/platform/atmel/atmel-isc.h
> >> @@ -183,6 +183,17 @@ struct isc_reg_offsets {
> >> u32 his_entry;
> >> };
> >>
> >> +enum isc_mc_pads {
> >> + ISC_PAD_SINK = 0,
> >> + ISC_PADS_NUM = 1,
> >> +};
> >> +
> >> +enum isc_scaler_pads {
> >> + ISC_SCALER_PAD_SINK = 0,
> >> + ISC_SCALER_PAD_SOURCE = 1,
> >> + ISC_SCALER_PADS_NUM = 2,
> >> +};
> >> +
> >> /*
> >> * struct isc_device - ISC device driver data/config struct
> >> * @regmap: Register map
> >> @@ -258,6 +269,12 @@ struct isc_reg_offsets {
> >> * be used as an input to the controller
> >> * @controller_formats_size: size of controller_formats array
> >> * @formats_list_size: size of formats_list array
> >> + * @pads: media controller pads for isc video entity
> >> + * @mdev: media device that is registered by the isc
> >> + * @remote_pad: remote pad on the connected subdevice
> >> + * @scaler_sd: subdevice for the scaler that isc registers
> >> + * @scaler_pads: media controller pads for the scaler subdevice
> >> + * @scaler_format: current format for the scaler subdevice
> >> */
> >> struct isc_device {
> >> struct regmap *regmap;
> >> @@ -346,6 +363,19 @@ struct isc_device {
> >> struct isc_format *formats_list;
> >> u32 controller_formats_size;
> >> u32 formats_list_size;
> >> +
> >> + struct {
> >> + struct media_pad pads[ISC_PADS_NUM];
> >> + struct media_device mdev;
> >> +
> >> + u32 remote_pad;
> >> + };
> >> +
> >> + struct {
> >> + struct v4l2_subdev scaler_sd;
> >> + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
> >> + struct v4l2_mbus_framefmt scaler_format;
> >> + };
> >> };
> >>
> >> extern const struct regmap_config isc_regmap_config;
> >> @@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
> >> void isc_subdev_cleanup(struct isc_device *isc);
> >> void isc_clk_cleanup(struct isc_device *isc);
> >>
> >> +int isc_mc_init(struct isc_device *isc, u32 ver);
> >> +int isc_mc_register(struct isc_device *isc);
> >> +void isc_mc_cleanup(struct isc_device *isc);
> >> +
> >> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
> >> + unsigned int code, int *index);
> >> #endif
> >> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> >> index 1b2063cce0f7..a3e4c32a66c4 100644
> >> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> >> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> >> @@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
> >> break;
> >> }
> >>
> >> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> >> +
> >> + ret = isc_mc_init(isc, ver);
> >> + if (ret < 0)
> >> + goto isc_probe_mc_init_err;
> >> +
> >> pm_runtime_set_active(dev);
> >> pm_runtime_enable(dev);
> >> pm_request_idle(dev);
> >> @@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
> >> ret = clk_prepare_enable(isc->ispck);
> >> if (ret) {
> >> dev_err(dev, "failed to enable ispck: %d\n", ret);
> >> - goto cleanup_subdev;
> >> + goto isc_probe_mc_init_err;
> >> }
> >>
> >> /* ispck should be greater or equal to hclock */
> >> @@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
> >> goto unprepare_clk;
> >> }
> >>
> >> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> >> dev_info(dev, "Microchip ISC version %x\n", ver);
> >>
> >> return 0;
> >> @@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
> >> unprepare_clk:
> >> clk_disable_unprepare(isc->ispck);
> >>
> >> +isc_probe_mc_init_err:
> >> + isc_mc_cleanup(isc);
> >> +
> >> cleanup_subdev:
> >> isc_subdev_cleanup(isc);
> >>
> >> @@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)
> >>
> >> pm_runtime_disable(&pdev->dev);
> >>
> >> + isc_mc_cleanup(isc);
> >> +
> >> isc_subdev_cleanup(isc);
> >>
> >> v4l2_device_unregister(&isc->v4l2_dev);
> >> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> >> index 2b1082295c13..d2ac80b1ccc3 100644
> >> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> >> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> >> @@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
> >> break;
> >> }
> >>
> >> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> >> +
> >> + ret = isc_mc_init(isc, ver);
> >> + if (ret < 0)
> >> + goto isc_probe_mc_init_err;
> >> +
> >> pm_runtime_set_active(dev);
> >> pm_runtime_enable(dev);
> >> pm_request_idle(dev);
> >>
> >> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
> >> dev_info(dev, "Microchip XISC version %x\n", ver);
> >>
> >> return 0;
> >>
> >> +isc_probe_mc_init_err:
> >> + isc_mc_cleanup(isc);
> >> +
> >> cleanup_subdev:
> >> isc_subdev_cleanup(isc);
> >>
> >> @@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)
> >>
> >> pm_runtime_disable(&pdev->dev);
> >>
> >> + isc_mc_cleanup(isc);
> >> +
> >> isc_subdev_cleanup(isc);
> >>
> >> v4l2_device_unregister(&isc->v4l2_dev);
> >> --
> >> 2.25.1
> >>
>

2022-01-11 17:00:36

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 12/23] media: atmel: atmel-isc: implement media controller

On 1/11/22 6:51 PM, Jacopo Mondi wrote:
> On Tue, Jan 11, 2022 at 04:30:15PM +0000, [email protected] wrote:
>> On 1/11/22 6:06 PM, Jacopo Mondi wrote:
>>> Hi Eugen,
>>>
>>> On Mon, Dec 13, 2021 at 03:49:29PM +0200, Eugen Hristev wrote:
>>>> Implement the support for media-controller.
>>>> This means that the capabilities of the driver have changed and now
>>>> it also advertises the IO_MC .
>>>> The driver will register it's media device, and add the video entity to this
>>>> media device. The subdevices are registered to the same media device.
>>>> The ISC will have a base entity which is auto-detected as atmel_isc_base.
>>>> It will also register a subdevice that allows cropping of the incoming frame
>>>> to the maximum frame size supported by the ISC.
>>>> The ISC will create a link between the subdevice that is asynchronously
>>>> registered and the atmel_isc_scaler entity.
>>>> Then, the atmel_isc_scaler and atmel_isc_base are connected through another
>>>> link.
>>>>
>>>> Signed-off-by: Eugen Hristev <[email protected]>
>>>> ---
>>>> Changes in v2:
>>>> - implement try formats
>>>>
>>>> drivers/media/platform/atmel/Makefile | 2 +-
>>>> drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
>>>> drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
>>>> drivers/media/platform/atmel/atmel-isc.h | 36 +++
>>>> .../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
>>>> .../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
>>>> 6 files changed, 370 insertions(+), 8 deletions(-)
>>>> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
>>>>
>>>> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
>>>> index 794e8f739287..aa7a18b428f6 100644
>>>> --- a/drivers/media/platform/atmel/Makefile
>>>> +++ b/drivers/media/platform/atmel/Makefile
>>>> @@ -1,7 +1,7 @@
>>>> # SPDX-License-Identifier: GPL-2.0-only
>>>> atmel-isc-objs = atmel-sama5d2-isc.o
>>>> atmel-xisc-objs = atmel-sama7g5-isc.o
>>>> -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
>>>> +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o
>>>>
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
>>>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>>>> index d0542b97a391..b0c3ed21f372 100644
>>>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>>>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>>>> @@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
>>>> struct isc_device, v4l2_dev);
>>>> struct isc_subdev_entity *subdev_entity =
>>>> container_of(notifier, struct isc_subdev_entity, notifier);
>>>> + int pad;
>>>>
>>>> if (video_is_registered(&isc->video_dev)) {
>>>> v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
>>>> @@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
>>>>
>>>> subdev_entity->sd = subdev;
>>>>
>>>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
>>>> + MEDIA_PAD_FL_SOURCE);
>>>> + if (pad < 0) {
>>>> + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
>>>> + subdev->name);
>>>> + return pad;
>>>> + }
>>>> +
>>>> + isc->remote_pad = pad;
>>>> +
>>>> return 0;
>>>> }
>>>>
>>>> @@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
>>>> v4l2_ctrl_handler_free(&isc->ctrls.handler);
>>>> }
>>>>
>>>> -static struct isc_format *find_format_by_code(struct isc_device *isc,
>>>> - unsigned int code, int *index)
>>>> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
>>>> + unsigned int code, int *index)
>>>> {
>>>> struct isc_format *fmt = &isc->formats_list[0];
>>>> unsigned int i;
>>>> @@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,
>>>>
>>>> return NULL;
>>>> }
>>>> +EXPORT_SYMBOL_GPL(isc_find_format_by_code);
>>>>
>>>> static int isc_formats_init(struct isc_device *isc)
>>>> {
>>>> @@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
>>>> NULL, &mbus_code)) {
>>>> mbus_code.index++;
>>>>
>>>> - fmt = find_format_by_code(isc, mbus_code.code, &i);
>>>> + fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
>>>> if (!fmt) {
>>>> v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
>>>> mbus_code.code);
>>>> @@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>>>> vdev->queue = q;
>>>> vdev->lock = &isc->lock;
>>>> vdev->ctrl_handler = &isc->ctrls.handler;
>>>> - vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
>>>> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
>>>> + V4L2_CAP_IO_MC;
>>>> video_set_drvdata(vdev, isc);
>>>>
>>>> ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>> @@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>>>> goto isc_async_complete_err;
>>>> }
>>>>
>>>> + ret = isc_mc_register(isc);
>>>> + if (ret < 0)
>>>> + goto isc_async_complete_unregister_device;
>>>> return 0;
>>>>
>>>> +isc_async_complete_unregister_device:
>>>> + video_unregister_device(vdev);
>>>> +
>>>> isc_async_complete_err:
>>>> mutex_destroy(&isc->lock);
>>>> return ret;
>>>> diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
>>>> new file mode 100644
>>>> index 000000000000..2aea81d77b2f
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/atmel/atmel-isc-mc.c
>>>> @@ -0,0 +1,287 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Microchip Image Sensor Controller (ISC) Media Controller support
>>>> + *
>>>> + * Copyright (C) 2021 Microchip Technology, Inc.
>>>> + *
>>>> + * Author: Eugen Hristev <[email protected]>
>>>> + *
>>>> + */
>>>> +
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-subdev.h>
>>>
>>> Should you include <media/media-entity.h> and <media/media-device.h> ?
>>
>> Hi Jacopo,
>>
>> They are probably being included from another header. I will add them.
>>
>
> Thank you :)
>
>>>
>>> Also, should you select MEDIA_CONTROLLER and V4L2_SUBDEV_API for all
>>> the Kconfig symbols ?
>>
>>
>> VIDEO_ATMEL_ISC directly selects:
>> select MEDIA_CONTROLLER
>> select VIDEO_V4L2_SUBDEV_API
>>
>> and VIDEO_ATMEL_XISC
>> depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API
>
> And selects VIDEO_ATMEL_ISC_BASE so it's fine!
>
> VIDEO_ATMEL_ISI does not. I'm fine if this is intended, I might have
> got lost in the several pieces this driver is composed of :)

The ISI is a totally different driver with no code shared with the ISC.
It's a different hardware, on some older platforms.
It does not support media controller and will not in the near future.

Initially there was ISI, then ISC appeared and a new driver was created.
Then a newer version of the ISC appeared named XISC, but a lot was
common with the old ISC, so I created a new driver XISC with shared
common base with the old ISC.
So now we have ISI, and ISC/XISC who share ATMEL_ISC_BASE

>
>>
>> and VIDEO_V4L2_SUBDEV_API depends on MEDIA_CONTROLLER
>>
>>>
>>>> +
>>>> +#include "atmel-isc-regs.h"
>>>> +#include "atmel-isc.h"
>>>> +
>>>> +static const struct media_device_ops isc_media_ops = {
>>>> +};
>>>
>>> Do you need this even if it's empty ?
>>>
>>
>> Not sure. I can remove it and pass NULL, but I haven't checked what the
>> subsystem is doing if there is no ops. I will try.
>>
>
> Seems like it is safe as the core checks for if (mdev->ops) before
> accessing it (except for mc-request for which operations are
> mandatory)
>
>>>> +
>>>> +static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_format *format)
>>>> +{
>>>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>>>> +
>>>> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
>>>> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
>>>> + format->pad);
>>>> + format->format = *v4l2_try_fmt;
>>>> +
>>>> + return 0;
>>>> + }
>>>> +
>>>> + format->format = isc->scaler_format;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_format *req_fmt)
>>>> +{
>>>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>>>> + struct isc_format *fmt;
>>>> + unsigned int i;
>>>> +
>>>> + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
>>>> + v4l_bound_align_image
>>>> + (&req_fmt->format.width, 16, isc->max_width, 0,
>>>> + &req_fmt->format.height, 16, isc->max_height, 0, 0);
>>>> + else
>>>> + v4l_bound_align_image
>>>> + (&req_fmt->format.width, 16, 10000, 0,
>>>> + &req_fmt->format.height, 16, 10000, 0, 0);
>>>> +
>>>> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
>>>> + req_fmt->format.field = V4L2_FIELD_NONE;
>>>> + req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> + req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> + req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>> +
>>>> + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
>>>> +
>>>> + if (!fmt)
>>>> + fmt = &isc->formats_list[0];
>>>> +
>>>> + req_fmt->format.code = fmt->mbus_code;
>>>> +
>>>> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>>>> + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
>>>> + req_fmt->pad);
>>>> + *v4l2_try_fmt = req_fmt->format;
>>>> + /* Trying on the pad sink makes the source sink change too */
>>>> + if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
>>>> + v4l2_try_fmt =
>>>> + v4l2_subdev_get_try_format(sd, sd_state,
>>>> + ISC_SCALER_PAD_SOURCE);
>>>> + *v4l2_try_fmt = req_fmt->format;
>>>> +
>>>> + v4l_bound_align_image(&v4l2_try_fmt->width,
>>>> + 16, isc->max_width, 0,
>>>> + &v4l2_try_fmt->height,
>>>> + 16, isc->max_height, 0, 0);
>>>> + }
>>>> + /* if we are just trying, we are done */
>>>> + return 0;
>>>> + }
>>>> +
>>>> + isc->scaler_format = req_fmt->format;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>>>> + int supported_index = 0;
>>>> + int i;
>>>> +
>>>> + for (i = 0; i < isc->formats_list_size; i++) {
>>>> + if (!isc->formats_list[i].sd_support)
>>>> + continue;
>>>> + if (supported_index == code->index) {
>>>> + code->code = isc->formats_list[i].mbus_code;
>>>> + return 0;
>>>> + }
>>>> + supported_index++;
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static int isc_scaler_g_sel(struct v4l2_subdev *sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_selection *sel)
>>>> +{
>>>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>>>> +
>>>> + if (sel->pad == ISC_SCALER_PAD_SOURCE)
>>>> + return -EINVAL;
>>>> +
>>>> + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
>>>> + sel->target != V4L2_SEL_TGT_CROP)
>>>> + return -EINVAL;
>>>> +
>>>> + sel->r.height = isc->max_height;
>>>> + sel->r.width = isc->max_width;
>>>> +
>>>> + sel->r.left = 0;
>>>> + sel->r.top = 0;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
>>>> + struct v4l2_subdev_state *sd_state)
>>>> +{
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
>>>> + v4l2_subdev_get_try_format(sd, sd_state, 0);
>>>> + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
>>>> +
>>>> + *v4l2_try_fmt = isc->scaler_format;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
>>>> + .enum_mbus_code = isc_scaler_enum_mbus_code,
>>>> + .set_fmt = isc_scaler_set_fmt,
>>>> + .get_fmt = isc_scaler_get_fmt,
>>>> + .get_selection = isc_scaler_g_sel,
>>>> + .init_cfg = isc_scaler_init_cfg,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
>>>> + .pad = &isc_scaler_pad_ops,
>>>> +};
>>>> +
>>>> +static int isc_init_own_sd(struct isc_device *isc)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
>>>> +
>>>> + isc->scaler_sd.owner = THIS_MODULE;
>>>> + isc->scaler_sd.dev = isc->dev;
>>>> + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
>>>> + "atmel_isc_scaler");
>>>> +
>>>> + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>>>> + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>>> +
>>>> + isc->scaler_format.height = isc->max_height;
>>>> + isc->scaler_format.width = isc->max_width;
>>>> + isc->scaler_format.code = isc->formats_list[0].mbus_code;
>>>> + isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
>>>> + isc->scaler_format.field = V4L2_FIELD_NONE;
>>>> + isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> + isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> + isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>> +
>>>> + ret = media_entity_pads_init(&isc->scaler_sd.entity,
>>>> + ISC_SCALER_PADS_NUM,
>>>> + isc->scaler_pads);
>>>> + if (ret < 0) {
>>>> + dev_err(isc->dev, "scaler sd media entity init failed\n");
>>>> + return ret;
>>>> + }
>>>> + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
>>>> + if (ret < 0) {
>>>> + dev_err(isc->dev, "scaler sd failed to register subdev\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +int isc_mc_init(struct isc_device *isc, u32 ver)
>>>> +{
>>>> + const struct of_device_id *match;
>>>> + int ret;
>>>> +
>>>> + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
>>>> + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
>>>> + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +
>>>> + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
>>>> + isc->pads);
>>>> + if (ret < 0) {
>>>> + dev_err(isc->dev, "media entity init failed\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + isc->mdev.dev = isc->dev;
>>>> + isc->mdev.ops = &isc_media_ops;
>>>> +
>>>> + match = of_match_node(isc->dev->driver->of_match_table,
>>>> + isc->dev->of_node);
>>>> +
>>>> + strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
>>>> + sizeof(isc->mdev.driver_name));
>>>> + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
>>>> + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
>>>> + isc->v4l2_dev.name);
>>>> + isc->mdev.hw_revision = ver;
>>>> +
>>>> + media_device_init(&isc->mdev);
>>>> +
>>>> + isc->v4l2_dev.mdev = &isc->mdev;
>>>> +
>>>> + return isc_init_own_sd(isc);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(isc_mc_init);
>>>> +
>>>> +int isc_mc_register(struct isc_device *isc)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + ret = media_create_pad_link(&isc->current_subdev->sd->entity,
>>>> + isc->remote_pad, &isc->scaler_sd.entity,
>>>> + ISC_SCALER_PAD_SINK,
>>>> + MEDIA_LNK_FL_ENABLED |
>>>> + MEDIA_LNK_FL_IMMUTABLE);
>>>> +
>>>> + if (ret < 0) {
>>>> + v4l2_err(&isc->v4l2_dev,
>>>> + "Failed to create pad link: %s to %s\n",
>>>> + isc->current_subdev->sd->entity.name,
>>>> + isc->scaler_sd.entity.name);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + dev_dbg(isc->dev, "link with %s pad: %d\n",
>>>> + isc->current_subdev->sd->name, isc->remote_pad);
>>>> +
>>>> + ret = media_create_pad_link(&isc->scaler_sd.entity,
>>>> + ISC_SCALER_PAD_SOURCE,
>>>> + &isc->video_dev.entity, ISC_PAD_SINK,
>>>> + MEDIA_LNK_FL_ENABLED |
>>>> + MEDIA_LNK_FL_IMMUTABLE);
>>>> +
>>>> + if (ret < 0) {
>>>> + v4l2_err(&isc->v4l2_dev,
>>>> + "Failed to create pad link: %s to %s\n",
>>>> + isc->scaler_sd.entity.name,
>>>> + isc->video_dev.entity.name);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
>>>> + ISC_SCALER_PAD_SOURCE);
>>>> +
>>>> + return media_device_register(&isc->mdev);
>>>
>>> I feel like the isc-base driver shold instead register the
>>> media-controller and this file shuld actually be only about
>>> isc-scaler or something. I might be missing why it is not possible
>>> though. It doesn't seem to me that mc support is conditional, so it
>>> can happen in the base driver directly ?
>>
>> This actually is the base driver. Just that the atmel-isc-base.c is
>> pretty crowded. So I moved the MC part in this file. But it's the same
>> base driver.
>>
>
> I understand. isc-base is pretty big, you're right.
> However breaking mc apart in a file that mixes registering an entity
> for the scaler and initializing mc for the whole driver feels weird,
> at least to me, as I don't immediately get why the mc part is handled
> by the scaler file...
>
>>>
>>> Links creation can still happen at async_complete() (I would have
>>> suggested _bound() time, but you have no asyn subdev for the scaler,
>>> so I guess the only async subdev here is the sensor).
>>
>> Yes, the sensor or any kind of subdevice (XISC normally is connected to
>> a bridge, ISC to a sensor directly )
>>
>>>
>>> All in all: can this function be removed and:
>>> 1) Base driver registers the media controller device in probe
>>
>> Should we register the media device even if it's not operational ? and
>> no video device has been registered ?
>> I thought it would be safer to have it registered with all the
>> links/entities/etc once the video node has been completed.
>>
>> But if you say this is the proper way, I can do that.
>> I wanted to keep things separated and not overcrowd the base file /probe
>> function, but if it's just a media_device_register call, I can do it there.
>
> You are right. Calling media_device_register() at async_complete time
> is the right thing to do.
>
> Thanks
> j
>
>
>>
>>> 2) links are created at async_complete time ?
>>
>> That works too. I think I could move them there.
>>
>> Thanks for reviewing,
>>
>> Eugen
>>
>>>
>>> Thanks
>>> j
>>>
>>>
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(isc_mc_register);
>>>> +
>>>> +void isc_mc_cleanup(struct isc_device *isc)
>>>> +{
>>>> + media_entity_cleanup(&isc->video_dev.entity);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(isc_mc_cleanup);
>>>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>>>> index 07fa6dbf8460..0b6370d7775f 100644
>>>> --- a/drivers/media/platform/atmel/atmel-isc.h
>>>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>>>> @@ -183,6 +183,17 @@ struct isc_reg_offsets {
>>>> u32 his_entry;
>>>> };
>>>>
>>>> +enum isc_mc_pads {
>>>> + ISC_PAD_SINK = 0,
>>>> + ISC_PADS_NUM = 1,
>>>> +};
>>>> +
>>>> +enum isc_scaler_pads {
>>>> + ISC_SCALER_PAD_SINK = 0,
>>>> + ISC_SCALER_PAD_SOURCE = 1,
>>>> + ISC_SCALER_PADS_NUM = 2,
>>>> +};
>>>> +
>>>> /*
>>>> * struct isc_device - ISC device driver data/config struct
>>>> * @regmap: Register map
>>>> @@ -258,6 +269,12 @@ struct isc_reg_offsets {
>>>> * be used as an input to the controller
>>>> * @controller_formats_size: size of controller_formats array
>>>> * @formats_list_size: size of formats_list array
>>>> + * @pads: media controller pads for isc video entity
>>>> + * @mdev: media device that is registered by the isc
>>>> + * @remote_pad: remote pad on the connected subdevice
>>>> + * @scaler_sd: subdevice for the scaler that isc registers
>>>> + * @scaler_pads: media controller pads for the scaler subdevice
>>>> + * @scaler_format: current format for the scaler subdevice
>>>> */
>>>> struct isc_device {
>>>> struct regmap *regmap;
>>>> @@ -346,6 +363,19 @@ struct isc_device {
>>>> struct isc_format *formats_list;
>>>> u32 controller_formats_size;
>>>> u32 formats_list_size;
>>>> +
>>>> + struct {
>>>> + struct media_pad pads[ISC_PADS_NUM];
>>>> + struct media_device mdev;
>>>> +
>>>> + u32 remote_pad;
>>>> + };
>>>> +
>>>> + struct {
>>>> + struct v4l2_subdev scaler_sd;
>>>> + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
>>>> + struct v4l2_mbus_framefmt scaler_format;
>>>> + };
>>>> };
>>>>
>>>> extern const struct regmap_config isc_regmap_config;
>>>> @@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
>>>> void isc_subdev_cleanup(struct isc_device *isc);
>>>> void isc_clk_cleanup(struct isc_device *isc);
>>>>
>>>> +int isc_mc_init(struct isc_device *isc, u32 ver);
>>>> +int isc_mc_register(struct isc_device *isc);
>>>> +void isc_mc_cleanup(struct isc_device *isc);
>>>> +
>>>> +struct isc_format *isc_find_format_by_code(struct isc_device *isc,
>>>> + unsigned int code, int *index);
>>>> #endif
>>>> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>>>> index 1b2063cce0f7..a3e4c32a66c4 100644
>>>> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>>>> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
>>>> @@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
>>>> break;
>>>> }
>>>>
>>>> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>>>> +
>>>> + ret = isc_mc_init(isc, ver);
>>>> + if (ret < 0)
>>>> + goto isc_probe_mc_init_err;
>>>> +
>>>> pm_runtime_set_active(dev);
>>>> pm_runtime_enable(dev);
>>>> pm_request_idle(dev);
>>>> @@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
>>>> ret = clk_prepare_enable(isc->ispck);
>>>> if (ret) {
>>>> dev_err(dev, "failed to enable ispck: %d\n", ret);
>>>> - goto cleanup_subdev;
>>>> + goto isc_probe_mc_init_err;
>>>> }
>>>>
>>>> /* ispck should be greater or equal to hclock */
>>>> @@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
>>>> goto unprepare_clk;
>>>> }
>>>>
>>>> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>>>> dev_info(dev, "Microchip ISC version %x\n", ver);
>>>>
>>>> return 0;
>>>> @@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
>>>> unprepare_clk:
>>>> clk_disable_unprepare(isc->ispck);
>>>>
>>>> +isc_probe_mc_init_err:
>>>> + isc_mc_cleanup(isc);
>>>> +
>>>> cleanup_subdev:
>>>> isc_subdev_cleanup(isc);
>>>>
>>>> @@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)
>>>>
>>>> pm_runtime_disable(&pdev->dev);
>>>>
>>>> + isc_mc_cleanup(isc);
>>>> +
>>>> isc_subdev_cleanup(isc);
>>>>
>>>> v4l2_device_unregister(&isc->v4l2_dev);
>>>> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>>>> index 2b1082295c13..d2ac80b1ccc3 100644
>>>> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>>>> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
>>>> @@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
>>>> break;
>>>> }
>>>>
>>>> + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>>>> +
>>>> + ret = isc_mc_init(isc, ver);
>>>> + if (ret < 0)
>>>> + goto isc_probe_mc_init_err;
>>>> +
>>>> pm_runtime_set_active(dev);
>>>> pm_runtime_enable(dev);
>>>> pm_request_idle(dev);
>>>>
>>>> - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
>>>> dev_info(dev, "Microchip XISC version %x\n", ver);
>>>>
>>>> return 0;
>>>>
>>>> +isc_probe_mc_init_err:
>>>> + isc_mc_cleanup(isc);
>>>> +
>>>> cleanup_subdev:
>>>> isc_subdev_cleanup(isc);
>>>>
>>>> @@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)
>>>>
>>>> pm_runtime_disable(&pdev->dev);
>>>>
>>>> + isc_mc_cleanup(isc);
>>>> +
>>>> isc_subdev_cleanup(isc);
>>>>
>>>> v4l2_device_unregister(&isc->v4l2_dev);
>>>> --
>>>> 2.25.1
>>>>
>>

2022-01-12 08:57:49

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 17/23] media: atmel: atmel-isc-base: use mutex to lock awb workqueue from streaming

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:34PM +0200, Eugen Hristev wrote:
> The AWB workqueue runs in a kernel thread and needs to be synchronized
> w.r.t. the streaming status.
> It is possible that streaming is stopped while the AWB workq is running.
> In this case it is likely that the check for isc->stop is done at one point
> in time, but the AWB computations are done later, including a call to
> isc_update_profile, which requires streaming to be started.
> Thus , isc_update_profile will fail if during this operation sequence the
> streaming was stopped.
> To solve this issue, a mutex is added, that will serialize the awb work and
> streaming stopping, with the mention that either streaming is stopped
> completely including termination of the last frame is done, and after that
> the AWB work can check stream status and stop; either first AWB work is
> completed and after that the streaming can stop correctly.
> The awb spin lock cannot be used since this spinlock is taken in the same
> context and using it in the stop streaming will result in a recursion BUG.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> drivers/media/platform/atmel/atmel-isc-base.c | 31 ++++++++++++++++---
> drivers/media/platform/atmel/atmel-isc.h | 1 +
> 2 files changed, 28 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index b0c3ed21f372..53cac1aac0fd 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -401,6 +401,7 @@ static void isc_stop_streaming(struct vb2_queue *vq)
> struct isc_buffer *buf;
> int ret;
>
> + mutex_lock(&isc->awb_mutex);
> v4l2_ctrl_activate(isc->do_wb_ctrl, false);
>
> isc->stop = true;
> @@ -410,6 +411,8 @@ static void isc_stop_streaming(struct vb2_queue *vq)
> v4l2_err(&isc->v4l2_dev,
> "Timeout waiting for end of the capture\n");
>
> + mutex_unlock(&isc->awb_mutex);
> +
> /* Disable DMA interrupt */
> regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE);
>
> @@ -1416,10 +1419,6 @@ static void isc_awb_work(struct work_struct *w)
> u32 min, max;
> int ret;
>
> - /* streaming is not active anymore */
> - if (isc->stop)
> - return;
> -
> if (ctrls->hist_stat != HIST_ENABLED)
> return;
>
> @@ -1470,7 +1469,24 @@ static void isc_awb_work(struct work_struct *w)
> }
> regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his,
> hist_id | baysel | ISC_HIS_CFG_RAR);

isc_stop_streaming() calls runtime_put and here you access the hw.

Feels like it's safer to hold the mutex for the whole duration of the
AWB routine ?

> +
> + /*
> + * We have to make sure the streaming has not stopped meanwhile.
> + * ISC requires a frame to clock the internal profile update.
> + * To avoid issues, lock the sequence with a mutex
> + */
> + mutex_lock(&isc->awb_mutex);
> +
> + /* streaming is not active anymore */
> + if (isc->stop) {
> + mutex_unlock(&isc->awb_mutex);
> + return;
> + };
> +
> isc_update_profile(isc);
> +
> + mutex_unlock(&isc->awb_mutex);
> +
> /* if awb has been disabled, we don't need to start another histogram */
> if (ctrls->awb)
> regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ);
> @@ -1549,6 +1565,8 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>
> isc_update_awb_ctrls(isc);
>
> + mutex_lock(&isc->awb_mutex);
> +
> if (!isc->stop) {
> /*
> * If we are streaming, we can update profile to
> @@ -1563,6 +1581,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
> */
> v4l2_ctrl_activate(isc->do_wb_ctrl, false);
> }
> + mutex_unlock(&isc->awb_mutex);
>
> /* if we have autowhitebalance on, start histogram procedure */
> if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
> @@ -1754,6 +1773,7 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
> {
> struct isc_device *isc = container_of(notifier->v4l2_dev,
> struct isc_device, v4l2_dev);
> + mutex_destroy(&isc->awb_mutex);
> cancel_work_sync(&isc->awb_work);
> video_unregister_device(&isc->video_dev);
> v4l2_ctrl_handler_free(&isc->ctrls.handler);
> @@ -1866,6 +1886,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> isc->current_subdev = container_of(notifier,
> struct isc_subdev_entity, notifier);
> mutex_init(&isc->lock);
> + mutex_init(&isc->awb_mutex);
> +
> init_completion(&isc->comp);
>
> /* Initialize videobuf2 queue */
> @@ -1941,6 +1963,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
> video_unregister_device(vdev);
>
> isc_async_complete_err:
> + mutex_destroy(&isc->awb_mutex);
> mutex_destroy(&isc->lock);
> return ret;
> }
> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> index 0b6370d7775f..c2cb805faff3 100644
> --- a/drivers/media/platform/atmel/atmel-isc.h
> +++ b/drivers/media/platform/atmel/atmel-isc.h
> @@ -307,6 +307,7 @@ struct isc_device {
> struct work_struct awb_work;
>
> struct mutex lock; /* serialize access to file operations */
> + struct mutex awb_mutex; /* serialize access to streaming status from awb work queue */
> spinlock_t awb_lock; /* serialize access to DMA buffers from awb work queue */
>
> struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM];
> --
> 2.25.1
>

2022-01-12 08:59:09

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 18/23] media: atmel: atmel-isc-base: add wb debug messages

Hello Eugen,

On Mon, Dec 13, 2021 at 03:49:35PM +0200, Eugen Hristev wrote:
> Add debug messages that make it easier to debug white balance algorithm.
>
> Signed-off-by: Eugen Hristev <[email protected]>

The dev_dbg() infrastructure is preferred, as far as I know v4l2_dbg()
is legacy and should be removed.

However the driver uses v4l2_dbg() already, so
Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j

> ---
> drivers/media/platform/atmel/atmel-isc-base.c | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index 53cac1aac0fd..f1f1019f9d82 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -1329,10 +1329,15 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
>
> if (!*min)
> *min = 1;
> +
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "isc wb: hist_id %u, hist_count %u",
> + ctrls->hist_id, *hist_count);
> }
>
> static void isc_wb_update(struct isc_ctrls *ctrls)
> {
> + struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls);
> u32 *hist_count = &ctrls->hist_count[0];
> u32 c, offset[4];
> u64 avg = 0;
> @@ -1349,6 +1354,9 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
> (u64)hist_count[ISC_HIS_CFG_MODE_GB];
> avg >>= 1;
>
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "isc wb: green components average %llu\n", avg);
> +
> /* Green histogram is null, nothing to do */
> if (!avg)
> return;
> @@ -1401,9 +1409,15 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
> else
> gw_gain[c] = 1 << 9;
>
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "isc wb: component %d, s_gain %u, gw_gain %u\n",
> + c, s_gain[c], gw_gain[c]);
> /* multiply both gains and adjust for decimals */
> ctrls->gain[c] = s_gain[c] * gw_gain[c];
> ctrls->gain[c] >>= 9;
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "isc wb: component %d, final gain %u\n",
> + c, ctrls->gain[c]);
> }
> }
>
> @@ -1423,6 +1437,10 @@ static void isc_awb_work(struct work_struct *w)
> return;
>
> isc_hist_count(isc, &min, &max);
> +
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max);
> +
> ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min;
> ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max;
>
> --
> 2.25.1
>

2022-01-12 09:03:10

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 19/23] media: atmel: atmel-isc-base: clamp wb gain coefficients

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:36PM +0200, Eugen Hristev wrote:
> White balance computed gains can overflow above the 13 bits hardware
> coefficient that can be used, in some specific scenarios like a subexposure
> from the sensor when the image is mostly black.
> In this case the computed gain has to be clamped to the maximum value
> allowed by the hardware.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> drivers/media/platform/atmel/atmel-isc-base.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index f1f1019f9d82..31c8e3029eee 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -1415,6 +1415,10 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
> /* multiply both gains and adjust for decimals */
> ctrls->gain[c] = s_gain[c] * gw_gain[c];
> ctrls->gain[c] >>= 9;
> +
> + /* make sure we are not out of range */
> + ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0));

#include <linux/minmax.h>

What is the type of gain[c} ? If it's unsigned clamping with [0 is not
required. I would in that case gain[c] = min(gain[c], ...)

It's fine anyway
Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j


> +
> v4l2_dbg(1, debug, &isc->v4l2_dev,
> "isc wb: component %d, final gain %u\n",
> c, ctrls->gain[c]);
> --
> 2.25.1
>

2022-01-12 09:03:40

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 20/23] media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:37PM +0200, Eugen Hristev wrote:
> Fix the mbus code for UYVY input format type to the right color rotation.
>
> Signed-off-by: Eugen Hristev <[email protected]>

Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j

> ---
> drivers/media/platform/atmel/atmel-sama7g5-isc.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> index d2ac80b1ccc3..38721bd902e2 100644
> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> @@ -188,7 +188,7 @@ static struct isc_format sama7g5_formats_list[] = {
> },
> {
> .fourcc = V4L2_PIX_FMT_UYVY,
> - .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
> .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT,
> },
> {
> --
> 2.25.1
>

2022-01-12 09:04:44

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 21/23] media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:38PM +0200, Eugen Hristev wrote:
> The ISC can dump the 8 bit and 10 bit raw bayer formats directly to
> the memory.
> Thus, add them to the supported output format list.
>
> Signed-off-by: Eugen Hristev <[email protected]>

Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j

> ---
> .../media/platform/atmel/atmel-sama5d2-isc.c | 24 +++++++++++++++++++
> .../media/platform/atmel/atmel-sama7g5-isc.c | 24 +++++++++++++++++++
> 2 files changed, 48 insertions(+)
>
> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> index a8d4ba60d3ac..025c3e8a7e95 100644
> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> @@ -88,6 +88,30 @@ static const struct isc_format sama5d2_controller_formats[] = {
> {
> .fourcc = V4L2_PIX_FMT_Y10,
> },
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGBRG10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGRBG10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SRGGB10,
> + },
> };
>
> /* This is a list of formats that the ISC can receive as *input* */
> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> index 38721bd902e2..9dc75eed0098 100644
> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> @@ -100,6 +100,30 @@ static const struct isc_format sama7g5_controller_formats[] = {
> {
> .fourcc = V4L2_PIX_FMT_Y16,
> },
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGBRG10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SGRBG10,
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_SRGGB10,
> + },
> };
>
> /* This is a list of formats that the ISC can receive as *input* */
> --
> 2.25.1
>

2022-01-12 09:05:25

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 22/23] media: atmel: atmel-isc: compact the controller formats list

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:39PM +0200, Eugen Hristev wrote:
> Compact the list array to be more readable.
> No other changes, only cosmetic.
>
> Signed-off-by: Eugen Hristev <[email protected]>

Reviewed-by: Jacopo Mondi <[email protected]>

Thanks
j

> ---
> .../media/platform/atmel/atmel-sama5d2-isc.c | 51 ++++++----------
> .../media/platform/atmel/atmel-sama7g5-isc.c | 60 +++++++------------
> 2 files changed, 37 insertions(+), 74 deletions(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> index 025c3e8a7e95..d96ee3373889 100644
> --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
> @@ -60,56 +60,39 @@
> static const struct isc_format sama5d2_controller_formats[] = {
> {
> .fourcc = V4L2_PIX_FMT_ARGB444,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_ARGB555,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_RGB565,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_ABGR32,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_XBGR32,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUV420,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUYV,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUV422P,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_GREY,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_Y10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SBGGR8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGBRG8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGRBG8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SRGGB8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SBGGR10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGBRG10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGRBG10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SRGGB10,
> },
> };
> diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> index 9dc75eed0098..e07ae188c15f 100644
> --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
> @@ -63,65 +63,45 @@
> static const struct isc_format sama7g5_controller_formats[] = {
> {
> .fourcc = V4L2_PIX_FMT_ARGB444,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_ARGB555,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_RGB565,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_ABGR32,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_XBGR32,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUV420,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_UYVY,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_VYUY,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUYV,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_YUV422P,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_GREY,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_Y10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_Y16,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SBGGR8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGBRG8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGRBG8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SRGGB8,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SBGGR10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGBRG10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SGRBG10,
> - },
> - {
> + }, {
> .fourcc = V4L2_PIX_FMT_SRGGB10,
> },
> };
> --
> 2.25.1
>

2022-01-12 09:20:43

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 23/23] media: atmel: atmel-isc: change format propagation to subdev into only verification

Hi Eugen

On Mon, Dec 13, 2021 at 03:49:40PM +0200, Eugen Hristev wrote:
> As a top MC video driver, the atmel-isc should not propagate the format to the
> subdevice.
> It should rather check at streamon() time if the subdev is properly configured
> with a compatible format.
> Removed the whole format finding logic, and reworked the format verification
> at streamon time, such that the ISC will return an error if the subdevice
> is not properly configured.
> With this being done, the module parameter 'sensor_prefered' makes no sense
> anymore. The ISC should not decide which format the sensor is using. The
> ISC should only cope with the situation and inform userspace if the streaming
> is possible in the current configuration.

Sounds great!

>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v3:
> - clamp to maximum resolution once the frame size from the subdev is found
>
> drivers/media/platform/atmel/atmel-isc-base.c | 271 ++++++++----------
> drivers/media/platform/atmel/atmel-isc.h | 1 +
> 2 files changed, 126 insertions(+), 146 deletions(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index 31c8e3029eee..00c8c9588a78 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -36,11 +36,6 @@ static unsigned int debug;
> module_param(debug, int, 0644);
> MODULE_PARM_DESC(debug, "debug level (0-2)");
>
> -static unsigned int sensor_preferred = 1;
> -module_param(sensor_preferred, uint, 0644);
> -MODULE_PARM_DESC(sensor_preferred,
> - "Sensor is preferred to output the specified format (1-on 0-off), default 1");
> -
> #define ISC_IS_FORMAT_RAW(mbus_code) \
> (((mbus_code) & 0xf000) == 0x3000)
>
> @@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
> * convert it to any of the formats that we usually can with a
> * RAW sensor. Thus, do not advertise them.
> */
> - if (!isc->config.sd_format ||
> + if (isc->config.sd_format &&

Is this change intentional ?

> !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
> return -EINVAL;
>
> @@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
> break;
> default:
> /* any other different formats are not supported */
> + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
> ret = -EINVAL;
> }
> v4l2_dbg(1, debug, &isc->v4l2_dev,
> "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
> rgb, yuv, grey, bayer);

Would it make sense to move this before the switch so that the
error messages, if any, appear later ?

>
> - /* we cannot output RAW if we do not receive RAW */
> - if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
> + if ((bayer) &&
> + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
> + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
> return -EINVAL;
> + }
>
> - /* we cannot output GREY if we do not receive RAW/GREY */
> if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
> - !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
> + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
> + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
> return -EINVAL;
> + }
> +
> + if ((rgb || bayer || yuv) &&
> + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
> + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
> + return -EINVAL;
> + }
>
> return ret;
> }
> @@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
> * If we do not know yet which format the subdev is using, we cannot
> * do anything.
> */
> - if (!isc->try_config.sd_format)
> + if (!isc->config.sd_format)
> return;
>
> fse.code = isc->try_config.sd_format->mbus_code;
> @@ -883,180 +888,141 @@ static void isc_try_fse(struct isc_device *isc,
> }
> }
>
> -static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
> - u32 *code)
> +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
> {
> - int i;
> - struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
> struct v4l2_pix_format *pixfmt = &f->fmt.pix;
> - struct v4l2_subdev_pad_config pad_cfg = {};
> - struct v4l2_subdev_state pad_state = {
> - .pads = &pad_cfg
> - };
> - struct v4l2_subdev_format format = {
> - .which = V4L2_SUBDEV_FORMAT_TRY,
> - };
> - u32 mbus_code;
> - int ret;
> - bool rlp_dma_direct_dump = false;
> + unsigned int i;
>
> if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> return -EINVAL;
>
> - /* Step 1: find a RAW format that is supported */
> - for (i = 0; i < isc->num_user_formats; i++) {
> - if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
> - sd_fmt = isc->user_formats[i];
> + isc->try_config.fourcc = isc->user_formats[0]->fourcc;
> +
> + /* find if the format requested is supported */
> + for (i = 0; i < isc->controller_formats_size; i++)
> + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
> + isc->try_config.fourcc = pixfmt->pixelformat;
> break;
> }
> - }
> - /* Step 2: We can continue with this RAW format, or we can look
> - * for better: maybe sensor supports directly what we need.
> - */
> - direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
> -
> - /* Step 3: We have both. We decide given the module parameter which
> - * one to use.
> - */
> - if (direct_fmt && sd_fmt && sensor_preferred)
> - sd_fmt = direct_fmt;
> -
> - /* Step 4: we do not have RAW but we have a direct format. Use it. */
> - if (direct_fmt && !sd_fmt)
> - sd_fmt = direct_fmt;
> -
> - /* Step 5: if we are using a direct format, we need to package
> - * everything as 8 bit data and just dump it
> - */
> - if (sd_fmt == direct_fmt)
> - rlp_dma_direct_dump = true;
> -
> - /* Step 6: We have no format. This can happen if the userspace
> - * requests some weird/invalid format.
> - * In this case, default to whatever we have
> - */
> - if (!sd_fmt && !direct_fmt) {
> - sd_fmt = isc->user_formats[isc->num_user_formats - 1];
> - v4l2_dbg(1, debug, &isc->v4l2_dev,
> - "Sensor not supporting %.4s, using %.4s\n",
> - (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
> - }
> -
> - if (!sd_fmt) {
> - ret = -EINVAL;
> - goto isc_try_fmt_err;
> - }
> -
> - /* Step 7: Print out what we decided for debugging */
> - v4l2_dbg(1, debug, &isc->v4l2_dev,
> - "Preferring to have sensor using format %.4s\n",
> - (char *)&sd_fmt->fourcc);
> -
> - /* Step 8: at this moment we decided which format the subdev will use */
> - isc->try_config.sd_format = sd_fmt;
> -
> - /* Limit to Atmel ISC hardware capabilities */
> - if (pixfmt->width > isc->max_width)
> - pixfmt->width = isc->max_width;
> - if (pixfmt->height > isc->max_height)
> - pixfmt->height = isc->max_height;
> -
> - /*
> - * The mbus format is the one the subdev outputs.
> - * The pixels will be transferred in this format Sensor -> ISC
> - */
> - mbus_code = sd_fmt->mbus_code;
> -
> - /*
> - * Validate formats. If the required format is not OK, default to raw.
> - */
> -
> - isc->try_config.fourcc = pixfmt->pixelformat;
> -
> - if (isc_try_validate_formats(isc)) {
> - pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
> - /* Re-try to validate the new format */
> - ret = isc_try_validate_formats(isc);
> - if (ret)
> - goto isc_try_fmt_err;
> - }
> -
> - ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
> - if (ret)
> - goto isc_try_fmt_err;
> -
> - ret = isc_try_configure_pipeline(isc);
> - if (ret)
> - goto isc_try_fmt_err;
>
> - /* Obtain frame sizes if possible to have crop requirements ready */
> - isc_try_fse(isc, &pad_state);
> -
> - v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
> - &pad_state, &format);
> - if (ret < 0)
> - goto isc_try_fmt_subdev_err;
> + /* If we did not find the requested format, we will fallback here */
> + pixfmt->pixelformat = isc->try_config.fourcc;
> + pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
> + pixfmt->field = V4L2_FIELD_NONE;
>
> - v4l2_fill_pix_format(pixfmt, &format.format);
> + isc_try_configure_rlp_dma(isc, false);
>
> /* Limit to Atmel ISC hardware capabilities */
> - if (pixfmt->width > isc->max_width)
> - pixfmt->width = isc->max_width;
> - if (pixfmt->height > isc->max_height)
> - pixfmt->height = isc->max_height;
> + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
> + &pixfmt->height, 16, isc->max_height, 0, 0);
>
> pixfmt->field = V4L2_FIELD_NONE;
> pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
> pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
> pixfmt->height;
>
> - if (code)
> - *code = mbus_code;
> + isc->try_fmt = *f;
>
> return 0;
> +}
>
> -isc_try_fmt_err:
> - v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
> -isc_try_fmt_subdev_err:
> - memset(&isc->try_config, 0, sizeof(isc->try_config));
> +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> +{
> + isc_try_fmt(isc, f);
>
> - return ret;
> + /* make the try configuration active */
> + isc->config = isc->try_config;
> + isc->fmt = isc->try_fmt;
> +
> + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
> + (char *)&f->fmt.pix.pixelformat,
> + f->fmt.pix.width, f->fmt.pix.height);
> +
> + return 0;
> }
>
> -static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> +static int isc_validate(struct isc_device *isc)
> {
> + int ret;
> + int i;
> + struct isc_format *sd_fmt = NULL;
> + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
> struct v4l2_subdev_format format = {
> .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> + .pad = isc->remote_pad,
> + };
> + struct v4l2_subdev_pad_config pad_cfg = {};
> + struct v4l2_subdev_state pad_state = {
> + .pads = &pad_cfg,
> };
> - u32 mbus_code = 0;
> - int ret;
>
> - ret = isc_try_fmt(isc, f, &mbus_code);
> + /* Get current format from subdev */
> + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
> + &format);

Ah! Haven't you just said we don't care anymore about the subdev
format ? :)

Kidding, I might got a bit lost in the logic, but if I look at your
above isc_try_validate_formats() it seems like an ideal candidate for
.link_validate() media_entity operation.

Just to make sure we're on the same page, here's how it should ideally
look like:
- set format does care about subdev format. It only checks that the
format required from the user is one of the ISC supported one. Ie.
no v4l2_subdev_call()

- as s_stream time your top driver calls media_pipeline_start()

- media_pipeline_start() walks all the entities in the pipeline and
validates the format of connected pads. To validate formats the
__media_pipeline_start() functions calls link_validate() on each
entity. You should in your driver set

static const struct media_entity_operations your_media_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};

if you want to use the default link validation procedure, or set the
callback to your custom validation function, which can behave more
or less like isc_try_validate_formats()

Does it match your understanding too ?

Thanks
j



> if (ret)
> return ret;
>
> - v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
> - set_fmt, NULL, &format);
> - if (ret < 0)
> - return ret;
> + /* Identify the subdev's format configuration */
> + for (i = 0; i < isc->num_user_formats; i++)
> + if (isc->user_formats[i]->mbus_code == format.format.code) {
> + sd_fmt = isc->user_formats[i];
> + break;
> + }
> +
> + /* Check if the format is not supported */
> + if (!sd_fmt) {
> + v4l2_err(&isc->v4l2_dev,
> + "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
> + format.format.code);
> + return -EPIPE;
> + }
> +
> + /* At this moment we know which format the subdev will use */
> + isc->try_config.sd_format = sd_fmt;
> +
> + /* If the sensor is not RAW, we can only do a direct dump */
> + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
> + isc_try_configure_rlp_dma(isc, true);
>
> /* Limit to Atmel ISC hardware capabilities */
> - if (f->fmt.pix.width > isc->max_width)
> - f->fmt.pix.width = isc->max_width;
> - if (f->fmt.pix.height > isc->max_height)
> - f->fmt.pix.height = isc->max_height;
> + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0,
> + &format.format.height, 16, isc->max_height, 0, 0);
>
> - isc->fmt = *f;
> + /* Check if the frame size is the same. Otherwise we may overflow */
> + if (pixfmt->height != format.format.height ||
> + pixfmt->width != format.format.width) {
> + v4l2_err(&isc->v4l2_dev,
> + "ISC not configured with the proper frame size: %dx%d\n",
> + format.format.width, format.format.height);
> + return -EPIPE;
> + }
>
> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> + "Identified subdev using format %.4s with %dx%d %d bpp\n",
> + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
> + isc->try_config.bpp);
> +
> + /* Reset and restart AWB if the subdevice changed the format */
> if (isc->try_config.sd_format && isc->config.sd_format &&
> isc->try_config.sd_format != isc->config.sd_format) {
> isc->ctrls.hist_stat = HIST_INIT;
> isc_reset_awb_ctrls(isc);
> isc_update_v4l2_ctrls(isc);
> }
> - /* make the try configuration active */
> +
> + /* Validate formats */
> + ret = isc_try_validate_formats(isc);
> + if (ret)
> + return ret;
> +
> + /* Obtain frame sizes if possible to have crop requirements ready */
> + isc_try_fse(isc, &pad_state);
> +
> + /* Configure ISC pipeline for the config */
> + ret = isc_try_configure_pipeline(isc);
> + if (ret)
> + return ret;
> +
> isc->config = isc->try_config;
>
> v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
> @@ -1064,6 +1030,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> return 0;
> }
>
> +static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
> +{
> + struct isc_device *isc = video_drvdata(file);
> + int ret;
> +
> + ret = isc_validate(isc);
> +
> + if (ret)
> + return ret;
> +
> + return vb2_ioctl_streamon(file, priv, bt);
> +}
> +
> static int isc_s_fmt_vid_cap(struct file *file, void *priv,
> struct v4l2_format *f)
> {
> @@ -1080,7 +1059,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
> {
> struct isc_device *isc = video_drvdata(file);
>
> - return isc_try_fmt(isc, f, NULL);
> + return isc_try_fmt(isc, f);
> }
>
> static int isc_enum_input(struct file *file, void *priv,
> @@ -1176,7 +1155,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
> .vidioc_dqbuf = vb2_ioctl_dqbuf,
> .vidioc_create_bufs = vb2_ioctl_create_bufs,
> .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> - .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamon = isc_streamon,
> .vidioc_streamoff = vb2_ioctl_streamoff,
>
> .vidioc_g_parm = isc_g_parm,
> @@ -1879,7 +1858,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
> };
> int ret;
>
> - ret = isc_try_fmt(isc, &f, NULL);
> + ret = isc_try_fmt(isc, &f);
> if (ret)
> return ret;
>
> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> index c2cb805faff3..7081698adddd 100644
> --- a/drivers/media/platform/atmel/atmel-isc.h
> +++ b/drivers/media/platform/atmel/atmel-isc.h
> @@ -297,6 +297,7 @@ struct isc_device {
> struct completion comp;
>
> struct v4l2_format fmt;
> + struct v4l2_format try_fmt;
> struct isc_format **user_formats;
> unsigned int num_user_formats;
>
> --
> 2.25.1
>

2022-01-12 09:52:24

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v3 07/23] media: atmel: atmel-isc-base: use streaming status when queueing buffers

On 11/01/2022 17:38, [email protected] wrote:
> On 1/11/22 5:53 PM, Hans Verkuil wrote:
>> On 13/12/2021 14:49, Eugen Hristev wrote:
>>> During experiments with libcamera, it looks like vb2_is_streaming returns
>>> true before our start streaming is called.
>>> Order of operations is streamon -> queue -> start_streaming
>>> ISC would have started the DMA immediately when a buffer is being added
>>> to the vbqueue if the queue is streaming.
>>> It is more safe to start the DMA after the start streaming of the driver is
>>> called.
>>> Thus, even if vb2queue is streaming, add the buffer to the dma queue of the
>>> driver instead of actually starting the DMA process, if the start streaming
>>> has not been called yet.
>>>
>>> Signed-off-by: Eugen Hristev <[email protected]>
>>> ---
>>> drivers/media/platform/atmel/atmel-isc-base.c | 17 ++++++++++-------
>>> 1 file changed, 10 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>>> index 26a6090f056c..e6c9071c04f0 100644
>>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>>> @@ -441,12 +441,14 @@ static void isc_buffer_queue(struct vb2_buffer *vb)
>>> unsigned long flags;
>>>
>>> spin_lock_irqsave(&isc->dma_queue_lock, flags);
>>> - if (!isc->cur_frm && list_empty(&isc->dma_queue) &&
>>> - vb2_is_streaming(vb->vb2_queue)) {
>>> +
>>> + if (!isc->cur_frm && list_empty(&isc->dma_queue) && !isc->stop) {
>>> isc->cur_frm = buf;
>>> isc_start_dma(isc);
>>> - } else
>>> + } else {
>>> list_add_tail(&buf->list, &isc->dma_queue);
>>> + }
>>> +
>>> spin_unlock_irqrestore(&isc->dma_queue_lock, flags);
>>> }
>>
>> Both the old and new code doesn't make a lot of sense.
>
> Hello Hans,
>
> I debated this a bit with Laurent when I initially wrote the patch.
>
> The problem with the current code, or the current way things are
> working, is the fact that vb2_is_streaming returns true from the moment
> of streamon.
> The DMA engine of the ISC is being started at the moment of
> start_streaming, and if buffers are being sent earlier/later, they are
> just added to the queue and DMA engine is restarted if it finished
> previously.
> However if frames are added beforehand, like, first streamon, then add

beforehand -> afterwards?

> some frames, and then later call the start_streaming, ISC will crash.
> (as I said, DMA engine is prepared at start_streaming).
> To avoid this, I changed the vb2_is_streaming calls to keeping an
> internal state of the streaming.
> Do you think I should try another different approach to solve this ?
> Perhaps do not prepare the ISC dma engine at start_streaming, but at
> another place ?
>
> Laurent do you remember the talk we had about this problem a while back ?
>
>>
>> buf_queue is only called by vb2 if start_streaming has already been called or is
>> about to be called.
>
> That's a huge difference for ISC driver as it is today. start_streaming
> being called afterwards, means the ISC DMA engine is not started (not
> started streaming), hence a crash.

The start_streaming op is called when streamon is called and if there are
sufficient buffers (>= q->min_buffers_needed). If there are insufficient
buffers at streamon time, then start_streaming is only called when the number
of queued buffers reaches the minimum.

vb2 has three different state checks that you can use:

vb2_is_busy() is true if buffers are allocated. Typically this means that you
can no longer do format changes or anything that might change the buffer
sizes or layout.

vb2_is_streaming() is true if streamon was called. If this is true, then vb2_is_busy()
is also true.

And finally vb2_start_streaming_called() is true if the start_streaming op
was called (i.e. DMA is in progress). If this is true, then vb2_is_streaming is
also true.

Basically vb2_is_streaming() reports the streaming state that userspace sees,
while vb2_start_streaming_called() reports the internal DMA streaming state.

The confusion is probably that you were using vb2_is_streaming instead of
vb2_start_streaming_called.

>
>>
>> Typically all that the buf_queue op does is to call list_add_tail(&buf->list, &isc->dma_queue);
>> inside the spinlock.
>>
>>>
>>> @@ -1014,7 +1016,7 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv,
>>> {
>>> struct isc_device *isc = video_drvdata(file);
>>>
>>> - if (vb2_is_streaming(&isc->vb2_vidq))
>>> + if (!isc->stop)
>>
>> This is weird as well. Normally this calls vb2_is_busy to check if the
>> queue is busy (that really means that buffers are already allocated, so
>> changing the format isn't allowed anymore).
>
> You think the query to the streaming status makes no sense here hence it
> should be removed completely ?

Right. And instead check vb2_is_busy().

I actually think that this is something that would fail with v4l2-compliance.

Do you still run v4l2-compliance?

> I can do that with a prequel patch if it's the case.

Probably wise.

Regards,

Hans

>
> Thanks for reviewing,
>
> Eugen
>
>>
>>> return -EBUSY;
>>>
>>> return isc_set_fmt(isc, f);
>>> @@ -1536,7 +1538,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>>>
>>> isc_update_awb_ctrls(isc);
>>>
>>> - if (vb2_is_streaming(&isc->vb2_vidq)) {
>>> + if (!isc->stop) {
>>
>> Ditto.
>>
>>> /*
>>> * If we are streaming, we can update profile to
>>> * have the new settings in place.
>>> @@ -1552,8 +1554,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>>> }
>>>
>>> /* if we have autowhitebalance on, start histogram procedure */
>>> - if (ctrls->awb == ISC_WB_AUTO &&
>>> - vb2_is_streaming(&isc->vb2_vidq) &&
>>> + if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
>>> ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
>>> isc_set_histogram(isc, true);
>>>
>>> @@ -1829,6 +1830,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>>> struct vb2_queue *q = &isc->vb2_vidq;
>>> int ret = 0;
>>>
>>> + isc->stop = true;
>>> +
>>
>> I'm really not sure that you need the stop bool at all.
>>
>>> INIT_WORK(&isc->awb_work, isc_awb_work);
>>>
>>> ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev);
>>
>> Regards,
>>
>> Hans
>>
>


2022-01-12 12:07:28

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH v3 00/23] media: atmel: atmel-isc: implement media controller

Hi Eugen,

On 13/12/2021 14:49, Eugen Hristev wrote:
> This series is the v3 of the series that attempts to support media controller
> in the atmel ISC and XISC drivers.
> This series also includes the csi2dc driver which was previously sent in a
> separate series:
> https://www.spinics.net/lists/linux-media/msg181042.html
> https://www.spinics.net/lists/linux-media/msg181044.html
> The driver now addresses comments received in latest v5 series from last year.
>
> The series includes some minor changes and fixes that improve the isc common
> code base, like removing the enum frameintervals VIDIOC, fixing bytesperline
> for planar formats, etc.
>
> Many thanks to folks from libcamera who helped a lot with understanding
> how a media controller driver should behave.
>
> Feedback is welcome !
>
> Changes in v3:
> - change in bindings, small fixes in csi2dc driver and conversion to mc
> for the isc-base.
> - removed some MAINTAINERS patches and used patterns in MAINTAINERS
>
> Changes in v2:
> - integrated many changes suggested by Jacopo in the review of the v1 series.
> - add a few new patches

Based on the current review status I think it would make sense to add
the following patches from this series to a PR:

1-6, 8-9, 11, 16, 18-22.

Are there any patches (esp. 11 and later) that require other patches
not in this list? In other words, does this make sense?

Regards,

Hans

>
>
> Eugen Hristev (23):
> MAINTAINERS: add microchip csi2dc
> dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc
> media: atmel: introduce microchip csi2dc driver
> media: atmel: atmel-isc: split the clock code into separate source
> file
> media: atmel: atmel-isc: replace video device name with module name
> media: atmel: atmel-sama7g5-isc: fix ispck leftover
> media: atmel: atmel-isc-base: use streaming status when queueing
> buffers
> media: atmel: atmel-isc-base: remove frameintervals VIDIOC
> media: atmel: atmel-isc-base: report frame sizes as full supported
> range
> media: atmel: atmel-isc-base: implement mbus_code support in enumfmt
> media: atmel: atmel-isc-base: fix bytesperline value for planar
> formats
> media: atmel: atmel-isc: implement media controller
> ARM: dts: at91: sama7g5: add nodes for video capture
> ARM: configs: at91: sama7: add xisc and csi2dc
> ARM: multi_v7_defconfig: add atmel video pipeline modules
> media: atmel: atmel-sama5d2-isc: fix wrong mask in YUYV format check
> media: atmel: atmel-isc-base: use mutex to lock awb workqueue from
> streaming
> media: atmel: atmel-isc-base: add wb debug messages
> media: atmel: atmel-isc-base: clamp wb gain coefficients
> media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo
> media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats
> media: atmel: atmel-isc: compact the controller formats list
> media: atmel: atmel-isc: change format propagation to subdev into only
> verification
>
> .../bindings/media/microchip,csi2dc.yaml | 197 +++++
> MAINTAINERS | 14 +-
> arch/arm/boot/dts/sama7g5.dtsi | 48 ++
> arch/arm/configs/multi_v7_defconfig | 3 +
> arch/arm/configs/sama7_defconfig | 2 +
> drivers/media/platform/Makefile | 1 +
> drivers/media/platform/atmel/Kconfig | 15 +
> drivers/media/platform/atmel/Makefile | 4 +-
> drivers/media/platform/atmel/atmel-isc-base.c | 790 ++++++-----------
> drivers/media/platform/atmel/atmel-isc-clk.c | 311 +++++++
> drivers/media/platform/atmel/atmel-isc-mc.c | 287 +++++++
> drivers/media/platform/atmel/atmel-isc.h | 49 ++
> .../media/platform/atmel/atmel-sama5d2-isc.c | 59 +-
> .../media/platform/atmel/atmel-sama7g5-isc.c | 72 +-
> .../media/platform/atmel/microchip-csi2dc.c | 806 ++++++++++++++++++
> 15 files changed, 2085 insertions(+), 573 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>


2022-01-12 12:47:01

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 00/23] media: atmel: atmel-isc: implement media controller

On 1/12/22 2:07 PM, Hans Verkuil wrote:
> Hi Eugen,
>
> On 13/12/2021 14:49, Eugen Hristev wrote:
>> This series is the v3 of the series that attempts to support media controller
>> in the atmel ISC and XISC drivers.
>> This series also includes the csi2dc driver which was previously sent in a
>> separate series:
>> https://www.spinics.net/lists/linux-media/msg181042.html
>> https://www.spinics.net/lists/linux-media/msg181044.html
>> The driver now addresses comments received in latest v5 series from last year.
>>
>> The series includes some minor changes and fixes that improve the isc common
>> code base, like removing the enum frameintervals VIDIOC, fixing bytesperline
>> for planar formats, etc.
>>
>> Many thanks to folks from libcamera who helped a lot with understanding
>> how a media controller driver should behave.
>>
>> Feedback is welcome !
>>
>> Changes in v3:
>> - change in bindings, small fixes in csi2dc driver and conversion to mc
>> for the isc-base.
>> - removed some MAINTAINERS patches and used patterns in MAINTAINERS
>>
>> Changes in v2:
>> - integrated many changes suggested by Jacopo in the review of the v1 series.
>> - add a few new patches
>
> Based on the current review status I think it would make sense to add
> the following patches from this series to a PR:
>
> 1-6, 8-9, 11, 16, 18-22.
>
> Are there any patches (esp. 11 and later) that require other patches
> not in this list? In other words, does this make sense?

Hello Hans,

Yes, this sounds good . I will rework the patches that are left next week.
Then I will send a v4 of this series with only the patches that are not
yet applied. And I will rebase it on top of the media tree.

Thanks for reviewing !

Eugen
>
> Regards,
>
> Hans
>
>>
>>
>> Eugen Hristev (23):
>> MAINTAINERS: add microchip csi2dc
>> dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc
>> media: atmel: introduce microchip csi2dc driver
>> media: atmel: atmel-isc: split the clock code into separate source
>> file
>> media: atmel: atmel-isc: replace video device name with module name
>> media: atmel: atmel-sama7g5-isc: fix ispck leftover
>> media: atmel: atmel-isc-base: use streaming status when queueing
>> buffers
>> media: atmel: atmel-isc-base: remove frameintervals VIDIOC
>> media: atmel: atmel-isc-base: report frame sizes as full supported
>> range
>> media: atmel: atmel-isc-base: implement mbus_code support in enumfmt
>> media: atmel: atmel-isc-base: fix bytesperline value for planar
>> formats
>> media: atmel: atmel-isc: implement media controller
>> ARM: dts: at91: sama7g5: add nodes for video capture
>> ARM: configs: at91: sama7: add xisc and csi2dc
>> ARM: multi_v7_defconfig: add atmel video pipeline modules
>> media: atmel: atmel-sama5d2-isc: fix wrong mask in YUYV format check
>> media: atmel: atmel-isc-base: use mutex to lock awb workqueue from
>> streaming
>> media: atmel: atmel-isc-base: add wb debug messages
>> media: atmel: atmel-isc-base: clamp wb gain coefficients
>> media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo
>> media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats
>> media: atmel: atmel-isc: compact the controller formats list
>> media: atmel: atmel-isc: change format propagation to subdev into only
>> verification
>>
>> .../bindings/media/microchip,csi2dc.yaml | 197 +++++
>> MAINTAINERS | 14 +-
>> arch/arm/boot/dts/sama7g5.dtsi | 48 ++
>> arch/arm/configs/multi_v7_defconfig | 3 +
>> arch/arm/configs/sama7_defconfig | 2 +
>> drivers/media/platform/Makefile | 1 +
>> drivers/media/platform/atmel/Kconfig | 15 +
>> drivers/media/platform/atmel/Makefile | 4 +-
>> drivers/media/platform/atmel/atmel-isc-base.c | 790 ++++++-----------
>> drivers/media/platform/atmel/atmel-isc-clk.c | 311 +++++++
>> drivers/media/platform/atmel/atmel-isc-mc.c | 287 +++++++
>> drivers/media/platform/atmel/atmel-isc.h | 49 ++
>> .../media/platform/atmel/atmel-sama5d2-isc.c | 59 +-
>> .../media/platform/atmel/atmel-sama7g5-isc.c | 72 +-
>> .../media/platform/atmel/microchip-csi2dc.c | 806 ++++++++++++++++++
>> 15 files changed, 2085 insertions(+), 573 deletions(-)
>> create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
>> create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
>> create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
>> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>>
>

2022-01-21 19:38:51

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 17/23] media: atmel: atmel-isc-base: use mutex to lock awb workqueue from streaming

On 1/12/22 10:58 AM, Jacopo Mondi wrote:
> Hi Eugen
>
> On Mon, Dec 13, 2021 at 03:49:34PM +0200, Eugen Hristev wrote:
>> The AWB workqueue runs in a kernel thread and needs to be synchronized
>> w.r.t. the streaming status.
>> It is possible that streaming is stopped while the AWB workq is running.
>> In this case it is likely that the check for isc->stop is done at one point
>> in time, but the AWB computations are done later, including a call to
>> isc_update_profile, which requires streaming to be started.
>> Thus , isc_update_profile will fail if during this operation sequence the
>> streaming was stopped.
>> To solve this issue, a mutex is added, that will serialize the awb work and
>> streaming stopping, with the mention that either streaming is stopped
>> completely including termination of the last frame is done, and after that
>> the AWB work can check stream status and stop; either first AWB work is
>> completed and after that the streaming can stop correctly.
>> The awb spin lock cannot be used since this spinlock is taken in the same
>> context and using it in the stop streaming will result in a recursion BUG.
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> drivers/media/platform/atmel/atmel-isc-base.c | 31 ++++++++++++++++---
>> drivers/media/platform/atmel/atmel-isc.h | 1 +
>> 2 files changed, 28 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>> index b0c3ed21f372..53cac1aac0fd 100644
>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>> @@ -401,6 +401,7 @@ static void isc_stop_streaming(struct vb2_queue *vq)
>> struct isc_buffer *buf;
>> int ret;
>>
>> + mutex_lock(&isc->awb_mutex);
>> v4l2_ctrl_activate(isc->do_wb_ctrl, false);
>>
>> isc->stop = true;
>> @@ -410,6 +411,8 @@ static void isc_stop_streaming(struct vb2_queue *vq)
>> v4l2_err(&isc->v4l2_dev,
>> "Timeout waiting for end of the capture\n");
>>
>> + mutex_unlock(&isc->awb_mutex);
>> +
>> /* Disable DMA interrupt */
>> regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE);
>>
>> @@ -1416,10 +1419,6 @@ static void isc_awb_work(struct work_struct *w)
>> u32 min, max;
>> int ret;
>>
>> - /* streaming is not active anymore */
>> - if (isc->stop)
>> - return;
>> -
>> if (ctrls->hist_stat != HIST_ENABLED)
>> return;
>>
>> @@ -1470,7 +1469,24 @@ static void isc_awb_work(struct work_struct *w)
>> }
>> regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his,
>> hist_id | baysel | ISC_HIS_CFG_RAR);
>
> isc_stop_streaming() calls runtime_put and here you access the hw.

Hi Jacopo,

That is correct. However the awb routine will call resume and get here
(before accessing the hardware) :

https://elixir.bootlin.com/linux/latest/source/drivers/media/platform/atmel/atmel-isc-base.c#L1722

So I think that we are good as we are now

>
> Feels like it's safer to hold the mutex for the whole duration of the
> AWB routine ?
>

I prefer to have the critical section as little as possible. As we are
only synchronizing the 'streaming status' , only this should be under
the lock.

If you have a different opinion, let me know.

Eugen

>> +
>> + /*
>> + * We have to make sure the streaming has not stopped meanwhile.
>> + * ISC requires a frame to clock the internal profile update.
>> + * To avoid issues, lock the sequence with a mutex
>> + */
>> + mutex_lock(&isc->awb_mutex);
>> +
>> + /* streaming is not active anymore */
>> + if (isc->stop) {
>> + mutex_unlock(&isc->awb_mutex);
>> + return;
>> + };
>> +
>> isc_update_profile(isc);
>> +
>> + mutex_unlock(&isc->awb_mutex);
>> +
>> /* if awb has been disabled, we don't need to start another histogram */
>> if (ctrls->awb)
>> regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ);
>> @@ -1549,6 +1565,8 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>>
>> isc_update_awb_ctrls(isc);
>>
>> + mutex_lock(&isc->awb_mutex);
>> +
>> if (!isc->stop) {
>> /*
>> * If we are streaming, we can update profile to
>> @@ -1563,6 +1581,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
>> */
>> v4l2_ctrl_activate(isc->do_wb_ctrl, false);
>> }
>> + mutex_unlock(&isc->awb_mutex);
>>
>> /* if we have autowhitebalance on, start histogram procedure */
>> if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
>> @@ -1754,6 +1773,7 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
>> {
>> struct isc_device *isc = container_of(notifier->v4l2_dev,
>> struct isc_device, v4l2_dev);
>> + mutex_destroy(&isc->awb_mutex);
>> cancel_work_sync(&isc->awb_work);
>> video_unregister_device(&isc->video_dev);
>> v4l2_ctrl_handler_free(&isc->ctrls.handler);
>> @@ -1866,6 +1886,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>> isc->current_subdev = container_of(notifier,
>> struct isc_subdev_entity, notifier);
>> mutex_init(&isc->lock);
>> + mutex_init(&isc->awb_mutex);
>> +
>> init_completion(&isc->comp);
>>
>> /* Initialize videobuf2 queue */
>> @@ -1941,6 +1963,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
>> video_unregister_device(vdev);
>>
>> isc_async_complete_err:
>> + mutex_destroy(&isc->awb_mutex);
>> mutex_destroy(&isc->lock);
>> return ret;
>> }
>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>> index 0b6370d7775f..c2cb805faff3 100644
>> --- a/drivers/media/platform/atmel/atmel-isc.h
>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>> @@ -307,6 +307,7 @@ struct isc_device {
>> struct work_struct awb_work;
>>
>> struct mutex lock; /* serialize access to file operations */
>> + struct mutex awb_mutex; /* serialize access to streaming status from awb work queue */
>> spinlock_t awb_lock; /* serialize access to DMA buffers from awb work queue */
>>
>> struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM];
>> --
>> 2.25.1
>>

2022-01-21 21:11:50

by jacopo mondi

[permalink] [raw]
Subject: Re: [PATCH v3 23/23] media: atmel: atmel-isc: change format propagation to subdev into only verification

Hi Eugen

On Thu, Jan 20, 2022 at 08:43:33AM +0000, [email protected] wrote:
> On 1/12/22 11:21 AM, Jacopo Mondi wrote:
> > Hi Eugen
>
>
> Hi Jacopo,
>
> >
> > On Mon, Dec 13, 2021 at 03:49:40PM +0200, Eugen Hristev wrote:
> >> As a top MC video driver, the atmel-isc should not propagate the format to the
> >> subdevice.
> >> It should rather check at streamon() time if the subdev is properly configured
> >> with a compatible format.
> >> Removed the whole format finding logic, and reworked the format verification
> >> at streamon time, such that the ISC will return an error if the subdevice
> >> is not properly configured.
> >> With this being done, the module parameter 'sensor_prefered' makes no sense
> >> anymore. The ISC should not decide which format the sensor is using. The
> >> ISC should only cope with the situation and inform userspace if the streaming
> >> is possible in the current configuration.
> >
> > Sounds great!
> >
> >>
> >> Signed-off-by: Eugen Hristev <[email protected]>
> >> ---
> >> Changes in v3:
> >> - clamp to maximum resolution once the frame size from the subdev is found
> >>
> >> drivers/media/platform/atmel/atmel-isc-base.c | 271 ++++++++----------
> >> drivers/media/platform/atmel/atmel-isc.h | 1 +
> >> 2 files changed, 126 insertions(+), 146 deletions(-)
> >>
> >> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> >> index 31c8e3029eee..00c8c9588a78 100644
> >> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> >> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> >> @@ -36,11 +36,6 @@ static unsigned int debug;
> >> module_param(debug, int, 0644);
> >> MODULE_PARM_DESC(debug, "debug level (0-2)");
> >>
> >> -static unsigned int sensor_preferred = 1;
> >> -module_param(sensor_preferred, uint, 0644);
> >> -MODULE_PARM_DESC(sensor_preferred,
> >> - "Sensor is preferred to output the specified format (1-on 0-off), default 1");
> >> -
> >> #define ISC_IS_FORMAT_RAW(mbus_code) \
> >> (((mbus_code) & 0xf000) == 0x3000)
> >>
> >> @@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
> >> * convert it to any of the formats that we usually can with a
> >> * RAW sensor. Thus, do not advertise them.
> >> */
> >> - if (!isc->config.sd_format ||
> >> + if (isc->config.sd_format &&
> >
> > Is this change intentional ?
> >
> >> !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
> >> return -EINVAL;
> >>
> >> @@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
> >> break;
> >> default:
> >> /* any other different formats are not supported */
> >> + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
> >> ret = -EINVAL;
> >> }
> >> v4l2_dbg(1, debug, &isc->v4l2_dev,
> >> "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
> >> rgb, yuv, grey, bayer);
> >
> > Would it make sense to move this before the switch so that the
> > error messages, if any, appear later ?
>
> Actually, no, because the variables rgb, yuv, grey, bayer, are set
> according to what happens in the switch
> >
> >>
> >> - /* we cannot output RAW if we do not receive RAW */
> >> - if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
> >> + if ((bayer) &&
> >> + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
> >> + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
> >> return -EINVAL;
> >> + }
> >>
> >> - /* we cannot output GREY if we do not receive RAW/GREY */
> >> if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
> >> - !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
> >> + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
> >> + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
> >> return -EINVAL;
> >> + }
> >> +
> >> + if ((rgb || bayer || yuv) &&
> >> + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
> >> + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
> >> + return -EINVAL;
> >> + }
> >>
> >> return ret;
> >> }
> >> @@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
> >> * If we do not know yet which format the subdev is using, we cannot
> >> * do anything.
> >> */
> >> - if (!isc->try_config.sd_format)
> >> + if (!isc->config.sd_format)
> >> return;
> >>
> >> fse.code = isc->try_config.sd_format->mbus_code;
> >> @@ -883,180 +888,141 @@ static void isc_try_fse(struct isc_device *isc,
> >> }
> >> }
> >>
> >> -static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
> >> - u32 *code)
> >> +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
> >> {
> >> - int i;
> >> - struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
> >> struct v4l2_pix_format *pixfmt = &f->fmt.pix;
> >> - struct v4l2_subdev_pad_config pad_cfg = {};
> >> - struct v4l2_subdev_state pad_state = {
> >> - .pads = &pad_cfg
> >> - };
> >> - struct v4l2_subdev_format format = {
> >> - .which = V4L2_SUBDEV_FORMAT_TRY,
> >> - };
> >> - u32 mbus_code;
> >> - int ret;
> >> - bool rlp_dma_direct_dump = false;
> >> + unsigned int i;
> >>
> >> if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> >> return -EINVAL;
> >>
> >> - /* Step 1: find a RAW format that is supported */
> >> - for (i = 0; i < isc->num_user_formats; i++) {
> >> - if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
> >> - sd_fmt = isc->user_formats[i];
> >> + isc->try_config.fourcc = isc->user_formats[0]->fourcc;
> >> +
> >> + /* find if the format requested is supported */
> >> + for (i = 0; i < isc->controller_formats_size; i++)
> >> + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
> >> + isc->try_config.fourcc = pixfmt->pixelformat;
> >> break;
> >> }
> >> - }
> >> - /* Step 2: We can continue with this RAW format, or we can look
> >> - * for better: maybe sensor supports directly what we need.
> >> - */
> >> - direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
> >> -
> >> - /* Step 3: We have both. We decide given the module parameter which
> >> - * one to use.
> >> - */
> >> - if (direct_fmt && sd_fmt && sensor_preferred)
> >> - sd_fmt = direct_fmt;
> >> -
> >> - /* Step 4: we do not have RAW but we have a direct format. Use it. */
> >> - if (direct_fmt && !sd_fmt)
> >> - sd_fmt = direct_fmt;
> >> -
> >> - /* Step 5: if we are using a direct format, we need to package
> >> - * everything as 8 bit data and just dump it
> >> - */
> >> - if (sd_fmt == direct_fmt)
> >> - rlp_dma_direct_dump = true;
> >> -
> >> - /* Step 6: We have no format. This can happen if the userspace
> >> - * requests some weird/invalid format.
> >> - * In this case, default to whatever we have
> >> - */
> >> - if (!sd_fmt && !direct_fmt) {
> >> - sd_fmt = isc->user_formats[isc->num_user_formats - 1];
> >> - v4l2_dbg(1, debug, &isc->v4l2_dev,
> >> - "Sensor not supporting %.4s, using %.4s\n",
> >> - (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
> >> - }
> >> -
> >> - if (!sd_fmt) {
> >> - ret = -EINVAL;
> >> - goto isc_try_fmt_err;
> >> - }
> >> -
> >> - /* Step 7: Print out what we decided for debugging */
> >> - v4l2_dbg(1, debug, &isc->v4l2_dev,
> >> - "Preferring to have sensor using format %.4s\n",
> >> - (char *)&sd_fmt->fourcc);
> >> -
> >> - /* Step 8: at this moment we decided which format the subdev will use */
> >> - isc->try_config.sd_format = sd_fmt;
> >> -
> >> - /* Limit to Atmel ISC hardware capabilities */
> >> - if (pixfmt->width > isc->max_width)
> >> - pixfmt->width = isc->max_width;
> >> - if (pixfmt->height > isc->max_height)
> >> - pixfmt->height = isc->max_height;
> >> -
> >> - /*
> >> - * The mbus format is the one the subdev outputs.
> >> - * The pixels will be transferred in this format Sensor -> ISC
> >> - */
> >> - mbus_code = sd_fmt->mbus_code;
> >> -
> >> - /*
> >> - * Validate formats. If the required format is not OK, default to raw.
> >> - */
> >> -
> >> - isc->try_config.fourcc = pixfmt->pixelformat;
> >> -
> >> - if (isc_try_validate_formats(isc)) {
> >> - pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
> >> - /* Re-try to validate the new format */
> >> - ret = isc_try_validate_formats(isc);
> >> - if (ret)
> >> - goto isc_try_fmt_err;
> >> - }
> >> -
> >> - ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
> >> - if (ret)
> >> - goto isc_try_fmt_err;
> >> -
> >> - ret = isc_try_configure_pipeline(isc);
> >> - if (ret)
> >> - goto isc_try_fmt_err;
> >>
> >> - /* Obtain frame sizes if possible to have crop requirements ready */
> >> - isc_try_fse(isc, &pad_state);
> >> -
> >> - v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
> >> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
> >> - &pad_state, &format);
> >> - if (ret < 0)
> >> - goto isc_try_fmt_subdev_err;
> >> + /* If we did not find the requested format, we will fallback here */
> >> + pixfmt->pixelformat = isc->try_config.fourcc;
> >> + pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
> >> + pixfmt->field = V4L2_FIELD_NONE;
> >>
> >> - v4l2_fill_pix_format(pixfmt, &format.format);
> >> + isc_try_configure_rlp_dma(isc, false);
> >>
> >> /* Limit to Atmel ISC hardware capabilities */
> >> - if (pixfmt->width > isc->max_width)
> >> - pixfmt->width = isc->max_width;
> >> - if (pixfmt->height > isc->max_height)
> >> - pixfmt->height = isc->max_height;
> >> + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
> >> + &pixfmt->height, 16, isc->max_height, 0, 0);
> >>
> >> pixfmt->field = V4L2_FIELD_NONE;
> >> pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
> >> pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
> >> pixfmt->height;
> >>
> >> - if (code)
> >> - *code = mbus_code;
> >> + isc->try_fmt = *f;
> >>
> >> return 0;
> >> +}
> >>
> >> -isc_try_fmt_err:
> >> - v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
> >> -isc_try_fmt_subdev_err:
> >> - memset(&isc->try_config, 0, sizeof(isc->try_config));
> >> +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> >> +{
> >> + isc_try_fmt(isc, f);
> >>
> >> - return ret;
> >> + /* make the try configuration active */
> >> + isc->config = isc->try_config;
> >> + isc->fmt = isc->try_fmt;
> >> +
> >> + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
> >> + (char *)&f->fmt.pix.pixelformat,
> >> + f->fmt.pix.width, f->fmt.pix.height);
> >> +
> >> + return 0;
> >> }
> >>
> >> -static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> >> +static int isc_validate(struct isc_device *isc)
> >> {
> >> + int ret;
> >> + int i;
> >> + struct isc_format *sd_fmt = NULL;
> >> + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
> >> struct v4l2_subdev_format format = {
> >> .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> >> + .pad = isc->remote_pad,
> >> + };
> >> + struct v4l2_subdev_pad_config pad_cfg = {};
> >> + struct v4l2_subdev_state pad_state = {
> >> + .pads = &pad_cfg,
> >> };
> >> - u32 mbus_code = 0;
> >> - int ret;
> >>
> >> - ret = isc_try_fmt(isc, f, &mbus_code);
> >> + /* Get current format from subdev */
> >> + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
> >> + &format);
> >
> > Ah! Haven't you just said we don't care anymore about the subdev
> > format ? :)
>
> We don't ! but we can't stream anything if the sensor streams a format
> that we don't understand do we ?
>
> >
> > Kidding, I might got a bit lost in the logic, but if I look at your
> > above isc_try_validate_formats() it seems like an ideal candidate for
> > .link_validate() media_entity operation.
> >
> > Just to make sure we're on the same page, here's how it should ideally
> > look like:
> > - set format does care about subdev format. It only checks that the
> > format required from the user is one of the ISC supported one. Ie.
> > no v4l2_subdev_call()
> >
> > - as s_stream time your top driver calls media_pipeline_start()
> >
> > - media_pipeline_start() walks all the entities in the pipeline and
> > validates the format of connected pads. To validate formats the
> > __media_pipeline_start() functions calls link_validate() on each
> > entity. You should in your driver set
>
> Is this done automatically ? if this would be the case, where all links

Have a look at the media_pipeline_start() function in
drivers/media/mc/mc-entity.c

It walks the entities linked in the graph and calls .link_validate()
on each link it finds

> are correctly set, then, in theory, it would not be possible that some
> entities have a format set that is not supported by the other side
>

Not if validation is done correctly ;)

> >
> > static const struct media_entity_operations your_media_entity_ops = {
> > .link_validate = v4l2_subdev_link_validate,
> > };
> >
> > if you want to use the default link validation procedure, or set the
> > callback to your custom validation function, which can behave more
> > or less like isc_try_validate_formats()
>
> Does this mean that I can ask the subdev for the format ?
> Or how can I obtain the format on the other side of the link ?
> link validate can do this for me ?

If you implement your own validation function then yes.
Have a look at v4l2_subdev_link_validate() in v4l2-subdev.c

It fetches formats from each side of the link and matches them.

As you have some more complex validations, you can define your own
validation function and compare the formats on each side of the link
with the same logic as implemented in isc_try_validate_formats() (if I
remember the function name right)

Thanks
j

>
>
> Thanks for reviewing,
> Eugen
> >
> > Does it match your understanding too ?
> >
> > Thanks
> > j
> >
> >
> >
> >> if (ret)
> >> return ret;
> >>
> >> - v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
> >> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
> >> - set_fmt, NULL, &format);
> >> - if (ret < 0)
> >> - return ret;
> >> + /* Identify the subdev's format configuration */
> >> + for (i = 0; i < isc->num_user_formats; i++)
> >> + if (isc->user_formats[i]->mbus_code == format.format.code) {
> >> + sd_fmt = isc->user_formats[i];
> >> + break;
> >> + }
> >> +
> >> + /* Check if the format is not supported */
> >> + if (!sd_fmt) {
> >> + v4l2_err(&isc->v4l2_dev,
> >> + "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
> >> + format.format.code);
> >> + return -EPIPE;
> >> + }
> >> +
> >> + /* At this moment we know which format the subdev will use */
> >> + isc->try_config.sd_format = sd_fmt;
> >> +
> >> + /* If the sensor is not RAW, we can only do a direct dump */
> >> + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
> >> + isc_try_configure_rlp_dma(isc, true);
> >>
> >> /* Limit to Atmel ISC hardware capabilities */
> >> - if (f->fmt.pix.width > isc->max_width)
> >> - f->fmt.pix.width = isc->max_width;
> >> - if (f->fmt.pix.height > isc->max_height)
> >> - f->fmt.pix.height = isc->max_height;
> >> + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0,
> >> + &format.format.height, 16, isc->max_height, 0, 0);
> >>
> >> - isc->fmt = *f;
> >> + /* Check if the frame size is the same. Otherwise we may overflow */
> >> + if (pixfmt->height != format.format.height ||
> >> + pixfmt->width != format.format.width) {
> >> + v4l2_err(&isc->v4l2_dev,
> >> + "ISC not configured with the proper frame size: %dx%d\n",
> >> + format.format.width, format.format.height);
> >> + return -EPIPE;
> >> + }
> >>
> >> + v4l2_dbg(1, debug, &isc->v4l2_dev,
> >> + "Identified subdev using format %.4s with %dx%d %d bpp\n",
> >> + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
> >> + isc->try_config.bpp);
> >> +
> >> + /* Reset and restart AWB if the subdevice changed the format */
> >> if (isc->try_config.sd_format && isc->config.sd_format &&
> >> isc->try_config.sd_format != isc->config.sd_format) {
> >> isc->ctrls.hist_stat = HIST_INIT;
> >> isc_reset_awb_ctrls(isc);
> >> isc_update_v4l2_ctrls(isc);
> >> }
> >> - /* make the try configuration active */
> >> +
> >> + /* Validate formats */
> >> + ret = isc_try_validate_formats(isc);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + /* Obtain frame sizes if possible to have crop requirements ready */
> >> + isc_try_fse(isc, &pad_state);
> >> +
> >> + /* Configure ISC pipeline for the config */
> >> + ret = isc_try_configure_pipeline(isc);
> >> + if (ret)
> >> + return ret;
> >> +
> >> isc->config = isc->try_config;
> >>
> >> v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
> >> @@ -1064,6 +1030,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
> >> return 0;
> >> }
> >>
> >> +static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
> >> +{
> >> + struct isc_device *isc = video_drvdata(file);
> >> + int ret;
> >> +
> >> + ret = isc_validate(isc);
> >> +
> >> + if (ret)
> >> + return ret;
> >> +
> >> + return vb2_ioctl_streamon(file, priv, bt);
> >> +}
> >> +
> >> static int isc_s_fmt_vid_cap(struct file *file, void *priv,
> >> struct v4l2_format *f)
> >> {
> >> @@ -1080,7 +1059,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
> >> {
> >> struct isc_device *isc = video_drvdata(file);
> >>
> >> - return isc_try_fmt(isc, f, NULL);
> >> + return isc_try_fmt(isc, f);
> >> }
> >>
> >> static int isc_enum_input(struct file *file, void *priv,
> >> @@ -1176,7 +1155,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
> >> .vidioc_dqbuf = vb2_ioctl_dqbuf,
> >> .vidioc_create_bufs = vb2_ioctl_create_bufs,
> >> .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> >> - .vidioc_streamon = vb2_ioctl_streamon,
> >> + .vidioc_streamon = isc_streamon,
> >> .vidioc_streamoff = vb2_ioctl_streamoff,
> >>
> >> .vidioc_g_parm = isc_g_parm,
> >> @@ -1879,7 +1858,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
> >> };
> >> int ret;
> >>
> >> - ret = isc_try_fmt(isc, &f, NULL);
> >> + ret = isc_try_fmt(isc, &f);
> >> if (ret)
> >> return ret;
> >>
> >> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> >> index c2cb805faff3..7081698adddd 100644
> >> --- a/drivers/media/platform/atmel/atmel-isc.h
> >> +++ b/drivers/media/platform/atmel/atmel-isc.h
> >> @@ -297,6 +297,7 @@ struct isc_device {
> >> struct completion comp;
> >>
> >> struct v4l2_format fmt;
> >> + struct v4l2_format try_fmt;
> >> struct isc_format **user_formats;
> >> unsigned int num_user_formats;
> >>
> >> --
> >> 2.25.1
> >>
>

2022-01-21 21:12:01

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 23/23] media: atmel: atmel-isc: change format propagation to subdev into only verification

On 1/12/22 11:21 AM, Jacopo Mondi wrote:
> Hi Eugen


Hi Jacopo,

>
> On Mon, Dec 13, 2021 at 03:49:40PM +0200, Eugen Hristev wrote:
>> As a top MC video driver, the atmel-isc should not propagate the format to the
>> subdevice.
>> It should rather check at streamon() time if the subdev is properly configured
>> with a compatible format.
>> Removed the whole format finding logic, and reworked the format verification
>> at streamon time, such that the ISC will return an error if the subdevice
>> is not properly configured.
>> With this being done, the module parameter 'sensor_prefered' makes no sense
>> anymore. The ISC should not decide which format the sensor is using. The
>> ISC should only cope with the situation and inform userspace if the streaming
>> is possible in the current configuration.
>
> Sounds great!
>
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> Changes in v3:
>> - clamp to maximum resolution once the frame size from the subdev is found
>>
>> drivers/media/platform/atmel/atmel-isc-base.c | 271 ++++++++----------
>> drivers/media/platform/atmel/atmel-isc.h | 1 +
>> 2 files changed, 126 insertions(+), 146 deletions(-)
>>
>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>> index 31c8e3029eee..00c8c9588a78 100644
>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>> @@ -36,11 +36,6 @@ static unsigned int debug;
>> module_param(debug, int, 0644);
>> MODULE_PARM_DESC(debug, "debug level (0-2)");
>>
>> -static unsigned int sensor_preferred = 1;
>> -module_param(sensor_preferred, uint, 0644);
>> -MODULE_PARM_DESC(sensor_preferred,
>> - "Sensor is preferred to output the specified format (1-on 0-off), default 1");
>> -
>> #define ISC_IS_FORMAT_RAW(mbus_code) \
>> (((mbus_code) & 0xf000) == 0x3000)
>>
>> @@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
>> * convert it to any of the formats that we usually can with a
>> * RAW sensor. Thus, do not advertise them.
>> */
>> - if (!isc->config.sd_format ||
>> + if (isc->config.sd_format &&
>
> Is this change intentional ?
>
>> !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
>> return -EINVAL;
>>
>> @@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
>> break;
>> default:
>> /* any other different formats are not supported */
>> + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
>> ret = -EINVAL;
>> }
>> v4l2_dbg(1, debug, &isc->v4l2_dev,
>> "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
>> rgb, yuv, grey, bayer);
>
> Would it make sense to move this before the switch so that the
> error messages, if any, appear later ?

Actually, no, because the variables rgb, yuv, grey, bayer, are set
according to what happens in the switch
>
>>
>> - /* we cannot output RAW if we do not receive RAW */
>> - if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
>> + if ((bayer) &&
>> + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
>> + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
>> return -EINVAL;
>> + }
>>
>> - /* we cannot output GREY if we do not receive RAW/GREY */
>> if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
>> - !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
>> + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
>> + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
>> return -EINVAL;
>> + }
>> +
>> + if ((rgb || bayer || yuv) &&
>> + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
>> + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
>> + return -EINVAL;
>> + }
>>
>> return ret;
>> }
>> @@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
>> * If we do not know yet which format the subdev is using, we cannot
>> * do anything.
>> */
>> - if (!isc->try_config.sd_format)
>> + if (!isc->config.sd_format)
>> return;
>>
>> fse.code = isc->try_config.sd_format->mbus_code;
>> @@ -883,180 +888,141 @@ static void isc_try_fse(struct isc_device *isc,
>> }
>> }
>>
>> -static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
>> - u32 *code)
>> +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
>> {
>> - int i;
>> - struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
>> struct v4l2_pix_format *pixfmt = &f->fmt.pix;
>> - struct v4l2_subdev_pad_config pad_cfg = {};
>> - struct v4l2_subdev_state pad_state = {
>> - .pads = &pad_cfg
>> - };
>> - struct v4l2_subdev_format format = {
>> - .which = V4L2_SUBDEV_FORMAT_TRY,
>> - };
>> - u32 mbus_code;
>> - int ret;
>> - bool rlp_dma_direct_dump = false;
>> + unsigned int i;
>>
>> if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>> return -EINVAL;
>>
>> - /* Step 1: find a RAW format that is supported */
>> - for (i = 0; i < isc->num_user_formats; i++) {
>> - if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
>> - sd_fmt = isc->user_formats[i];
>> + isc->try_config.fourcc = isc->user_formats[0]->fourcc;
>> +
>> + /* find if the format requested is supported */
>> + for (i = 0; i < isc->controller_formats_size; i++)
>> + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
>> + isc->try_config.fourcc = pixfmt->pixelformat;
>> break;
>> }
>> - }
>> - /* Step 2: We can continue with this RAW format, or we can look
>> - * for better: maybe sensor supports directly what we need.
>> - */
>> - direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
>> -
>> - /* Step 3: We have both. We decide given the module parameter which
>> - * one to use.
>> - */
>> - if (direct_fmt && sd_fmt && sensor_preferred)
>> - sd_fmt = direct_fmt;
>> -
>> - /* Step 4: we do not have RAW but we have a direct format. Use it. */
>> - if (direct_fmt && !sd_fmt)
>> - sd_fmt = direct_fmt;
>> -
>> - /* Step 5: if we are using a direct format, we need to package
>> - * everything as 8 bit data and just dump it
>> - */
>> - if (sd_fmt == direct_fmt)
>> - rlp_dma_direct_dump = true;
>> -
>> - /* Step 6: We have no format. This can happen if the userspace
>> - * requests some weird/invalid format.
>> - * In this case, default to whatever we have
>> - */
>> - if (!sd_fmt && !direct_fmt) {
>> - sd_fmt = isc->user_formats[isc->num_user_formats - 1];
>> - v4l2_dbg(1, debug, &isc->v4l2_dev,
>> - "Sensor not supporting %.4s, using %.4s\n",
>> - (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
>> - }
>> -
>> - if (!sd_fmt) {
>> - ret = -EINVAL;
>> - goto isc_try_fmt_err;
>> - }
>> -
>> - /* Step 7: Print out what we decided for debugging */
>> - v4l2_dbg(1, debug, &isc->v4l2_dev,
>> - "Preferring to have sensor using format %.4s\n",
>> - (char *)&sd_fmt->fourcc);
>> -
>> - /* Step 8: at this moment we decided which format the subdev will use */
>> - isc->try_config.sd_format = sd_fmt;
>> -
>> - /* Limit to Atmel ISC hardware capabilities */
>> - if (pixfmt->width > isc->max_width)
>> - pixfmt->width = isc->max_width;
>> - if (pixfmt->height > isc->max_height)
>> - pixfmt->height = isc->max_height;
>> -
>> - /*
>> - * The mbus format is the one the subdev outputs.
>> - * The pixels will be transferred in this format Sensor -> ISC
>> - */
>> - mbus_code = sd_fmt->mbus_code;
>> -
>> - /*
>> - * Validate formats. If the required format is not OK, default to raw.
>> - */
>> -
>> - isc->try_config.fourcc = pixfmt->pixelformat;
>> -
>> - if (isc_try_validate_formats(isc)) {
>> - pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
>> - /* Re-try to validate the new format */
>> - ret = isc_try_validate_formats(isc);
>> - if (ret)
>> - goto isc_try_fmt_err;
>> - }
>> -
>> - ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
>> - if (ret)
>> - goto isc_try_fmt_err;
>> -
>> - ret = isc_try_configure_pipeline(isc);
>> - if (ret)
>> - goto isc_try_fmt_err;
>>
>> - /* Obtain frame sizes if possible to have crop requirements ready */
>> - isc_try_fse(isc, &pad_state);
>> -
>> - v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
>> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
>> - &pad_state, &format);
>> - if (ret < 0)
>> - goto isc_try_fmt_subdev_err;
>> + /* If we did not find the requested format, we will fallback here */
>> + pixfmt->pixelformat = isc->try_config.fourcc;
>> + pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
>> + pixfmt->field = V4L2_FIELD_NONE;
>>
>> - v4l2_fill_pix_format(pixfmt, &format.format);
>> + isc_try_configure_rlp_dma(isc, false);
>>
>> /* Limit to Atmel ISC hardware capabilities */
>> - if (pixfmt->width > isc->max_width)
>> - pixfmt->width = isc->max_width;
>> - if (pixfmt->height > isc->max_height)
>> - pixfmt->height = isc->max_height;
>> + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
>> + &pixfmt->height, 16, isc->max_height, 0, 0);
>>
>> pixfmt->field = V4L2_FIELD_NONE;
>> pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
>> pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
>> pixfmt->height;
>>
>> - if (code)
>> - *code = mbus_code;
>> + isc->try_fmt = *f;
>>
>> return 0;
>> +}
>>
>> -isc_try_fmt_err:
>> - v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
>> -isc_try_fmt_subdev_err:
>> - memset(&isc->try_config, 0, sizeof(isc->try_config));
>> +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>> +{
>> + isc_try_fmt(isc, f);
>>
>> - return ret;
>> + /* make the try configuration active */
>> + isc->config = isc->try_config;
>> + isc->fmt = isc->try_fmt;
>> +
>> + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
>> + (char *)&f->fmt.pix.pixelformat,
>> + f->fmt.pix.width, f->fmt.pix.height);
>> +
>> + return 0;
>> }
>>
>> -static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>> +static int isc_validate(struct isc_device *isc)
>> {
>> + int ret;
>> + int i;
>> + struct isc_format *sd_fmt = NULL;
>> + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
>> struct v4l2_subdev_format format = {
>> .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>> + .pad = isc->remote_pad,
>> + };
>> + struct v4l2_subdev_pad_config pad_cfg = {};
>> + struct v4l2_subdev_state pad_state = {
>> + .pads = &pad_cfg,
>> };
>> - u32 mbus_code = 0;
>> - int ret;
>>
>> - ret = isc_try_fmt(isc, f, &mbus_code);
>> + /* Get current format from subdev */
>> + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
>> + &format);
>
> Ah! Haven't you just said we don't care anymore about the subdev
> format ? :)

We don't ! but we can't stream anything if the sensor streams a format
that we don't understand do we ?

>
> Kidding, I might got a bit lost in the logic, but if I look at your
> above isc_try_validate_formats() it seems like an ideal candidate for
> .link_validate() media_entity operation.
>
> Just to make sure we're on the same page, here's how it should ideally
> look like:
> - set format does care about subdev format. It only checks that the
> format required from the user is one of the ISC supported one. Ie.
> no v4l2_subdev_call()
>
> - as s_stream time your top driver calls media_pipeline_start()
>
> - media_pipeline_start() walks all the entities in the pipeline and
> validates the format of connected pads. To validate formats the
> __media_pipeline_start() functions calls link_validate() on each
> entity. You should in your driver set

Is this done automatically ? if this would be the case, where all links
are correctly set, then, in theory, it would not be possible that some
entities have a format set that is not supported by the other side

>
> static const struct media_entity_operations your_media_entity_ops = {
> .link_validate = v4l2_subdev_link_validate,
> };
>
> if you want to use the default link validation procedure, or set the
> callback to your custom validation function, which can behave more
> or less like isc_try_validate_formats()

Does this mean that I can ask the subdev for the format ?
Or how can I obtain the format on the other side of the link ?
link validate can do this for me ?


Thanks for reviewing,
Eugen
>
> Does it match your understanding too ?
>
> Thanks
> j
>
>
>
>> if (ret)
>> return ret;
>>
>> - v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
>> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
>> - set_fmt, NULL, &format);
>> - if (ret < 0)
>> - return ret;
>> + /* Identify the subdev's format configuration */
>> + for (i = 0; i < isc->num_user_formats; i++)
>> + if (isc->user_formats[i]->mbus_code == format.format.code) {
>> + sd_fmt = isc->user_formats[i];
>> + break;
>> + }
>> +
>> + /* Check if the format is not supported */
>> + if (!sd_fmt) {
>> + v4l2_err(&isc->v4l2_dev,
>> + "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
>> + format.format.code);
>> + return -EPIPE;
>> + }
>> +
>> + /* At this moment we know which format the subdev will use */
>> + isc->try_config.sd_format = sd_fmt;
>> +
>> + /* If the sensor is not RAW, we can only do a direct dump */
>> + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
>> + isc_try_configure_rlp_dma(isc, true);
>>
>> /* Limit to Atmel ISC hardware capabilities */
>> - if (f->fmt.pix.width > isc->max_width)
>> - f->fmt.pix.width = isc->max_width;
>> - if (f->fmt.pix.height > isc->max_height)
>> - f->fmt.pix.height = isc->max_height;
>> + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0,
>> + &format.format.height, 16, isc->max_height, 0, 0);
>>
>> - isc->fmt = *f;
>> + /* Check if the frame size is the same. Otherwise we may overflow */
>> + if (pixfmt->height != format.format.height ||
>> + pixfmt->width != format.format.width) {
>> + v4l2_err(&isc->v4l2_dev,
>> + "ISC not configured with the proper frame size: %dx%d\n",
>> + format.format.width, format.format.height);
>> + return -EPIPE;
>> + }
>>
>> + v4l2_dbg(1, debug, &isc->v4l2_dev,
>> + "Identified subdev using format %.4s with %dx%d %d bpp\n",
>> + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
>> + isc->try_config.bpp);
>> +
>> + /* Reset and restart AWB if the subdevice changed the format */
>> if (isc->try_config.sd_format && isc->config.sd_format &&
>> isc->try_config.sd_format != isc->config.sd_format) {
>> isc->ctrls.hist_stat = HIST_INIT;
>> isc_reset_awb_ctrls(isc);
>> isc_update_v4l2_ctrls(isc);
>> }
>> - /* make the try configuration active */
>> +
>> + /* Validate formats */
>> + ret = isc_try_validate_formats(isc);
>> + if (ret)
>> + return ret;
>> +
>> + /* Obtain frame sizes if possible to have crop requirements ready */
>> + isc_try_fse(isc, &pad_state);
>> +
>> + /* Configure ISC pipeline for the config */
>> + ret = isc_try_configure_pipeline(isc);
>> + if (ret)
>> + return ret;
>> +
>> isc->config = isc->try_config;
>>
>> v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
>> @@ -1064,6 +1030,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>> return 0;
>> }
>>
>> +static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
>> +{
>> + struct isc_device *isc = video_drvdata(file);
>> + int ret;
>> +
>> + ret = isc_validate(isc);
>> +
>> + if (ret)
>> + return ret;
>> +
>> + return vb2_ioctl_streamon(file, priv, bt);
>> +}
>> +
>> static int isc_s_fmt_vid_cap(struct file *file, void *priv,
>> struct v4l2_format *f)
>> {
>> @@ -1080,7 +1059,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
>> {
>> struct isc_device *isc = video_drvdata(file);
>>
>> - return isc_try_fmt(isc, f, NULL);
>> + return isc_try_fmt(isc, f);
>> }
>>
>> static int isc_enum_input(struct file *file, void *priv,
>> @@ -1176,7 +1155,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
>> .vidioc_dqbuf = vb2_ioctl_dqbuf,
>> .vidioc_create_bufs = vb2_ioctl_create_bufs,
>> .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>> - .vidioc_streamon = vb2_ioctl_streamon,
>> + .vidioc_streamon = isc_streamon,
>> .vidioc_streamoff = vb2_ioctl_streamoff,
>>
>> .vidioc_g_parm = isc_g_parm,
>> @@ -1879,7 +1858,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
>> };
>> int ret;
>>
>> - ret = isc_try_fmt(isc, &f, NULL);
>> + ret = isc_try_fmt(isc, &f);
>> if (ret)
>> return ret;
>>
>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>> index c2cb805faff3..7081698adddd 100644
>> --- a/drivers/media/platform/atmel/atmel-isc.h
>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>> @@ -297,6 +297,7 @@ struct isc_device {
>> struct completion comp;
>>
>> struct v4l2_format fmt;
>> + struct v4l2_format try_fmt;
>> struct isc_format **user_formats;
>> unsigned int num_user_formats;
>>
>> --
>> 2.25.1
>>

2022-01-21 21:12:31

by Eugen Hristev

[permalink] [raw]
Subject: Re: [PATCH v3 23/23] media: atmel: atmel-isc: change format propagation to subdev into only verification

On 1/20/22 10:43 AM, Eugen Hristev - M18282 wrote:
> On 1/12/22 11:21 AM, Jacopo Mondi wrote:
>> Hi Eugen
>
>
> Hi Jacopo,
>
>>
>> On Mon, Dec 13, 2021 at 03:49:40PM +0200, Eugen Hristev wrote:
>>> As a top MC video driver, the atmel-isc should not propagate the format to the
>>> subdevice.
>>> It should rather check at streamon() time if the subdev is properly configured
>>> with a compatible format.
>>> Removed the whole format finding logic, and reworked the format verification
>>> at streamon time, such that the ISC will return an error if the subdevice
>>> is not properly configured.
>>> With this being done, the module parameter 'sensor_prefered' makes no sense
>>> anymore. The ISC should not decide which format the sensor is using. The
>>> ISC should only cope with the situation and inform userspace if the streaming
>>> is possible in the current configuration.
>>
>> Sounds great!
>>
>>>
>>> Signed-off-by: Eugen Hristev <[email protected]>
>>> ---
>>> Changes in v3:
>>> - clamp to maximum resolution once the frame size from the subdev is found
>>>
>>> drivers/media/platform/atmel/atmel-isc-base.c | 271 ++++++++----------
>>> drivers/media/platform/atmel/atmel-isc.h | 1 +
>>> 2 files changed, 126 insertions(+), 146 deletions(-)
>>>
>>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>>> index 31c8e3029eee..00c8c9588a78 100644
>>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>>> @@ -36,11 +36,6 @@ static unsigned int debug;
>>> module_param(debug, int, 0644);
>>> MODULE_PARM_DESC(debug, "debug level (0-2)");
>>>
>>> -static unsigned int sensor_preferred = 1;
>>> -module_param(sensor_preferred, uint, 0644);
>>> -MODULE_PARM_DESC(sensor_preferred,
>>> - "Sensor is preferred to output the specified format (1-on 0-off), default 1");
>>> -
>>> #define ISC_IS_FORMAT_RAW(mbus_code) \
>>> (((mbus_code) & 0xf000) == 0x3000)
>>>
>>> @@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
>>> * convert it to any of the formats that we usually can with a
>>> * RAW sensor. Thus, do not advertise them.
>>> */
>>> - if (!isc->config.sd_format ||
>>> + if (isc->config.sd_format &&
>>
>> Is this change intentional ?
>>
>>> !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
>>> return -EINVAL;
>>>
>>> @@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
>>> break;
>>> default:
>>> /* any other different formats are not supported */
>>> + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
>>> ret = -EINVAL;
>>> }
>>> v4l2_dbg(1, debug, &isc->v4l2_dev,
>>> "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
>>> rgb, yuv, grey, bayer);
>>
>> Would it make sense to move this before the switch so that the
>> error messages, if any, appear later ?
>
> Actually, no, because the variables rgb, yuv, grey, bayer, are set
> according to what happens in the switch
>>
>>>
>>> - /* we cannot output RAW if we do not receive RAW */
>>> - if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
>>> + if ((bayer) &&
>>> + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
>>> + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
>>> return -EINVAL;
>>> + }
>>>
>>> - /* we cannot output GREY if we do not receive RAW/GREY */
>>> if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
>>> - !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
>>> + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
>>> + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
>>> return -EINVAL;
>>> + }
>>> +
>>> + if ((rgb || bayer || yuv) &&
>>> + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
>>> + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
>>> + return -EINVAL;
>>> + }
>>>
>>> return ret;
>>> }
>>> @@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
>>> * If we do not know yet which format the subdev is using, we cannot
>>> * do anything.
>>> */
>>> - if (!isc->try_config.sd_format)
>>> + if (!isc->config.sd_format)
>>> return;
>>>
>>> fse.code = isc->try_config.sd_format->mbus_code;
>>> @@ -883,180 +888,141 @@ static void isc_try_fse(struct isc_device *isc,
>>> }
>>> }
>>>
>>> -static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
>>> - u32 *code)
>>> +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
>>> {
>>> - int i;
>>> - struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
>>> struct v4l2_pix_format *pixfmt = &f->fmt.pix;
>>> - struct v4l2_subdev_pad_config pad_cfg = {};
>>> - struct v4l2_subdev_state pad_state = {
>>> - .pads = &pad_cfg
>>> - };
>>> - struct v4l2_subdev_format format = {
>>> - .which = V4L2_SUBDEV_FORMAT_TRY,
>>> - };
>>> - u32 mbus_code;
>>> - int ret;
>>> - bool rlp_dma_direct_dump = false;
>>> + unsigned int i;
>>>
>>> if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>>> return -EINVAL;
>>>
>>> - /* Step 1: find a RAW format that is supported */
>>> - for (i = 0; i < isc->num_user_formats; i++) {
>>> - if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
>>> - sd_fmt = isc->user_formats[i];
>>> + isc->try_config.fourcc = isc->user_formats[0]->fourcc;
>>> +
>>> + /* find if the format requested is supported */
>>> + for (i = 0; i < isc->controller_formats_size; i++)
>>> + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
>>> + isc->try_config.fourcc = pixfmt->pixelformat;
>>> break;
>>> }
>>> - }
>>> - /* Step 2: We can continue with this RAW format, or we can look
>>> - * for better: maybe sensor supports directly what we need.
>>> - */
>>> - direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
>>> -
>>> - /* Step 3: We have both. We decide given the module parameter which
>>> - * one to use.
>>> - */
>>> - if (direct_fmt && sd_fmt && sensor_preferred)
>>> - sd_fmt = direct_fmt;
>>> -
>>> - /* Step 4: we do not have RAW but we have a direct format. Use it. */
>>> - if (direct_fmt && !sd_fmt)
>>> - sd_fmt = direct_fmt;
>>> -
>>> - /* Step 5: if we are using a direct format, we need to package
>>> - * everything as 8 bit data and just dump it
>>> - */
>>> - if (sd_fmt == direct_fmt)
>>> - rlp_dma_direct_dump = true;
>>> -
>>> - /* Step 6: We have no format. This can happen if the userspace
>>> - * requests some weird/invalid format.
>>> - * In this case, default to whatever we have
>>> - */
>>> - if (!sd_fmt && !direct_fmt) {
>>> - sd_fmt = isc->user_formats[isc->num_user_formats - 1];
>>> - v4l2_dbg(1, debug, &isc->v4l2_dev,
>>> - "Sensor not supporting %.4s, using %.4s\n",
>>> - (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
>>> - }
>>> -
>>> - if (!sd_fmt) {
>>> - ret = -EINVAL;
>>> - goto isc_try_fmt_err;
>>> - }
>>> -
>>> - /* Step 7: Print out what we decided for debugging */
>>> - v4l2_dbg(1, debug, &isc->v4l2_dev,
>>> - "Preferring to have sensor using format %.4s\n",
>>> - (char *)&sd_fmt->fourcc);
>>> -
>>> - /* Step 8: at this moment we decided which format the subdev will use */
>>> - isc->try_config.sd_format = sd_fmt;
>>> -
>>> - /* Limit to Atmel ISC hardware capabilities */
>>> - if (pixfmt->width > isc->max_width)
>>> - pixfmt->width = isc->max_width;
>>> - if (pixfmt->height > isc->max_height)
>>> - pixfmt->height = isc->max_height;
>>> -
>>> - /*
>>> - * The mbus format is the one the subdev outputs.
>>> - * The pixels will be transferred in this format Sensor -> ISC
>>> - */
>>> - mbus_code = sd_fmt->mbus_code;
>>> -
>>> - /*
>>> - * Validate formats. If the required format is not OK, default to raw.
>>> - */
>>> -
>>> - isc->try_config.fourcc = pixfmt->pixelformat;
>>> -
>>> - if (isc_try_validate_formats(isc)) {
>>> - pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
>>> - /* Re-try to validate the new format */
>>> - ret = isc_try_validate_formats(isc);
>>> - if (ret)
>>> - goto isc_try_fmt_err;
>>> - }
>>> -
>>> - ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
>>> - if (ret)
>>> - goto isc_try_fmt_err;
>>> -
>>> - ret = isc_try_configure_pipeline(isc);
>>> - if (ret)
>>> - goto isc_try_fmt_err;
>>>
>>> - /* Obtain frame sizes if possible to have crop requirements ready */
>>> - isc_try_fse(isc, &pad_state);
>>> -
>>> - v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
>>> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
>>> - &pad_state, &format);
>>> - if (ret < 0)
>>> - goto isc_try_fmt_subdev_err;
>>> + /* If we did not find the requested format, we will fallback here */
>>> + pixfmt->pixelformat = isc->try_config.fourcc;
>>> + pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> + pixfmt->field = V4L2_FIELD_NONE;
>>>
>>> - v4l2_fill_pix_format(pixfmt, &format.format);
>>> + isc_try_configure_rlp_dma(isc, false);
>>>
>>> /* Limit to Atmel ISC hardware capabilities */
>>> - if (pixfmt->width > isc->max_width)
>>> - pixfmt->width = isc->max_width;
>>> - if (pixfmt->height > isc->max_height)
>>> - pixfmt->height = isc->max_height;
>>> + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
>>> + &pixfmt->height, 16, isc->max_height, 0, 0);
>>>
>>> pixfmt->field = V4L2_FIELD_NONE;
>>> pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
>>> pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
>>> pixfmt->height;
>>>
>>> - if (code)
>>> - *code = mbus_code;
>>> + isc->try_fmt = *f;
>>>
>>> return 0;
>>> +}
>>>
>>> -isc_try_fmt_err:
>>> - v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
>>> -isc_try_fmt_subdev_err:
>>> - memset(&isc->try_config, 0, sizeof(isc->try_config));
>>> +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>>> +{
>>> + isc_try_fmt(isc, f);
>>>
>>> - return ret;
>>> + /* make the try configuration active */
>>> + isc->config = isc->try_config;
>>> + isc->fmt = isc->try_fmt;
>>> +
>>> + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
>>> + (char *)&f->fmt.pix.pixelformat,
>>> + f->fmt.pix.width, f->fmt.pix.height);
>>> +
>>> + return 0;
>>> }
>>>
>>> -static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>>> +static int isc_validate(struct isc_device *isc)
>>> {
>>> + int ret;
>>> + int i;
>>> + struct isc_format *sd_fmt = NULL;
>>> + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
>>> struct v4l2_subdev_format format = {
>>> .which = V4L2_SUBDEV_FORMAT_ACTIVE,
>>> + .pad = isc->remote_pad,
>>> + };
>>> + struct v4l2_subdev_pad_config pad_cfg = {};
>>> + struct v4l2_subdev_state pad_state = {
>>> + .pads = &pad_cfg,
>>> };
>>> - u32 mbus_code = 0;
>>> - int ret;
>>>
>>> - ret = isc_try_fmt(isc, f, &mbus_code);
>>> + /* Get current format from subdev */
>>> + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
>>> + &format);
>>
>> Ah! Haven't you just said we don't care anymore about the subdev
>> format ? :)
>
> We don't ! but we can't stream anything if the sensor streams a format
> that we don't understand do we ?

Actually, thinking about this, we really need to know what format is at
the input for the ISC. According to this format, we need to understand
how to process it, since the ISC could have at input either Raw bayer
and we need to know the rotation, or directly a non-raw format like yuv422 .

Rather than asking the subdev for the streaming format, we could obtain
this information from the pad link ?

>
>>
>> Kidding, I might got a bit lost in the logic, but if I look at your
>> above isc_try_validate_formats() it seems like an ideal candidate for
>> .link_validate() media_entity operation.
>>
>> Just to make sure we're on the same page, here's how it should ideally
>> look like:
>> - set format does care about subdev format. It only checks that the
>> format required from the user is one of the ISC supported one. Ie.
>> no v4l2_subdev_call()
>>
>> - as s_stream time your top driver calls media_pipeline_start()
>>
>> - media_pipeline_start() walks all the entities in the pipeline and
>> validates the format of connected pads. To validate formats the
>> __media_pipeline_start() functions calls link_validate() on each
>> entity. You should in your driver set
>
> Is this done automatically ? if this would be the case, where all links
> are correctly set, then, in theory, it would not be possible that some
> entities have a format set that is not supported by the other side
>
>>
>> static const struct media_entity_operations your_media_entity_ops = {
>> .link_validate = v4l2_subdev_link_validate,
>> };
>>
>> if you want to use the default link validation procedure, or set the
>> callback to your custom validation function, which can behave more
>> or less like isc_try_validate_formats()
>
> Does this mean that I can ask the subdev for the format ?
> Or how can I obtain the format on the other side of the link ?
> link validate can do this for me ?
>
>
> Thanks for reviewing,
> Eugen
>>
>> Does it match your understanding too ?
>>
>> Thanks
>> j
>>
>>
>>
>>> if (ret)
>>> return ret;
>>>
>>> - v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
>>> - ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
>>> - set_fmt, NULL, &format);
>>> - if (ret < 0)
>>> - return ret;
>>> + /* Identify the subdev's format configuration */
>>> + for (i = 0; i < isc->num_user_formats; i++)
>>> + if (isc->user_formats[i]->mbus_code == format.format.code) {
>>> + sd_fmt = isc->user_formats[i];
>>> + break;
>>> + }
>>> +
>>> + /* Check if the format is not supported */
>>> + if (!sd_fmt) {
>>> + v4l2_err(&isc->v4l2_dev,
>>> + "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
>>> + format.format.code);
>>> + return -EPIPE;
>>> + }
>>> +
>>> + /* At this moment we know which format the subdev will use */
>>> + isc->try_config.sd_format = sd_fmt;
>>> +
>>> + /* If the sensor is not RAW, we can only do a direct dump */
>>> + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
>>> + isc_try_configure_rlp_dma(isc, true);
>>>
>>> /* Limit to Atmel ISC hardware capabilities */
>>> - if (f->fmt.pix.width > isc->max_width)
>>> - f->fmt.pix.width = isc->max_width;
>>> - if (f->fmt.pix.height > isc->max_height)
>>> - f->fmt.pix.height = isc->max_height;
>>> + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0,
>>> + &format.format.height, 16, isc->max_height, 0, 0);
>>>
>>> - isc->fmt = *f;
>>> + /* Check if the frame size is the same. Otherwise we may overflow */
>>> + if (pixfmt->height != format.format.height ||
>>> + pixfmt->width != format.format.width) {
>>> + v4l2_err(&isc->v4l2_dev,
>>> + "ISC not configured with the proper frame size: %dx%d\n",
>>> + format.format.width, format.format.height);
>>> + return -EPIPE;
>>> + }
>>>
>>> + v4l2_dbg(1, debug, &isc->v4l2_dev,
>>> + "Identified subdev using format %.4s with %dx%d %d bpp\n",
>>> + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
>>> + isc->try_config.bpp);
>>> +
>>> + /* Reset and restart AWB if the subdevice changed the format */
>>> if (isc->try_config.sd_format && isc->config.sd_format &&
>>> isc->try_config.sd_format != isc->config.sd_format) {
>>> isc->ctrls.hist_stat = HIST_INIT;
>>> isc_reset_awb_ctrls(isc);
>>> isc_update_v4l2_ctrls(isc);
>>> }
>>> - /* make the try configuration active */
>>> +
>>> + /* Validate formats */
>>> + ret = isc_try_validate_formats(isc);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* Obtain frame sizes if possible to have crop requirements ready */
>>> + isc_try_fse(isc, &pad_state);
>>> +
>>> + /* Configure ISC pipeline for the config */
>>> + ret = isc_try_configure_pipeline(isc);
>>> + if (ret)
>>> + return ret;
>>> +
>>> isc->config = isc->try_config;
>>>
>>> v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
>>> @@ -1064,6 +1030,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
>>> return 0;
>>> }
>>>
>>> +static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
>>> +{
>>> + struct isc_device *isc = video_drvdata(file);
>>> + int ret;
>>> +
>>> + ret = isc_validate(isc);
>>> +
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return vb2_ioctl_streamon(file, priv, bt);
>>> +}
>>> +
>>> static int isc_s_fmt_vid_cap(struct file *file, void *priv,
>>> struct v4l2_format *f)
>>> {
>>> @@ -1080,7 +1059,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
>>> {
>>> struct isc_device *isc = video_drvdata(file);
>>>
>>> - return isc_try_fmt(isc, f, NULL);
>>> + return isc_try_fmt(isc, f);
>>> }
>>>
>>> static int isc_enum_input(struct file *file, void *priv,
>>> @@ -1176,7 +1155,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
>>> .vidioc_dqbuf = vb2_ioctl_dqbuf,
>>> .vidioc_create_bufs = vb2_ioctl_create_bufs,
>>> .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>>> - .vidioc_streamon = vb2_ioctl_streamon,
>>> + .vidioc_streamon = isc_streamon,
>>> .vidioc_streamoff = vb2_ioctl_streamoff,
>>>
>>> .vidioc_g_parm = isc_g_parm,
>>> @@ -1879,7 +1858,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
>>> };
>>> int ret;
>>>
>>> - ret = isc_try_fmt(isc, &f, NULL);
>>> + ret = isc_try_fmt(isc, &f);
>>> if (ret)
>>> return ret;
>>>
>>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>>> index c2cb805faff3..7081698adddd 100644
>>> --- a/drivers/media/platform/atmel/atmel-isc.h
>>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>>> @@ -297,6 +297,7 @@ struct isc_device {
>>> struct completion comp;
>>>
>>> struct v4l2_format fmt;
>>> + struct v4l2_format try_fmt;
>>> struct isc_format **user_formats;
>>> unsigned int num_user_formats;
>>>
>>> --
>>> 2.25.1
>>>
>