diff options
author | Aspen Smith <root@gws.fyi> | 2024-07-28T16·58-0400 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-08-01T10·06+0000 |
commit | 756539a59687f9abc9fef5fce50b5590c35a242f (patch) | |
tree | 06bd57732c359bb5b366b225043b59c6dd2f2ad9 /users/flokli/ipu6-softisp/kernel/softisp.patch | |
parent | bdf82698592acc3f27fa7a9aa612ea1ad7970437 (diff) |
chore(3p/sources): Bump channels & overlays (2024-07-28) r/8435
* Treewide: re-run depotfmt * //third_party/nixpkgs:html5validator: build with Python 3.11, dependency openstackdocstheme doesn't support 3.12 * //users/sterni/machines/ingeborg: adapt to poorly handled fcgiwrap module API change: https://github.com/NixOS/nixpkgs/pull/318599 * //tvix/*-go: regenerate protobuf files * //third_party/nixpkgs:treefmt: Remove patch for merged pull request * //users/flokli/ipu6-softisp: rebase, drop upstreamed kernel patches Change-Id: Ie4e0df007c287e8cd6207683a9a25838aa5bd39a Reviewed-on: https://cl.tvl.fyi/c/depot/+/11971 Autosubmit: sterni <sternenseemann@systemli.org> Reviewed-by: sterni <sternenseemann@systemli.org> Reviewed-by: flokli <flokli@flokli.de> Reviewed-by: aspen <root@gws.fyi> Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su> Reviewed-by: Ilan Joselevich <personal@ilanjoselevich.com>
Diffstat (limited to 'users/flokli/ipu6-softisp/kernel/softisp.patch')
-rw-r--r-- | users/flokli/ipu6-softisp/kernel/softisp.patch | 16732 |
1 files changed, 59 insertions, 16673 deletions
diff --git a/users/flokli/ipu6-softisp/kernel/softisp.patch b/users/flokli/ipu6-softisp/kernel/softisp.patch index 8731ed914c1d..e1395bbb135f 100644 --- a/users/flokli/ipu6-softisp/kernel/softisp.patch +++ b/users/flokli/ipu6-softisp/kernel/softisp.patch @@ -1,16249 +1,7 @@ -From 037f05a9772f3243907bb826e913971ee20e9487 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:31 +0300 -Subject: [PATCH 01/33] media: mc: Add INTERNAL pad flag - -Internal source pads will be used as routing endpoints in V4L2 -[GS]_ROUTING IOCTLs, to indicate that the stream begins in the entity. - -Also prevent creating links to pads that have been flagged as internal. - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - Documentation/userspace-api/media/glossary.rst | 6 ++++++ - Documentation/userspace-api/media/mediactl/media-types.rst | 6 ++++++ - drivers/media/mc/mc-entity.c | 6 +++++- - include/uapi/linux/media.h | 1 + - 4 files changed, 18 insertions(+), 1 deletion(-) - -diff --git a/Documentation/userspace-api/media/glossary.rst b/Documentation/userspace-api/media/glossary.rst -index 96a360edbf3b..f7b99a4527c7 100644 ---- a/Documentation/userspace-api/media/glossary.rst -+++ b/Documentation/userspace-api/media/glossary.rst -@@ -173,6 +173,12 @@ Glossary - An integrated circuit that integrates all components of a computer - or other electronic systems. - -+_media-glossary-stream: -+ Stream -+ A distinct flow of data (image data or metadata) over a media pipeline -+ from source to sink. A source may be e.g. an image sensor and a sink -+ e.g. a memory buffer. -+ - V4L2 API - **V4L2 userspace API** - -diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst -index 0ffeece1e0c8..28941da27790 100644 ---- a/Documentation/userspace-api/media/mediactl/media-types.rst -+++ b/Documentation/userspace-api/media/mediactl/media-types.rst -@@ -361,6 +361,7 @@ Types and flags used to represent the media graph elements - .. _MEDIA-PAD-FL-SINK: - .. _MEDIA-PAD-FL-SOURCE: - .. _MEDIA-PAD-FL-MUST-CONNECT: -+.. _MEDIA-PAD-FL-INTERNAL: - - .. flat-table:: Media pad flags - :header-rows: 0 -@@ -382,6 +383,11 @@ Types and flags used to represent the media graph elements - when this flag isn't set; the absence of the flag doesn't imply - there is none. - -+ * - ``MEDIA_PAD_FL_INTERNAL`` -+ - The internal flag indicates an internal pad that has no external -+ connections. Such a pad shall not be connected with a link. The -+ internal flag indicates that the :ref:``stream -+ <media-glossary-stream>`` either starts or ends in the entity. - - One and only one of ``MEDIA_PAD_FL_SINK`` and ``MEDIA_PAD_FL_SOURCE`` - must be set for every pad. -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index 543a392f8635..1fc80fd3e5e3 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -1075,7 +1075,8 @@ int media_get_pad_index(struct media_entity *entity, u32 pad_type, - - for (i = 0; i < entity->num_pads; i++) { - if ((entity->pads[i].flags & -- (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type) -+ (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE | -+ MEDIA_PAD_FL_INTERNAL)) != pad_type) - continue; - - if (entity->pads[i].sig_type == sig_type) -@@ -1098,6 +1099,9 @@ media_create_pad_link(struct media_entity *source, u16 source_pad, - return -EINVAL; - if (WARN_ON(!(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE))) - return -EINVAL; -+ if (WARN_ON(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE && -+ source->pads[source_pad].flags & MEDIA_PAD_FL_INTERNAL)) -+ return -EINVAL; - if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK))) - return -EINVAL; - -diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h -index 1c80b1d6bbaf..80cfd12a43fc 100644 ---- a/include/uapi/linux/media.h -+++ b/include/uapi/linux/media.h -@@ -208,6 +208,7 @@ struct media_entity_desc { - #define MEDIA_PAD_FL_SINK (1U << 0) - #define MEDIA_PAD_FL_SOURCE (1U << 1) - #define MEDIA_PAD_FL_MUST_CONNECT (1U << 2) -+#define MEDIA_PAD_FL_INTERNAL (1U << 3) - - struct media_pad_desc { - __u32 entity; /* entity ID */ --- -2.43.2 - - -From 5f0cdae874f1c0237936c2c12a9fc019b93de4c9 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:32 +0300 -Subject: [PATCH 02/33] media: uapi: Add generic serial metadata mbus formats - -Add generic serial metadata mbus formats. These formats describe data -width and packing but not the content itself. The reason for specifying -such formats is that the formats as such are fairly device specific but -they are still handled by CSI-2 receiver drivers that should not be aware -of device specific formats. What makes generic metadata formats possible -is that these formats are parsed by software only, after capturing the -data to system memory. - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - .../media/v4l/subdev-formats.rst | 257 ++++++++++++++++++ - include/uapi/linux/media-bus-format.h | 9 + - 2 files changed, 266 insertions(+) - -diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst -index eb3cd20b0cf2..7d107873cddd 100644 ---- a/Documentation/userspace-api/media/v4l/subdev-formats.rst -+++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst -@@ -8306,3 +8306,260 @@ The following table lists the existing metadata formats. - both sides of the link and the bus format is a fixed - metadata format that is not configurable from userspace. - Width and height will be set to 0 for this format. -+ -+Generic Serial Metadata Formats -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Generic serial metadata formats are used on serial busses where the actual data -+content is more or less device specific but the data is transmitted and received -+by multiple devices that do not process the data in any way, simply writing -+it to system memory for processing in software at the end of the pipeline. -+ -+The more specific variant describing the actual data is used on the internal -+source pad of the originating sub-device. -+ -+"b" in an array cell signifies a byte of data, followed by the number of byte -+and finally the bit number in subscript. "p" indicates a padding bit. -+ -+.. _media-bus-format-generic-meta: -+ -+.. cssclass: longtable -+ -+.. flat-table:: Generic Serial Metadata Formats -+ :header-rows: 2 -+ :stub-columns: 0 -+ -+ * - Identifier -+ - Code -+ - -+ - :cspan:`23` Data organization -+ * - -+ - -+ - Bit -+ - 23 -+ - 22 -+ - 21 -+ - 20 -+ - 19 -+ - 18 -+ - 17 -+ - 16 -+ - 15 -+ - 14 -+ - 13 -+ - 12 -+ - 11 -+ - 10 -+ - 9 -+ - 8 -+ - 7 -+ - 6 -+ - 5 -+ - 4 -+ - 3 -+ - 2 -+ - 1 -+ - 0 -+ * .. _MEDIA-BUS-FMT-META-8: -+ -+ - MEDIA_BUS_FMT_META_8 -+ - 0x8001 -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ * .. _MEDIA-BUS-FMT-META-10: -+ -+ - MEDIA_BUS_FMT_META_10 -+ - 0x8002 -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ * .. _MEDIA-BUS-FMT-META-12: -+ -+ - MEDIA_BUS_FMT_META_12 -+ - 0x8003 -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ - p -+ - p -+ * .. _MEDIA-BUS-FMT-META-14: -+ -+ - MEDIA_BUS_FMT_META_14 -+ - 0x8004 -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ * .. _MEDIA-BUS-FMT-META-16: -+ -+ - MEDIA_BUS_FMT_META_16 -+ - 0x8005 -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ * .. _MEDIA-BUS-FMT-META-20: -+ -+ - MEDIA_BUS_FMT_META_20 -+ - 0x8007 -+ - -+ - -+ - -+ - -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ * .. _MEDIA-BUS-FMT-META-24: -+ -+ - MEDIA_BUS_FMT_META_24 -+ - 0x8009 -+ - -+ - b0\ :sub:`7` -+ - b0\ :sub:`6` -+ - b0\ :sub:`5` -+ - b0\ :sub:`4` -+ - b0\ :sub:`3` -+ - b0\ :sub:`2` -+ - b0\ :sub:`1` -+ - b0\ :sub:`0` -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -+ - p -diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h -index f05f747e444d..d4c1d991014b 100644 ---- a/include/uapi/linux/media-bus-format.h -+++ b/include/uapi/linux/media-bus-format.h -@@ -174,4 +174,13 @@ - */ - #define MEDIA_BUS_FMT_METADATA_FIXED 0x7001 - -+/* Generic line based metadata formats for serial buses. Next is 0x8008. */ -+#define MEDIA_BUS_FMT_META_8 0x8001 -+#define MEDIA_BUS_FMT_META_10 0x8002 -+#define MEDIA_BUS_FMT_META_12 0x8003 -+#define MEDIA_BUS_FMT_META_14 0x8004 -+#define MEDIA_BUS_FMT_META_16 0x8005 -+#define MEDIA_BUS_FMT_META_20 0x8006 -+#define MEDIA_BUS_FMT_META_24 0x8007 -+ - #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ --- -2.43.2 - - -From 8af4eeaee34159605ec86b57fa638a82fd968f31 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:33 +0300 -Subject: [PATCH 03/33] media: uapi: Document which mbus format fields are - valid for metadata - -Now that metadata mbus formats have been added, it is necessary to define -which fields in struct v4l2_mbus_format are applicable to them (not many). - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - include/uapi/linux/v4l2-mediabus.h | 18 ++++++++++++------ - 1 file changed, 12 insertions(+), 6 deletions(-) - -diff --git a/include/uapi/linux/v4l2-mediabus.h b/include/uapi/linux/v4l2-mediabus.h -index 6b07b73473b5..3cadb3b58b85 100644 ---- a/include/uapi/linux/v4l2-mediabus.h -+++ b/include/uapi/linux/v4l2-mediabus.h -@@ -19,12 +19,18 @@ - * @width: image width - * @height: image height - * @code: data format code (from enum v4l2_mbus_pixelcode) -- * @field: used interlacing type (from enum v4l2_field) -- * @colorspace: colorspace of the data (from enum v4l2_colorspace) -- * @ycbcr_enc: YCbCr encoding of the data (from enum v4l2_ycbcr_encoding) -- * @hsv_enc: HSV encoding of the data (from enum v4l2_hsv_encoding) -- * @quantization: quantization of the data (from enum v4l2_quantization) -- * @xfer_func: transfer function of the data (from enum v4l2_xfer_func) -+ * @field: used interlacing type (from enum v4l2_field), not applicable -+ * to metadata mbus codes -+ * @colorspace: colorspace of the data (from enum v4l2_colorspace), zero on -+ * metadata mbus codes -+ * @ycbcr_enc: YCbCr encoding of the data (from enum v4l2_ycbcr_encoding), zero -+ * on metadata mbus codes -+ * @hsv_enc: HSV encoding of the data (from enum v4l2_hsv_encoding), zero on -+ * metadata mbus codes -+ * @quantization: quantization of the data (from enum v4l2_quantization), zero -+ * on metadata mbus codes -+ * @xfer_func: transfer function of the data (from enum v4l2_xfer_func), zero -+ * on metadata mbus codes - * @flags: flags (V4L2_MBUS_FRAMEFMT_*) - * @reserved: reserved bytes that can be later used - */ --- -2.43.2 - - -From ed5884d40def9adfa77841427e52733746158a77 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:34 +0300 -Subject: [PATCH 04/33] media: uapi: Add a macro to tell whether an mbus code - is metadata - -Add a macro to tell whether a given mbus code is metadata. - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - include/uapi/linux/media-bus-format.h | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h -index d4c1d991014b..6fcd7d276bc6 100644 ---- a/include/uapi/linux/media-bus-format.h -+++ b/include/uapi/linux/media-bus-format.h -@@ -183,4 +183,7 @@ - #define MEDIA_BUS_FMT_META_20 0x8006 - #define MEDIA_BUS_FMT_META_24 0x8007 - -+#define MEDIA_BUS_FMT_IS_META(code) \ -+ ((code) & 0xf000 == 0x7000 || (code) & 0xf000 == 0x8000) -+ - #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ --- -2.43.2 - - -From c0e682a815c5baf012c8963968385c197e7e0943 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:35 +0300 -Subject: [PATCH 05/33] media: uapi: Add generic 8-bit metadata format - definitions - -Generic 8-bit metadata formats define the in-memory data layout but not -the format of the data itself. The reasoning for having such formats is to -allow CSI-2 receiver drivers to receive and DMA drivers to write the data -to memory without knowing a large number of device specific formats. - -These formats may be used only in conjunction of a Media controller -pipeline where the internal pad of the source sub-device defines the -specific format of the data (using an mbus code). - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - .../userspace-api/media/v4l/meta-formats.rst | 1 + - .../media/v4l/metafmt-generic.rst | 331 ++++++++++++++++++ - drivers/media/v4l2-core/v4l2-ioctl.c | 8 + - include/uapi/linux/videodev2.h | 9 + - 4 files changed, 349 insertions(+) - create mode 100644 Documentation/userspace-api/media/v4l/metafmt-generic.rst - -diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst -index 0bb61fc5bc00..919f595576b9 100644 ---- a/Documentation/userspace-api/media/v4l/meta-formats.rst -+++ b/Documentation/userspace-api/media/v4l/meta-formats.rst -@@ -19,3 +19,4 @@ These formats are used for the :ref:`metadata` interface only. - metafmt-vsp1-hgo - metafmt-vsp1-hgt - metafmt-vivid -+ metafmt-generic -diff --git a/Documentation/userspace-api/media/v4l/metafmt-generic.rst b/Documentation/userspace-api/media/v4l/metafmt-generic.rst -new file mode 100644 -index 000000000000..a27bfc721edf ---- /dev/null -+++ b/Documentation/userspace-api/media/v4l/metafmt-generic.rst -@@ -0,0 +1,331 @@ -+.. SPDX-License-Identifier: GPL-2.0 OR GFDL-1.1-no-invariants-or-later -+ -+************************************************************************************************************************************************************************************************************************************************************************************************************************** -+V4L2_META_FMT_GENERIC_8 ('MET8'), V4L2_META_FMT_GENERIC_CSI2_10 ('MC1A'), V4L2_META_FMT_GENERIC_CSI2_12 ('MC1C'), V4L2_META_FMT_GENERIC_CSI2_14 ('MC1E'), V4L2_META_FMT_GENERIC_CSI2_16 ('MC1G'), V4L2_META_FMT_GENERIC_CSI2_20 ('MC1K'), V4L2_META_FMT_GENERIC_CSI2_24 ('MC1O'), V4L2_META_FMT_GENERIC_CSI2_2_24 ('MC2O') -+************************************************************************************************************************************************************************************************************************************************************************************************************************** -+ -+ -+Generic line-based metadata formats -+ -+ -+Description -+=========== -+ -+These generic line-based metadata formats define the memory layout of the data -+without defining the format or meaning of the metadata itself. These formats may -+only be used with a Media controller pipeline where the more specific format is -+defined in an :ref:`internal source pad <MEDIA-PAD-FL-INTERNAL>` of the source -+sub-device. See also :ref:`source routes <v4l2-subdev-source-routes>`. -+ -+.. _v4l2-meta-fmt-generic-8: -+ -+V4L2_META_FMT_GENERIC_8 -+----------------------- -+ -+The V4L2_META_FMT_GENERIC_8 format is a plain 8-bit metadata format. -+ -+This format is also used on CSI-2 on both 8 bits per sample as well as on -+16 bits per sample when two bytes of metadata are packed into one sample. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_8.** -+Each cell is one byte. "M" denotes a byte of metadata. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - M\ :sub:`10` -+ - M\ :sub:`20` -+ - M\ :sub:`30` -+ * - start + 4: -+ - M\ :sub:`01` -+ - M\ :sub:`11` -+ - M\ :sub:`21` -+ - M\ :sub:`31` -+ -+.. _v4l2-meta-fmt-generic-csi2-10: -+ -+V4L2_META_FMT_GENERIC_CSI2_10 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_10 contains packed 8-bit generic metadata, 10 bits -+for each 8 bits of data. Every four bytes of metadata is followed by a single -+byte of padding. The way the data is stored follows the CSI-2 specification. -+ -+This format is also used on CSI-2 on 20 bits per sample format that packs two -+bytes of metadata into one sample. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_10.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - M\ :sub:`10` -+ - M\ :sub:`20` -+ - M\ :sub:`30` -+ - p -+ * - start + 5: -+ - M\ :sub:`01` -+ - M\ :sub:`11` -+ - M\ :sub:`21` -+ - M\ :sub:`31` -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-12: -+ -+V4L2_META_FMT_GENERIC_CSI2_12 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_12 contains packed 8-bit generic metadata, 12 bits -+for each 8 bits of data. Every four bytes of metadata is followed by two bytes -+of padding. The way the data is stored follows the CSI-2 specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_12.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - M\ :sub:`10` -+ - M\ :sub:`20` -+ - M\ :sub:`30` -+ - p -+ - p -+ * - start + 6: -+ - M\ :sub:`01` -+ - M\ :sub:`11` -+ - M\ :sub:`21` -+ - M\ :sub:`31` -+ - p -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-14: -+ -+V4L2_META_FMT_GENERIC_CSI2_14 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_14 contains packed 8-bit generic metadata, 14 bits -+for each 8 bits of data. Every four bytes of metadata is followed by three -+bytes of padding. The way the data is stored follows the CSI-2 specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_14.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - M\ :sub:`10` -+ - M\ :sub:`20` -+ - M\ :sub:`30` -+ - p -+ - p -+ - p -+ * - start + 7: -+ - M\ :sub:`01` -+ - M\ :sub:`11` -+ - M\ :sub:`21` -+ - M\ :sub:`31` -+ - p -+ - p -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-16: -+ -+V4L2_META_FMT_GENERIC_CSI2_16 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_16 contains packed 8-bit generic metadata, 16 bits -+for each 8 bits of data. Every byte of metadata is followed by one byte of -+padding. The way the data is stored follows the CSI-2 specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_16.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - p -+ - M\ :sub:`10` -+ - p -+ - M\ :sub:`20` -+ - p -+ - M\ :sub:`30` -+ - p -+ * - start + 8: -+ - M\ :sub:`01` -+ - p -+ - M\ :sub:`11` -+ - p -+ - M\ :sub:`21` -+ - p -+ - M\ :sub:`31` -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-20: -+ -+V4L2_META_FMT_GENERIC_CSI2_20 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_20 contains packed 8-bit generic metadata, 20 bits -+for each 8 bits of data. Every byte of metadata is followed by alternating one -+and two bytes of padding. The way the data is stored follows the CSI-2 -+specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_20.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - p -+ - M\ :sub:`10` -+ - p -+ - p -+ - M\ :sub:`20` -+ - p -+ - M\ :sub:`30` -+ - p -+ - p -+ * - start + 10: -+ - M\ :sub:`01` -+ - p -+ - M\ :sub:`11` -+ - p -+ - p -+ - M\ :sub:`21` -+ - p -+ - M\ :sub:`31` -+ - p -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-24: -+ -+V4L2_META_FMT_GENERIC_CSI2_24 -+----------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_24 contains packed 8-bit generic metadata, 24 bits -+for each 8 bits of data. Every byte of metadata is followed by two bytes of -+padding. The way the data is stored follows the CSI-2 specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_24.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - p -+ - p -+ - M\ :sub:`10` -+ - p -+ - p -+ - M\ :sub:`20` -+ - p -+ - p -+ - M\ :sub:`30` -+ - p -+ - p -+ * - start + 12: -+ - M\ :sub:`01` -+ - p -+ - p -+ - M\ :sub:`11` -+ - p -+ - p -+ - M\ :sub:`21` -+ - p -+ - p -+ - M\ :sub:`31` -+ - p -+ - p -+ -+.. _v4l2-meta-fmt-generic-csi2-2-24: -+ -+V4L2_META_FMT_GENERIC_CSI2_2_24 -+------------------------------- -+ -+V4L2_META_FMT_GENERIC_CSI2_2_24 contains packed 8-bit generic metadata, 24 bits -+for each two times 8 bits of data. Every two bytes of metadata are followed by -+one byte of padding. The way the data is stored follows the CSI-2 -+specification. -+ -+This format is little endian. -+ -+**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_2_24.** -+Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding. -+ -+.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{1.2cm}|p{.8cm}| -+ -+.. flat-table:: -+ :header-rows: 0 -+ :stub-columns: 0 -+ :widths: 12 8 8 8 8 8 8 -+ -+ * - start + 0: -+ - M\ :sub:`00` -+ - M\ :sub:`10` -+ - p -+ - M\ :sub:`20` -+ - M\ :sub:`30` -+ - p -+ * - start + 6: -+ - M\ :sub:`01` -+ - M\ :sub:`11` -+ - p -+ - M\ :sub:`21` -+ - M\ :sub:`31` -+ - p -+ -diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c -index 33076af4dfdb..4eb3db1773e1 100644 ---- a/drivers/media/v4l2-core/v4l2-ioctl.c -+++ b/drivers/media/v4l2-core/v4l2-ioctl.c -@@ -1452,6 +1452,14 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) - case V4L2_PIX_FMT_Y210: descr = "10-bit YUYV Packed"; break; - case V4L2_PIX_FMT_Y212: descr = "12-bit YUYV Packed"; break; - case V4L2_PIX_FMT_Y216: descr = "16-bit YUYV Packed"; break; -+ case V4L2_META_FMT_GENERIC_8: descr = "8-bit Generic Metadata"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_10: descr = "8b Generic Meta, 10b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_12: descr = "8b Generic Meta, 12b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_14: descr = "8b Generic Meta, 14b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_16: descr = "8b Generic Meta, 16b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_20: descr = "8b Generic Meta, 20b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_24: descr = "8b Generic Meta, 24b CSI-2"; break; -+ case V4L2_META_FMT_GENERIC_CSI2_2_24: descr = "2x8b Generic Meta, 24b CSI-2"; break; - - default: - /* Compressed formats */ -diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h -index 68e7ac178cc2..2c4e03d47789 100644 ---- a/include/uapi/linux/videodev2.h -+++ b/include/uapi/linux/videodev2.h -@@ -839,6 +839,15 @@ struct v4l2_pix_format { - #define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */ - #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ - -+#define V4L2_META_FMT_GENERIC_8 v4l2_fourcc('M', 'E', 'T', '8') /* Generic 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_10 v4l2_fourcc('M', 'C', '1', 'A') /* 10-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_12 v4l2_fourcc('M', 'C', '1', 'C') /* 12-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_14 v4l2_fourcc('M', 'C', '1', 'E') /* 14-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_16 v4l2_fourcc('M', 'C', '1', 'G') /* 16-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_20 v4l2_fourcc('M', 'C', '1', 'K') /* 20-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_24 v4l2_fourcc('M', 'C', '1', 'O') /* 24-bit CSI-2 packed 8-bit metadata */ -+#define V4L2_META_FMT_GENERIC_CSI2_2_24 v4l2_fourcc('M', 'C', '2', 'O') /* 2 bytes of 8-bit metadata, 24-bit CSI-2 packed */ -+ - /* priv field value to indicates that subsequent fields are valid. */ - #define V4L2_PIX_FMT_PRIV_MAGIC 0xfeedcafe - --- -2.43.2 - - -From 453627c23062ff0aa01e0e46e3b7922ddf82f998 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:36 +0300 -Subject: [PATCH 06/33] media: v4l: Support line-based metadata capture - -many camera sensors, among other devices, transmit embedded data and image -data for each CSI-2 frame. This embedded data typically contains register -configuration of the sensor that has been used to capture the image data -of the same frame. - -The embedded data is received by the CSI-2 receiver and has the same -properties as the image data, including that it is line based: it has -width, height and bytesperline (stride). - -Add these fields to struct v4l2_meta_format and document them. - -Also add V4L2_FMT_FLAG_META_LINE_BASED to tell a given format is -line-based i.e. these fields of struct v4l2_meta_format are valid for it. - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - .../userspace-api/media/v4l/dev-meta.rst | 15 +++++++++++++++ - .../userspace-api/media/v4l/vidioc-enum-fmt.rst | 7 +++++++ - .../media/videodev2.h.rst.exceptions | 1 + - drivers/media/v4l2-core/v4l2-ioctl.c | 5 +++-- - include/uapi/linux/videodev2.h | 10 ++++++++++ - 5 files changed, 36 insertions(+), 2 deletions(-) - -diff --git a/Documentation/userspace-api/media/v4l/dev-meta.rst b/Documentation/userspace-api/media/v4l/dev-meta.rst -index 0e7e1ee1471a..4b24bae6e171 100644 ---- a/Documentation/userspace-api/media/v4l/dev-meta.rst -+++ b/Documentation/userspace-api/media/v4l/dev-meta.rst -@@ -65,3 +65,18 @@ to 0. - - ``buffersize`` - - Maximum buffer size in bytes required for data. The value is set by the - driver. -+ * - __u32 -+ - ``width`` -+ - Width of a line of metadata in samples. Valid when :c:type`v4l2_fmtdesc` -+ flag ``V4L2_FMT_FLAG_META_LINE_BASED`` is set, otherwise zero. See -+ :c:func:`VIDIOC_ENUM_FMT`. -+ * - __u32 -+ - ``height`` -+ - Number of rows of metadata. Valid when :c:type`v4l2_fmtdesc` flag -+ ``V4L2_FMT_FLAG_META_LINE_BASED`` is set, otherwise zero. See -+ :c:func:`VIDIOC_ENUM_FMT`. -+ * - __u32 -+ - ``bytesperline`` -+ - Offset in bytes between the beginning of two consecutive lines. Valid -+ when :c:type`v4l2_fmtdesc` flag ``V4L2_FMT_FLAG_META_LINE_BASED`` is -+ set, otherwise zero. See :c:func:`VIDIOC_ENUM_FMT`. -diff --git a/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst b/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst -index 000c154b0f98..6d7664345a4e 100644 ---- a/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst -+++ b/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst -@@ -227,6 +227,13 @@ the ``mbus_code`` field is handled differently: - The application can ask to configure the quantization of the capture - device when calling the :ref:`VIDIOC_S_FMT <VIDIOC_G_FMT>` ioctl with - :ref:`V4L2_PIX_FMT_FLAG_SET_CSC <v4l2-pix-fmt-flag-set-csc>` set. -+ * - ``V4L2_FMT_FLAG_META_LINE_BASED`` -+ - 0x0200 -+ - The metadata format is line-based. In this case the ``width``, -+ ``height`` and ``bytesperline`` fields of :c:type:`v4l2_meta_format` are -+ valid. The buffer consists of ``height`` lines, each having ``width`` -+ bytes of data and offset between the beginning of each two consecutive -+ lines is ``bytesperline``. - - Return Value - ============ -diff --git a/Documentation/userspace-api/media/videodev2.h.rst.exceptions b/Documentation/userspace-api/media/videodev2.h.rst.exceptions -index 3e58aac4ef0b..bdc628e8c1d6 100644 ---- a/Documentation/userspace-api/media/videodev2.h.rst.exceptions -+++ b/Documentation/userspace-api/media/videodev2.h.rst.exceptions -@@ -215,6 +215,7 @@ replace define V4L2_FMT_FLAG_CSC_XFER_FUNC fmtdesc-flags - replace define V4L2_FMT_FLAG_CSC_YCBCR_ENC fmtdesc-flags - replace define V4L2_FMT_FLAG_CSC_HSV_ENC fmtdesc-flags - replace define V4L2_FMT_FLAG_CSC_QUANTIZATION fmtdesc-flags -+replace define V4L2_FMT_FLAG_META_LINE_BASED fmtdesc-flags - - # V4L2 timecode types - replace define V4L2_TC_TYPE_24FPS timecode-type -diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c -index 4eb3db1773e1..1fcec1515bcc 100644 ---- a/drivers/media/v4l2-core/v4l2-ioctl.c -+++ b/drivers/media/v4l2-core/v4l2-ioctl.c -@@ -343,8 +343,9 @@ static void v4l_print_format(const void *arg, bool write_only) - case V4L2_BUF_TYPE_META_OUTPUT: - meta = &p->fmt.meta; - pixelformat = meta->dataformat; -- pr_cont(", dataformat=%p4cc, buffersize=%u\n", -- &pixelformat, meta->buffersize); -+ pr_cont(", dataformat=%p4cc, buffersize=%u, width=%u, height=%u, bytesperline=%u\n", -+ &pixelformat, meta->buffersize, meta->width, -+ meta->height, meta->bytesperline); - break; - } - } -diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h -index 2c4e03d47789..48fb44772098 100644 ---- a/include/uapi/linux/videodev2.h -+++ b/include/uapi/linux/videodev2.h -@@ -878,6 +878,7 @@ struct v4l2_fmtdesc { - #define V4L2_FMT_FLAG_CSC_YCBCR_ENC 0x0080 - #define V4L2_FMT_FLAG_CSC_HSV_ENC V4L2_FMT_FLAG_CSC_YCBCR_ENC - #define V4L2_FMT_FLAG_CSC_QUANTIZATION 0x0100 -+#define V4L2_FMT_FLAG_META_LINE_BASED 0x0200 - - /* Frame Size and frame rate enumeration */ - /* -@@ -2424,10 +2425,19 @@ struct v4l2_sdr_format { - * struct v4l2_meta_format - metadata format definition - * @dataformat: little endian four character code (fourcc) - * @buffersize: maximum size in bytes required for data -+ * @width: number of bytes of data per line (valid for line based -+ * formats only, see format documentation) -+ * @height: number of lines of data per buffer (valid for line based -+ * formats only) -+ * @bytesperline: offset between the beginnings of two adjacent lines in -+ * bytes (valid for line based formats only) - */ - struct v4l2_meta_format { - __u32 dataformat; - __u32 buffersize; -+ __u32 width; -+ __u32 height; -+ __u32 bytesperline; - } __attribute__ ((packed)); - - /** --- -2.43.2 - - -From 090bb3894bda739ff06e8a431815210b731056b7 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus <sakari.ailus@linux.intel.com> -Date: Tue, 8 Aug 2023 10:55:37 +0300 -Subject: [PATCH 07/33] media: Add media bus codes for MIPI CCS embedded data - -Add new MIPI CCS embedded data media bus formats. - -Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> ---- - .../media/v4l/subdev-formats.rst | 32 +++++++++++++++++++ - include/uapi/linux/media-bus-format.h | 10 +++++- - 2 files changed, 41 insertions(+), 1 deletion(-) - -diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst -index 7d107873cddd..7afb057a09c5 100644 ---- a/Documentation/userspace-api/media/v4l/subdev-formats.rst -+++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst -@@ -8563,3 +8563,35 @@ and finally the bit number in subscript. "p" indicates a padding bit. - - p - - p - - p -+ -+MIPI CCS Embedded Data Formats -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+`MIPI CCS <https://www.mipi.org/specifications/camera-command-set>`_ defines an -+metadata format for sensor embedded data, which is used to store the register -+configuration used for capturing a given frame. The format is defined in the CCS -+specification. -+ -+The bit depth of the CCS embedded data matches the pixel data bit depth -+configured on the sensor. The formats used and their corresponding generic -+formats are listed in the table below. -+ -+.. flat-table: CCS embedded data mbus formats and corresponding generic formats -+ :header-rows: 1 -+ -+ * - CCS embedded data mbus format -+ - Generic metadata format -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_8 -+ - MEDIA_BUS_FMT_META_8 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_10 -+ - MEDIA_BUS_FMT_META_10 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_12 -+ - MEDIA_BUS_FMT_META_12 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_14 -+ - MEDIA_BUS_FMT_META_14 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_16 -+ - MEDIA_BUS_FMT_META_16 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_20 -+ - MEDIA_BUS_FMT_META_20 -+ * - MEDIA_BUS_FMT_CCS_EMBEDDED_24 -+ - MEDIA_BUS_FMT_META_24 -diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h -index 6fcd7d276bc6..d5014ef3c43a 100644 ---- a/include/uapi/linux/media-bus-format.h -+++ b/include/uapi/linux/media-bus-format.h -@@ -183,7 +183,15 @@ - #define MEDIA_BUS_FMT_META_20 0x8006 - #define MEDIA_BUS_FMT_META_24 0x8007 - -+/* Specific metadata formats. Next is 0x9008. */ -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_8 0x9001 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_10 0x9002 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_12 0x9003 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_14 0x9004 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_16 0x9005 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_20 0x9006 -+#define MEDIA_BUS_FMT_CCS_EMBEDDED_24 0x9007 -+ - #define MEDIA_BUS_FMT_IS_META(code) \ - ((code) & 0xf000 == 0x7000 || (code) & 0xf000 == 0x8000) -- - #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ --- -2.43.2 - - -From 44f8084f055969874d2216ba4e6e225046931e73 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:15 +0800 -Subject: [PATCH 08/33] media: intel/ipu6: add Intel IPU6 PCI device driver - -Intel Image Processing Unit 6th Gen includes input and processing systems -but the hardware presents itself as a single PCI device in system. - -IPU6 PCI device driver basically does PCI configurations and load -the firmware binary, initialises IPU virtual bus, and sets up platform -specific variants to support multiple IPU6 devices in single device -driver. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - .../media/pci/intel/ipu6/ipu6-platform-regs.h | 179 ++++ - drivers/media/pci/intel/ipu6/ipu6.c | 966 ++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6.h | 356 +++++++ - 3 files changed, 1501 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-regs.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h -new file mode 100644 -index 000000000000..cae26009c9fc ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h -@@ -0,0 +1,179 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2018 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_PLATFORM_REGS_H -+#define IPU6_PLATFORM_REGS_H -+ -+#include <linux/bits.h> -+ -+/* -+ * IPU6 uses uniform address within IPU6, therefore all subsystem registers -+ * locates in one single space starts from 0 but in different sctions with -+ * different addresses, the subsystem offsets are defined to 0 as the -+ * register definition will have the address offset to 0. -+ */ -+#define IPU6_UNIFIED_OFFSET 0 -+ -+#define IPU6_ISYS_IOMMU0_OFFSET 0x2e0000 -+#define IPU6_ISYS_IOMMU1_OFFSET 0x2e0500 -+#define IPU6_ISYS_IOMMUI_OFFSET 0x2e0a00 -+ -+#define IPU6_PSYS_IOMMU0_OFFSET 0x1b0000 -+#define IPU6_PSYS_IOMMU1_OFFSET 0x1b0700 -+#define IPU6_PSYS_IOMMU1R_OFFSET 0x1b0e00 -+#define IPU6_PSYS_IOMMUI_OFFSET 0x1b1500 -+ -+/* the offset from IOMMU base register */ -+#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET 0x0c -+#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET 0x4c -+#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET 0x8c -+ -+#define IPU6_MMU_INFO_OFFSET 0x8 -+ -+#define IPU6_ISYS_SPC_OFFSET 0x210000 -+ -+#define IPU6SE_PSYS_SPC_OFFSET 0x110000 -+#define IPU6_PSYS_SPC_OFFSET 0x118000 -+ -+#define IPU6_ISYS_DMEM_OFFSET 0x200000 -+#define IPU6_PSYS_DMEM_OFFSET 0x100000 -+ -+#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE 0x27c000 -+#define IPU6_REG_ISYS_UNISPART_IRQ_MASK 0x27c004 -+#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS 0x27c008 -+#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR 0x27c00c -+#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE 0x27c010 -+#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE 0x27c014 -+#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG 0x27c414 -+#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG 0x27c418 -+#define IPU6_ISYS_UNISPART_IRQ_CSI0 BIT(2) -+#define IPU6_ISYS_UNISPART_IRQ_CSI1 BIT(3) -+#define IPU6_ISYS_UNISPART_IRQ_SW BIT(22) -+ -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE 0x2b0200 -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK 0x2b0204 -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS 0x2b0208 -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR 0x2b020c -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE 0x2b0210 -+#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE 0x2b0214 -+ -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE 0x2d2100 -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK 0x2d2104 -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS 0x2d2108 -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR 0x2d210c -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE 0x2d2110 -+#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE 0x2d2114 -+ -+/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */ -+#define IPU6_REG_ISYS_CDC_THRESHOLD(i) (0x27c400 + ((i) * 4)) -+ -+#define IPU6_CSI_IRQ_NUM_PER_PIPE 4 -+#define IPU6SE_ISYS_CSI_PORT_NUM 4 -+#define IPU6_ISYS_CSI_PORT_NUM 8 -+ -+#define IPU6_ISYS_CSI_PORT_IRQ(irq_num) BIT(irq_num) -+ -+/* PKG DIR OFFSET in IMR in secure mode */ -+#define IPU6_PKG_DIR_IMR_OFFSET 0x40 -+ -+#define IPU6_ISYS_REG_SPC_STATUS_CTRL 0x0 -+ -+#define IPU6_ISYS_SPC_STATUS_START BIT(1) -+#define IPU6_ISYS_SPC_STATUS_RUN BIT(3) -+#define IPU6_ISYS_SPC_STATUS_READY BIT(5) -+#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) -+#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) -+ -+#define IPU6_PSYS_REG_SPC_STATUS_CTRL 0x0 -+#define IPU6_PSYS_REG_SPC_START_PC 0x4 -+#define IPU6_PSYS_REG_SPC_ICACHE_BASE 0x10 -+#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14 -+ -+#define IPU6_PSYS_SPC_STATUS_START BIT(1) -+#define IPU6_PSYS_SPC_STATUS_RUN BIT(3) -+#define IPU6_PSYS_SPC_STATUS_READY BIT(5) -+#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) -+#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) -+ -+#define IPU6_PSYS_REG_SPP0_STATUS_CTRL 0x20000 -+ -+#define IPU6_INFO_ENABLE_SNOOP BIT(0) -+#define IPU6_INFO_DEC_FORCE_FLUSH BIT(1) -+#define IPU6_INFO_DEC_PASS_THROUGH BIT(2) -+#define IPU6_INFO_ZLW BIT(3) -+#define IPU6_INFO_REQUEST_DESTINATION_IOSF BIT(9) -+#define IPU6_INFO_IMR_BASE BIT(10) -+#define IPU6_INFO_IMR_DESTINED BIT(11) -+ -+#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF -+ -+/* -+ * s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the -+ * pixel s2m remp ability.Remap here means that s2m rearange the order -+ * of the pixels in each 4 pixels group. -+ * For examle, mirroring remping means that if input's 4 first pixels -+ * are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels. -+ * 0xE4 is from s2m MAS document. It means no remapping. -+ */ -+#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 -+/* -+ * csi_be_soc_pixel_remapping is for the enabling of the pixel remapping. -+ * This remapping is exactly like the stream2mmio remapping. -+ */ -+#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 -+ -+#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000 -+#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000 -+#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc) -+#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc) -+#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc) -+ -+enum ipu6_device_ab_group1_target_id { -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER, -+ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB, -+}; -+ -+enum nci_ab_access_mode { -+ NCI_AB_ACCESS_MODE_RW, /* read & write */ -+ NCI_AB_ACCESS_MODE_RO, /* read only */ -+ NCI_AB_ACCESS_MODE_WO, /* write only */ -+ NCI_AB_ACCESS_MODE_NA, /* No access at all */ -+}; -+ -+/* IRQ-related registers in PSYS */ -+#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200 -+#define IPU6_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204 -+#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208 -+#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c -+#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210 -+#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214 -+/* There are 8 FW interrupts, n = 0..7 */ -+#define IPU6_PSYS_GPDEV_FWIRQ0 5 -+#define IPU6_PSYS_GPDEV_FWIRQ1 6 -+#define IPU6_PSYS_GPDEV_FWIRQ2 7 -+#define IPU6_PSYS_GPDEV_FWIRQ3 8 -+#define IPU6_PSYS_GPDEV_FWIRQ4 9 -+#define IPU6_PSYS_GPDEV_FWIRQ5 10 -+#define IPU6_PSYS_GPDEV_FWIRQ6 11 -+#define IPU6_PSYS_GPDEV_FWIRQ7 12 -+#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n) BIT(n) -+#define IPU6_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100) -+ -+#endif /* IPU6_PLATFORM_REGS_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c -new file mode 100644 -index 000000000000..abd40a783729 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6.c -@@ -0,0 +1,966 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/dma-mapping.h> -+#include <linux/err.h> -+#include <linux/firmware.h> -+#include <linux/kernel.h> -+#include <linux/interrupt.h> -+#include <linux/io.h> -+#include <linux/list.h> -+#include <linux/module.h> -+#include <linux/pci-ats.h> -+#include <linux/pm_runtime.h> -+#include <linux/property.h> -+#include <linux/scatterlist.h> -+#include <linux/slab.h> -+#include <linux/types.h> -+ -+#include <media/ipu-bridge.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-buttress.h" -+#include "ipu6-cpd.h" -+#include "ipu6-isys.h" -+#include "ipu6-mmu.h" -+#include "ipu6-platform-buttress-regs.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+#include "ipu6-platform-regs.h" -+ -+#define IPU6_PCI_BAR 0 -+ -+struct ipu6_cell_program { -+ u32 magic_number; -+ -+ u32 blob_offset; -+ u32 blob_size; -+ -+ u32 start[3]; -+ -+ u32 icache_source; -+ u32 icache_target; -+ u32 icache_size; -+ -+ u32 pmem_source; -+ u32 pmem_target; -+ u32 pmem_size; -+ -+ u32 data_source; -+ u32 data_target; -+ u32 data_size; -+ -+ u32 bss_target; -+ u32 bss_size; -+ -+ u32 cell_id; -+ u32 regs_addr; -+ -+ u32 cell_pmem_data_bus_address; -+ u32 cell_dmem_data_bus_address; -+ u32 cell_pmem_control_bus_address; -+ u32 cell_dmem_control_bus_address; -+ -+ u32 next; -+ u32 dummy[2]; -+}; -+ -+static u32 ipu6se_csi_offsets[] = { -+ IPU6_CSI_PORT_A_ADDR_OFFSET, -+ IPU6_CSI_PORT_B_ADDR_OFFSET, -+ IPU6_CSI_PORT_C_ADDR_OFFSET, -+ IPU6_CSI_PORT_D_ADDR_OFFSET -+}; -+ -+/* -+ * IPU6 on TGL support maximum 8 csi2 ports -+ * IPU6SE on JSL and IPU6EP on ADL support maximum 4 csi2 ports -+ * IPU6EP on MTL support maximum 6 csi2 ports -+ */ -+static u32 ipu6_tgl_csi_offsets[] = { -+ IPU6_CSI_PORT_A_ADDR_OFFSET, -+ IPU6_CSI_PORT_B_ADDR_OFFSET, -+ IPU6_CSI_PORT_C_ADDR_OFFSET, -+ IPU6_CSI_PORT_D_ADDR_OFFSET, -+ IPU6_CSI_PORT_E_ADDR_OFFSET, -+ IPU6_CSI_PORT_F_ADDR_OFFSET, -+ IPU6_CSI_PORT_G_ADDR_OFFSET, -+ IPU6_CSI_PORT_H_ADDR_OFFSET -+}; -+ -+static u32 ipu6ep_mtl_csi_offsets[] = { -+ IPU6_CSI_PORT_A_ADDR_OFFSET, -+ IPU6_CSI_PORT_B_ADDR_OFFSET, -+ IPU6_CSI_PORT_C_ADDR_OFFSET, -+ IPU6_CSI_PORT_D_ADDR_OFFSET, -+ IPU6_CSI_PORT_E_ADDR_OFFSET, -+ IPU6_CSI_PORT_F_ADDR_OFFSET -+}; -+ -+static u32 ipu6_csi_offsets[] = { -+ IPU6_CSI_PORT_A_ADDR_OFFSET, -+ IPU6_CSI_PORT_B_ADDR_OFFSET, -+ IPU6_CSI_PORT_C_ADDR_OFFSET, -+ IPU6_CSI_PORT_D_ADDR_OFFSET -+}; -+ -+static struct ipu6_isys_internal_pdata isys_ipdata = { -+ .hw_variant = { -+ .offset = IPU6_UNIFIED_OFFSET, -+ .nr_mmus = 3, -+ .mmu_hw = { -+ { -+ .offset = IPU6_ISYS_IOMMU0_OFFSET, -+ .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, -+ .nr_l1streams = 16, -+ .l1_block_sz = { -+ 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, -+ 1, 1, 1, 1, 1, 1 -+ }, -+ .nr_l2streams = 16, -+ .l2_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .insert_read_before_invalidate = false, -+ .l1_stream_id_reg_offset = -+ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, -+ .l2_stream_id_reg_offset = -+ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, -+ }, -+ { -+ .offset = IPU6_ISYS_IOMMU1_OFFSET, -+ .info_bits = 0, -+ .nr_l1streams = 16, -+ .l1_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 1, 1, 4 -+ }, -+ .nr_l2streams = 16, -+ .l2_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .insert_read_before_invalidate = false, -+ .l1_stream_id_reg_offset = -+ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, -+ .l2_stream_id_reg_offset = -+ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, -+ }, -+ { -+ .offset = IPU6_ISYS_IOMMUI_OFFSET, -+ .info_bits = 0, -+ .nr_l1streams = 0, -+ .nr_l2streams = 0, -+ .insert_read_before_invalidate = false, -+ }, -+ }, -+ .cdc_fifos = 3, -+ .cdc_fifo_threshold = {6, 8, 2}, -+ .dmem_offset = IPU6_ISYS_DMEM_OFFSET, -+ .spc_offset = IPU6_ISYS_SPC_OFFSET, -+ }, -+ .isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN, -+}; -+ -+static struct ipu6_psys_internal_pdata psys_ipdata = { -+ .hw_variant = { -+ .offset = IPU6_UNIFIED_OFFSET, -+ .nr_mmus = 4, -+ .mmu_hw = { -+ { -+ .offset = IPU6_PSYS_IOMMU0_OFFSET, -+ .info_bits = -+ IPU6_INFO_REQUEST_DESTINATION_IOSF, -+ .nr_l1streams = 16, -+ .l1_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .nr_l2streams = 16, -+ .l2_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .insert_read_before_invalidate = false, -+ .l1_stream_id_reg_offset = -+ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, -+ .l2_stream_id_reg_offset = -+ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, -+ }, -+ { -+ .offset = IPU6_PSYS_IOMMU1_OFFSET, -+ .info_bits = 0, -+ .nr_l1streams = 32, -+ .l1_block_sz = { -+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 10, -+ 5, 4, 14, 6, 4, 14, 6, 4, 8, -+ 4, 2, 1, 1, 1, 1, 14 -+ }, -+ .nr_l2streams = 32, -+ .l2_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .insert_read_before_invalidate = false, -+ .l1_stream_id_reg_offset = -+ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, -+ .l2_stream_id_reg_offset = -+ IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, -+ }, -+ { -+ .offset = IPU6_PSYS_IOMMU1R_OFFSET, -+ .info_bits = 0, -+ .nr_l1streams = 16, -+ .l1_block_sz = { -+ 1, 4, 4, 4, 4, 16, 8, 4, 32, -+ 16, 16, 2, 2, 2, 1, 12 -+ }, -+ .nr_l2streams = 16, -+ .l2_block_sz = { -+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -+ 2, 2, 2, 2, 2, 2 -+ }, -+ .insert_read_before_invalidate = false, -+ .l1_stream_id_reg_offset = -+ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, -+ .l2_stream_id_reg_offset = -+ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, -+ }, -+ { -+ .offset = IPU6_PSYS_IOMMUI_OFFSET, -+ .info_bits = 0, -+ .nr_l1streams = 0, -+ .nr_l2streams = 0, -+ .insert_read_before_invalidate = false, -+ }, -+ }, -+ .dmem_offset = IPU6_PSYS_DMEM_OFFSET, -+ }, -+}; -+ -+static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { -+ .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, -+ .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, -+ .freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL, -+ .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, -+ .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK, -+ .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, -+ .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, -+}; -+ -+static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { -+ .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, -+ .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, -+ .freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL, -+ .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, -+ .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK, -+ .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, -+ .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, -+}; -+ -+static void -+ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, -+ const struct ipu6_hw_variants *hw_variant, -+ int pkg_dir_idx, void __iomem *base, -+ u64 *pkg_dir, dma_addr_t pkg_dir_vied_address) -+{ -+ struct ipu6_cell_program *prog; -+ void __iomem *spc_base; -+ u32 server_fw_addr; -+ dma_addr_t dma_addr; -+ u32 pg_offset; -+ -+ server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2)); -+ if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX) -+ dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl); -+ else -+ dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl); -+ -+ pg_offset = server_fw_addr - dma_addr; -+ prog = (struct ipu6_cell_program *)((u64)isp->cpd_fw->data + pg_offset); -+ spc_base = base + prog->regs_addr; -+ if (spc_base != (base + hw_variant->spc_offset)) -+ dev_warn(&isp->pdev->dev, -+ "SPC reg addr %p not matching value from CPD %p\n", -+ base + hw_variant->spc_offset, spc_base); -+ writel(server_fw_addr + prog->blob_offset + -+ prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE); -+ writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, -+ spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); -+ writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC); -+ writel(pkg_dir_vied_address, base + hw_variant->dmem_offset); -+} -+ -+void ipu6_configure_spc(struct ipu6_device *isp, -+ const struct ipu6_hw_variants *hw_variant, -+ int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, -+ dma_addr_t pkg_dir_dma_addr) -+{ -+ void __iomem *dmem_base = base + hw_variant->dmem_offset; -+ void __iomem *spc_regs_base = base + hw_variant->spc_offset; -+ u32 val; -+ -+ val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); -+ val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; -+ writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); -+ -+ if (isp->secure_mode) -+ writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base); -+ else -+ ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base, -+ pkg_dir, pkg_dir_dma_addr); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6); -+ -+static void ipu6_internal_pdata_init(struct ipu6_device *isp) -+{ -+ u8 hw_ver = isp->hw_ver; -+ -+ isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS; -+ isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT; -+ isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE; -+ isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE; -+ isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START; -+ isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END; -+ isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS; -+ isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES; -+ isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX; -+ isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE; -+ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_csi_offsets); -+ isys_ipdata.csi2.offsets = ipu6_csi_offsets; -+ isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK; -+ isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; -+ isys_ipdata.csi2.ctrl0_irq_clear = -+ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; -+ isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; -+ isys_ipdata.csi2.ctrl0_irq_enable = -+ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; -+ isys_ipdata.csi2.ctrl0_irq_status = -+ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; -+ isys_ipdata.csi2.ctrl0_irq_lnp = -+ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; -+ isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver); -+ psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET; -+ isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS; -+ -+ if (is_ipu6ep(hw_ver)) { -+ isys_ipdata.ltr = IPU6EP_LTR_VALUE; -+ isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH; -+ } -+ -+ if (is_ipu6_tgl(hw_ver)) { -+ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_tgl_csi_offsets); -+ isys_ipdata.csi2.offsets = ipu6_tgl_csi_offsets; -+ } -+ -+ if (is_ipu6ep_mtl(hw_ver)) { -+ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6ep_mtl_csi_offsets); -+ isys_ipdata.csi2.offsets = ipu6ep_mtl_csi_offsets; -+ -+ isys_ipdata.csi2.ctrl0_irq_edge = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; -+ isys_ipdata.csi2.ctrl0_irq_clear = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; -+ isys_ipdata.csi2.ctrl0_irq_mask = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; -+ isys_ipdata.csi2.ctrl0_irq_enable = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; -+ isys_ipdata.csi2.ctrl0_irq_lnp = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; -+ isys_ipdata.csi2.ctrl0_irq_status = -+ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; -+ isys_ipdata.csi2.fw_access_port_ofs = -+ CSI_REG_HUB_FW_ACCESS_PORT_V6OFS; -+ isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE; -+ isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH; -+ } -+ -+ if (is_ipu6se(hw_ver)) { -+ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6se_csi_offsets); -+ isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK; -+ isys_ipdata.csi2.offsets = ipu6se_csi_offsets; -+ isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS; -+ isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT; -+ isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE; -+ isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE; -+ isys_ipdata.sensor_type_start = -+ IPU6SE_FW_ISYS_SENSOR_TYPE_START; -+ isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END; -+ isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS; -+ isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES; -+ isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX; -+ isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE; -+ psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET; -+ } -+} -+ -+static int ipu6_isys_check_fwnode_graph(struct fwnode_handle *fwnode) -+{ -+ struct fwnode_handle *endpoint; -+ -+ if (IS_ERR_OR_NULL(fwnode)) -+ return -EINVAL; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (endpoint) { -+ fwnode_handle_put(endpoint); -+ return 0; -+ } -+ -+ return ipu6_isys_check_fwnode_graph(fwnode->secondary); -+} -+ -+static struct ipu6_bus_device * -+ipu6_isys_init(struct pci_dev *pdev, struct device *parent, -+ struct ipu6_buttress_ctrl *ctrl, void __iomem *base, -+ const struct ipu6_isys_internal_pdata *ipdata) -+{ -+ struct device *dev = &pdev->dev; -+ struct fwnode_handle *fwnode = dev_fwnode(dev); -+ struct ipu6_bus_device *isys_adev; -+ struct ipu6_isys_pdata *pdata; -+ int ret; -+ -+ /* check fwnode at first, fallback into bridge if no fwnode graph */ -+ ret = ipu6_isys_check_fwnode_graph(fwnode); -+ if (ret) { -+ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { -+ dev_err(dev, -+ "fwnode graph has no endpoints connection\n"); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb); -+ if (ret) { -+ dev_err_probe(dev, ret, "IPU6 bridge init failed\n"); -+ return ERR_PTR(ret); -+ } -+ } -+ -+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); -+ if (!pdata) -+ return ERR_PTR(-ENOMEM); -+ -+ pdata->base = base; -+ pdata->ipdata = ipdata; -+ -+ isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, -+ IPU6_ISYS_NAME); -+ if (IS_ERR(isys_adev)) { -+ dev_err_probe(dev, PTR_ERR(isys_adev), -+ "ipu6_bus_initialize_device isys failed\n"); -+ kfree(pdata); -+ return ERR_CAST(isys_adev); -+ } -+ -+ isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID, -+ &ipdata->hw_variant); -+ if (IS_ERR(isys_adev->mmu)) { -+ dev_err_probe(dev, PTR_ERR(isys_adev), -+ "ipu6_mmu_init(isys_adev->mmu) failed\n"); -+ put_device(&isys_adev->auxdev.dev); -+ kfree(pdata); -+ return ERR_CAST(isys_adev->mmu); -+ } -+ -+ isys_adev->mmu->dev = &isys_adev->auxdev.dev; -+ -+ ret = ipu6_bus_add_device(isys_adev); -+ if (ret) { -+ kfree(pdata); -+ return ERR_PTR(ret); -+ } -+ -+ return isys_adev; -+} -+ -+static struct ipu6_bus_device * -+ipu6_psys_init(struct pci_dev *pdev, struct device *parent, -+ struct ipu6_buttress_ctrl *ctrl, void __iomem *base, -+ const struct ipu6_psys_internal_pdata *ipdata) -+{ -+ struct ipu6_bus_device *psys_adev; -+ struct ipu6_psys_pdata *pdata; -+ int ret; -+ -+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); -+ if (!pdata) -+ return ERR_PTR(-ENOMEM); -+ -+ pdata->base = base; -+ pdata->ipdata = ipdata; -+ -+ psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, -+ IPU6_PSYS_NAME); -+ if (IS_ERR(psys_adev)) { -+ dev_err_probe(&pdev->dev, PTR_ERR(psys_adev), -+ "ipu6_bus_initialize_device psys failed\n"); -+ kfree(pdata); -+ return ERR_CAST(psys_adev); -+ } -+ -+ psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID, -+ &ipdata->hw_variant); -+ if (IS_ERR(psys_adev->mmu)) { -+ dev_err_probe(&pdev->dev, PTR_ERR(psys_adev), -+ "ipu6_mmu_init(psys_adev->mmu) failed\n"); -+ put_device(&psys_adev->auxdev.dev); -+ kfree(pdata); -+ return ERR_CAST(psys_adev->mmu); -+ } -+ -+ psys_adev->mmu->dev = &psys_adev->auxdev.dev; -+ -+ ret = ipu6_bus_add_device(psys_adev); -+ if (ret) { -+ kfree(pdata); -+ return ERR_PTR(ret); -+ } -+ -+ return psys_adev; -+} -+ -+static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) -+{ -+ int ret; -+ -+ /* disable IPU6 PCI ATS on mtl ES2 */ -+ if (is_ipu6ep_mtl(hw_ver) && boot_cpu_data.x86_stepping == 0x2 && -+ pci_ats_supported(dev)) -+ pci_disable_ats(dev); -+ -+ /* No PCI msi capability for IPU6EP */ -+ if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) { -+ /* likely do nothing as msi not enabled by default */ -+ pci_disable_msi(dev); -+ return 0; -+ } -+ -+ ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI); -+ if (ret < 0) -+ return dev_err_probe(&dev->dev, ret, "Request msi failed"); -+ -+ return 0; -+} -+ -+static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) -+{ -+ u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); -+ -+ if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL) -+ val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; -+ else -+ val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; -+ -+ if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL) -+ val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; -+ else -+ val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; -+ -+ writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL); -+} -+ -+static int request_cpd_fw(const struct firmware **firmware_p, const char *name, -+ struct device *device) -+{ -+ const struct firmware *fw; -+ struct firmware *dst; -+ int ret = 0; -+ -+ ret = request_firmware(&fw, name, device); -+ if (ret) -+ return ret; -+ -+ if (is_vmalloc_addr(fw->data)) { -+ *firmware_p = fw; -+ return 0; -+ } -+ -+ dst = kzalloc(sizeof(*dst), GFP_KERNEL); -+ if (!dst) { -+ ret = -ENOMEM; -+ goto release_firmware; -+ } -+ -+ dst->size = fw->size; -+ dst->data = vmalloc(fw->size); -+ if (!dst->data) { -+ kfree(dst); -+ ret = -ENOMEM; -+ goto release_firmware; -+ } -+ -+ memcpy((void *)dst->data, fw->data, fw->size); -+ *firmware_p = dst; -+ -+release_firmware: -+ release_firmware(fw); -+ -+ return ret; -+} -+ -+static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) -+{ -+ struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; -+ struct device *dev = &pdev->dev; -+ void __iomem *isys_base = NULL; -+ void __iomem *psys_base = NULL; -+ struct ipu6_device *isp; -+ phys_addr_t phys; -+ u32 val, version, sku_id; -+ int ret; -+ -+ isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); -+ if (!isp) -+ return -ENOMEM; -+ -+ isp->pdev = pdev; -+ INIT_LIST_HEAD(&isp->devices); -+ -+ ret = pcim_enable_device(pdev); -+ if (ret) -+ return dev_err_probe(dev, ret, "Enable PCI device failed\n"); -+ -+ phys = pci_resource_start(pdev, IPU6_PCI_BAR); -+ dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); -+ -+ ret = pcim_iomap_regions(pdev, 1 << IPU6_PCI_BAR, pci_name(pdev)); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to I/O mem remappinp\n"); -+ -+ isp->base = pcim_iomap_table(pdev)[IPU6_PCI_BAR]; -+ pci_set_drvdata(pdev, isp); -+ pci_set_master(pdev); -+ -+ isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); -+ switch (id->device) { -+ case PCI_DEVICE_ID_INTEL_IPU6: -+ isp->hw_ver = IPU6_VER_6; -+ isp->cpd_fw_name = IPU6_FIRMWARE_NAME; -+ break; -+ case PCI_DEVICE_ID_INTEL_IPU6SE: -+ isp->hw_ver = IPU6_VER_6SE; -+ isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME; -+ isp->cpd_metadata_cmpnt_size = -+ sizeof(struct ipu6se_cpd_metadata_cmpnt); -+ break; -+ case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP: -+ case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN: -+ case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP: -+ isp->hw_ver = IPU6_VER_6EP; -+ isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME; -+ break; -+ case PCI_DEVICE_ID_INTEL_IPU6EP_MTL: -+ isp->hw_ver = IPU6_VER_6EP_MTL; -+ isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; -+ break; -+ default: -+ return dev_err_probe(dev, -ENODEV, -+ "Unsupported IPU6 device %x\n", -+ id->device); -+ } -+ -+ ipu6_internal_pdata_init(isp); -+ -+ isys_base = isp->base + isys_ipdata.hw_variant.offset; -+ psys_base = isp->base + psys_ipdata.hw_variant.offset; -+ -+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39)); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); -+ -+ ret = dma_set_max_seg_size(dev, UINT_MAX); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to set max_seg_size\n"); -+ -+ ret = ipu6_pci_config_setup(pdev, isp->hw_ver); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_buttress_init(isp); -+ if (ret) -+ return ret; -+ -+ ret = request_cpd_fw(&isp->cpd_fw, isp->cpd_fw_name, dev); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, -+ "Requesting signed firmware %s failed\n", -+ isp->cpd_fw_name); -+ goto buttress_exit; -+ } -+ -+ ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data, -+ isp->cpd_fw->size); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, -+ "Failed to validate cpd\n"); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl, -+ sizeof(isys_buttress_ctrl), GFP_KERNEL); -+ if (!isys_ctrl) { -+ ret = -ENOMEM; -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base, -+ &isys_ipdata); -+ if (IS_ERR(isp->isys)) { -+ ret = PTR_ERR(isp->isys); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl, -+ sizeof(psys_buttress_ctrl), GFP_KERNEL); -+ if (!psys_ctrl) { -+ ret = -ENOMEM; -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl, -+ psys_base, &psys_ipdata); -+ if (IS_ERR(isp->psys)) { -+ ret = PTR_ERR(isp->psys); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); -+ if (ret < 0) -+ goto out_ipu6_bus_del_devices; -+ -+ ret = ipu6_mmu_hw_init(isp->psys->mmu); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, -+ "Failed to set MMU hardware\n"); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, -+ &isp->psys->fw_sgt); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, -+ "failed to create pkg dir\n"); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, -+ ipu6_buttress_isr_threaded, -+ IRQF_SHARED, IPU6_NAME, isp); -+ if (ret) { -+ dev_err_probe(dev, ret, "Requesting irq failed\n"); -+ goto out_ipu6_bus_del_devices; -+ } -+ -+ ret = ipu6_buttress_authenticate(isp); -+ if (ret) { -+ dev_err_probe(&isp->pdev->dev, ret, -+ "FW authentication failed\n"); -+ goto out_free_irq; -+ } -+ -+ ipu6_mmu_hw_cleanup(isp->psys->mmu); -+ pm_runtime_put(&isp->psys->auxdev.dev); -+ -+ /* Configure the arbitration mechanisms for VC requests */ -+ ipu6_configure_vc_mechanism(isp); -+ -+ val = readl(isp->base + BUTTRESS_REG_SKU); -+ sku_id = FIELD_GET(GENMASK(6, 4), val); -+ version = FIELD_GET(GENMASK(3, 0), val); -+ dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id, -+ pdev->device, isp->hw_ver); -+ -+ pm_runtime_put_noidle(dev); -+ pm_runtime_allow(dev); -+ -+ isp->bus_ready_to_probe = true; -+ -+ return 0; -+ -+out_free_irq: -+ devm_free_irq(dev, pdev->irq, isp); -+out_ipu6_bus_del_devices: -+ if (isp->psys) { -+ ipu6_cpd_free_pkg_dir(isp->psys); -+ ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); -+ } -+ if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) -+ ipu6_mmu_cleanup(isp->psys->mmu); -+ if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) -+ ipu6_mmu_cleanup(isp->isys->mmu); -+ ipu6_bus_del_devices(pdev); -+ release_firmware(isp->cpd_fw); -+buttress_exit: -+ ipu6_buttress_exit(isp); -+ -+ return ret; -+} -+ -+static void ipu6_pci_remove(struct pci_dev *pdev) -+{ -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ struct ipu6_mmu *isys_mmu = isp->isys->mmu; -+ struct ipu6_mmu *psys_mmu = isp->psys->mmu; -+ -+ devm_free_irq(&pdev->dev, pdev->irq, isp); -+ ipu6_cpd_free_pkg_dir(isp->psys); -+ -+ ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); -+ ipu6_buttress_exit(isp); -+ -+ ipu6_bus_del_devices(pdev); -+ -+ pm_runtime_forbid(&pdev->dev); -+ pm_runtime_get_noresume(&pdev->dev); -+ -+ pci_release_regions(pdev); -+ pci_disable_device(pdev); -+ -+ release_firmware(isp->cpd_fw); -+ -+ ipu6_mmu_cleanup(psys_mmu); -+ ipu6_mmu_cleanup(isys_mmu); -+} -+ -+static void ipu6_pci_reset_prepare(struct pci_dev *pdev) -+{ -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ -+ pm_runtime_forbid(&isp->pdev->dev); -+} -+ -+static void ipu6_pci_reset_done(struct pci_dev *pdev) -+{ -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ -+ ipu6_buttress_restore(isp); -+ if (isp->secure_mode) -+ ipu6_buttress_reset_authentication(isp); -+ -+ isp->need_ipc_reset = true; -+ pm_runtime_allow(&isp->pdev->dev); -+} -+ -+/* -+ * PCI base driver code requires driver to provide these to enable -+ * PCI device level PM state transitions (D0<->D3) -+ */ -+static int ipu6_suspend(struct device *dev) -+{ -+ return 0; -+} -+ -+static int ipu6_resume(struct device *dev) -+{ -+ struct pci_dev *pdev = to_pci_dev(dev); -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ struct ipu6_buttress *b = &isp->buttress; -+ int ret; -+ -+ /* Configure the arbitration mechanisms for VC requests */ -+ ipu6_configure_vc_mechanism(isp); -+ -+ isp->secure_mode = ipu6_buttress_get_secure_mode(isp); -+ dev_info(dev, "IPU6 in %s mode\n", -+ isp->secure_mode ? "secure" : "non-secure"); -+ -+ ipu6_buttress_restore(isp); -+ -+ ret = ipu6_buttress_ipc_reset(isp, &b->cse); -+ if (ret) -+ dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); -+ -+ ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); -+ if (ret < 0) { -+ dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n"); -+ return 0; -+ } -+ -+ ret = ipu6_buttress_authenticate(isp); -+ if (ret) -+ dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret); -+ -+ pm_runtime_put(&isp->psys->auxdev.dev); -+ -+ return 0; -+} -+ -+static int ipu6_runtime_resume(struct device *dev) -+{ -+ struct pci_dev *pdev = to_pci_dev(dev); -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ int ret; -+ -+ ipu6_configure_vc_mechanism(isp); -+ ipu6_buttress_restore(isp); -+ -+ if (isp->need_ipc_reset) { -+ struct ipu6_buttress *b = &isp->buttress; -+ -+ isp->need_ipc_reset = false; -+ ret = ipu6_buttress_ipc_reset(isp, &b->cse); -+ if (ret) -+ dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); -+ } -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ipu6_pm_ops = { -+ SET_SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume) -+ SET_RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL) -+}; -+ -+static const struct pci_device_id ipu6_pci_tbl[] = { -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6) }, -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6SE) }, -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLP) }, -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLN) }, -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_RPLP) }, -+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_MTL) }, -+ { } -+}; -+MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl); -+ -+static const struct pci_error_handlers pci_err_handlers = { -+ .reset_prepare = ipu6_pci_reset_prepare, -+ .reset_done = ipu6_pci_reset_done, -+}; -+ -+static struct pci_driver ipu6_pci_driver = { -+ .name = IPU6_NAME, -+ .id_table = ipu6_pci_tbl, -+ .probe = ipu6_pci_probe, -+ .remove = ipu6_pci_remove, -+ .driver = { -+ .pm = pm_ptr(&ipu6_pm_ops), -+ }, -+ .err_handler = &pci_err_handlers, -+}; -+ -+module_pci_driver(ipu6_pci_driver); -+ -+MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); -+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); -+MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); -+MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); -+MODULE_AUTHOR("Qingwu Zhang <qingwu.zhang@intel.com>"); -+MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>"); -+MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>"); -+MODULE_LICENSE("GPL"); -+MODULE_DESCRIPTION("Intel IPU6 PCI driver"); -diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h -new file mode 100644 -index 000000000000..04e7e7e61ca5 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6.h -@@ -0,0 +1,356 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_H -+#define IPU6_H -+ -+#include <linux/list.h> -+#include <linux/pci.h> -+#include <linux/types.h> -+ -+#include "ipu6-buttress.h" -+ -+struct firmware; -+struct pci_dev; -+struct ipu6_bus_device; -+ -+#define PCI_DEVICE_ID_INTEL_IPU6 0x9a19 -+#define PCI_DEVICE_ID_INTEL_IPU6SE 0x4e19 -+#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLP 0x465d -+#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLN 0x462e -+#define PCI_DEVICE_ID_INTEL_IPU6EP_RPLP 0xa75d -+#define PCI_DEVICE_ID_INTEL_IPU6EP_MTL 0x7d19 -+ -+#define IPU6_NAME "intel-ipu6" -+#define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" -+ -+#define IPU6SE_FIRMWARE_NAME "intel/ipu6se_fw.bin" -+#define IPU6EP_FIRMWARE_NAME "intel/ipu6ep_fw.bin" -+#define IPU6_FIRMWARE_NAME "intel/ipu6_fw.bin" -+#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu6epmtl_fw.bin" -+ -+enum ipu6_version { -+ IPU6_VER_INVALID = 0, -+ IPU6_VER_6 = 1, -+ IPU6_VER_6SE = 3, -+ IPU6_VER_6EP = 5, -+ IPU6_VER_6EP_MTL = 6, -+}; -+ -+/* -+ * IPU6 - TGL -+ * IPU6SE - JSL -+ * IPU6EP - ADL/RPL -+ * IPU6EP_MTL - MTL -+ */ -+static inline bool is_ipu6se(u8 hw_ver) -+{ -+ return hw_ver == IPU6_VER_6SE; -+} -+ -+static inline bool is_ipu6ep(u8 hw_ver) -+{ -+ return hw_ver == IPU6_VER_6EP; -+} -+ -+static inline bool is_ipu6ep_mtl(u8 hw_ver) -+{ -+ return hw_ver == IPU6_VER_6EP_MTL; -+} -+ -+static inline bool is_ipu6_tgl(u8 hw_ver) -+{ -+ return hw_ver == IPU6_VER_6; -+} -+ -+/* -+ * ISYS DMA can overshoot. For higher resolutions over allocation is one line -+ * but it must be at minimum 1024 bytes. Value could be different in -+ * different versions / generations thus provide it via platform data. -+ */ -+#define IPU6_ISYS_OVERALLOC_MIN 1024 -+ -+/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */ -+#define IPU6_DEVICE_GDA_NR_PAGES 128 -+ -+/* Virtualization factor to calculate the available virtual pages */ -+#define IPU6_DEVICE_GDA_VIRT_FACTOR 32 -+ -+#define NR_OF_MMU_RESOURCES 2 -+ -+struct ipu6_device { -+ struct pci_dev *pdev; -+ struct list_head devices; -+ struct ipu6_bus_device *isys; -+ struct ipu6_bus_device *psys; -+ struct ipu6_buttress buttress; -+ -+ const struct firmware *cpd_fw; -+ const char *cpd_fw_name; -+ u32 cpd_metadata_cmpnt_size; -+ -+ void __iomem *base; -+ bool need_ipc_reset; -+ bool secure_mode; -+ u8 hw_ver; -+ bool bus_ready_to_probe; -+}; -+ -+#define IPU6_ISYS_NAME "isys" -+#define IPU6_PSYS_NAME "psys" -+ -+#define IPU6_MMU_MAX_DEVICES 4 -+#define IPU6_MMU_ADDR_BITS 32 -+/* The firmware is accessible within the first 2 GiB only in non-secure mode. */ -+#define IPU6_MMU_ADDR_BITS_NON_SECURE 31 -+ -+#define IPU6_MMU_MAX_TLB_L1_STREAMS 32 -+#define IPU6_MMU_MAX_TLB_L2_STREAMS 32 -+#define IPU6_MAX_LI_BLOCK_ADDR 128 -+#define IPU6_MAX_L2_BLOCK_ADDR 64 -+ -+#define IPU6_ISYS_MAX_CSI2_LEGACY_PORTS 4 -+#define IPU6_ISYS_MAX_CSI2_COMBO_PORTS 2 -+ -+#define IPU6_MAX_FRAME_COUNTER 0xff -+ -+#define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX -+#define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX -+ -+/* -+ * To maximize the IOSF utlization, IPU6 need to send requests in bursts. -+ * At the DMA interface with the buttress, there are CDC FIFOs with burst -+ * collection capability. CDC FIFO burst collectors have a configurable -+ * threshold and is configured based on the outcome of performance measurements. -+ * -+ * isys has 3 ports with IOSF interface for VC0, VC1 and VC2 -+ * psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2 -+ * -+ * Threshold values are pre-defined and are arrived at after performance -+ * evaluations on a type of IPU6 -+ */ -+#define IPU6_MAX_VC_IOSF_PORTS 4 -+ -+/* -+ * IPU6 must configure correct arbitration mechanism related to the IOSF VC -+ * requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on -+ * stall and 1 means stall until the request is completed. -+ */ -+#define IPU6_BTRS_ARB_MODE_TYPE_REARB 0 -+#define IPU6_BTRS_ARB_MODE_TYPE_STALL 1 -+ -+/* Currently chosen arbitration mechanism for VC0 */ -+#define IPU6_BTRS_ARB_STALL_MODE_VC0 \ -+ IPU6_BTRS_ARB_MODE_TYPE_REARB -+ -+/* Currently chosen arbitration mechanism for VC1 */ -+#define IPU6_BTRS_ARB_STALL_MODE_VC1 \ -+ IPU6_BTRS_ARB_MODE_TYPE_REARB -+ -+/* -+ * MMU Invalidation HW bug workaround by ZLW mechanism -+ * -+ * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in -+ * wrong translation or replication of the translation. This will cause data -+ * corruption. So we cannot directly use the MMU V2 invalidation registers -+ * to invalidate the MMU. Instead, whenever an invalidate is called, we need to -+ * clear the TLB by evicting all the valid translations by filling it with trash -+ * buffer (which is guaranteed not to be used by any other processes). ZLW is -+ * used to fill the L1 and L2 caches with the trash buffer translations. ZLW -+ * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in -+ * advance to the L1 and L2 caches without triggering any memory operations. -+ * -+ * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream -+ * One L1 block has 16 entries, hence points to 16 * 4K pages -+ * L2 -> 16 streams and 32 blocks. 2 blocks per streams -+ * One L2 block maps to 1024 L1 entries, hence points to 4MB address range -+ * 2 blocks per L2 stream means, 1 stream points to 8MB range -+ * -+ * As we need to clear the caches and 8MB being the biggest cache size, we need -+ * to have trash buffer which points to 8MB address range. As these trash -+ * buffers are not used for any memory transactions, we need only the least -+ * amount of physical memory. So we reserve 8MB IOVA address range but only -+ * one page is reserved from physical memory. Each of this 8MB IOVA address -+ * range is then mapped to the same physical memory page. -+ */ -+/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ -+#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) -+/* Max L2 blocks per stream */ -+#define IPU6_MMUV2_MAX_L2_BLOCKS 2 -+/* Max L1 blocks per stream */ -+#define IPU6_MMUV2_MAX_L1_BLOCKS 16 -+#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) -+/* Entries per L1 block */ -+#define MMUV2_ENTRIES_PER_L1_BLOCK 16 -+#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) -+#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE -+ -+/* -+ * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page -+ * table caches. Both these L1 and L2 caches are divided into multiple sections -+ * called streams. There is maximum 16 streams for both caches. Each of these -+ * sections are subdivided into multiple blocks. When nr_l1streams = 0 and -+ * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support -+ * L1/L2 page table caches. -+ * -+ * L1 stream per block sizes are configurable and varies per usecase. -+ * L2 has constant block sizes - 2 blocks per stream. -+ * -+ * MMU1 support pre-fetching of the pages to have less cache lookup misses. To -+ * enable the pre-fetching, MMU1 AT (Address Translator) device registers -+ * need to be configured. -+ * -+ * There are four types of memory accesses which requires ZLW configuration. -+ * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. -+ * -+ * 1. Sequential Access or 1D mode -+ * Set ZLW_EN -> 1 -+ * set ZLW_PAGE_CROSS_1D -> 1 -+ * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where -+ * N is pre-defined and hardcoded in the platform data -+ * Set ZLW_2D -> 0 -+ * -+ * 2. ZLW 2D mode -+ * Set ZLW_EN -> 1 -+ * set ZLW_PAGE_CROSS_1D -> 1, -+ * Set ZLW_N -> 0 -+ * Set ZLW_2D -> 1 -+ * -+ * 3. ZLW Enable (no 1D or 2D mode) -+ * Set ZLW_EN -> 1 -+ * set ZLW_PAGE_CROSS_1D -> 0, -+ * Set ZLW_N -> 0 -+ * Set ZLW_2D -> 0 -+ * -+ * 4. ZLW disable -+ * Set ZLW_EN -> 0 -+ * set ZLW_PAGE_CROSS_1D -> 0, -+ * Set ZLW_N -> 0 -+ * Set ZLW_2D -> 0 -+ * -+ * To configure the ZLW for the above memory access, four registers are -+ * available. Hence to track these four settings, we have the following entries -+ * in the struct ipu6_mmu_hw. Each of these entries are per stream and -+ * available only for the L1 streams. -+ * -+ * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) -+ * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary -+ * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted -+ * Insert ZLW request N pages ahead address. -+ * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) -+ * -+ * -+ * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined -+ * as per the usecase specific calculations. Any change to this pre-defined -+ * table has to happen in sync with IPU6 FW. -+ */ -+struct ipu6_mmu_hw { -+ union { -+ unsigned long offset; -+ void __iomem *base; -+ }; -+ u32 info_bits; -+ u8 nr_l1streams; -+ /* -+ * L1 has variable blocks per stream - total of 64 blocks and maximum of -+ * 16 blocks per stream. Configurable by using the block start address -+ * per stream. Block start address is calculated from the block size -+ */ -+ u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; -+ /* Is ZLW is enabled in each stream */ -+ bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; -+ bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; -+ u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; -+ bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; -+ -+ u32 l1_stream_id_reg_offset; -+ u32 l2_stream_id_reg_offset; -+ -+ u8 nr_l2streams; -+ /* -+ * L2 has fixed 2 blocks per stream. Block address is calculated -+ * from the block size -+ */ -+ u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; -+ /* flag to track if WA is needed for successive invalidate HW bug */ -+ bool insert_read_before_invalidate; -+}; -+ -+struct ipu6_mmu_pdata { -+ u32 nr_mmus; -+ struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; -+ int mmid; -+}; -+ -+struct ipu6_isys_csi2_pdata { -+ void __iomem *base; -+}; -+ -+struct ipu6_isys_internal_csi2_pdata { -+ u32 nports; -+ u32 irq_mask; -+ u32 *offsets; -+ u32 ctrl0_irq_edge; -+ u32 ctrl0_irq_clear; -+ u32 ctrl0_irq_mask; -+ u32 ctrl0_irq_enable; -+ u32 ctrl0_irq_lnp; -+ u32 ctrl0_irq_status; -+ u32 fw_access_port_ofs; -+}; -+ -+struct ipu6_isys_internal_tpg_pdata { -+ u32 ntpgs; -+ u32 *offsets; -+ u32 *sels; -+}; -+ -+struct ipu6_hw_variants { -+ unsigned long offset; -+ u32 nr_mmus; -+ struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; -+ u8 cdc_fifos; -+ u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS]; -+ u32 dmem_offset; -+ u32 spc_offset; -+}; -+ -+struct ipu6_isys_internal_pdata { -+ struct ipu6_isys_internal_csi2_pdata csi2; -+ struct ipu6_hw_variants hw_variant; -+ u32 num_parallel_streams; -+ u32 isys_dma_overshoot; -+ u32 sram_gran_shift; -+ u32 sram_gran_size; -+ u32 max_sram_size; -+ u32 max_streams; -+ u32 max_send_queues; -+ u32 max_sram_blocks; -+ u32 max_devq_size; -+ u32 sensor_type_start; -+ u32 sensor_type_end; -+ u32 ltr; -+ u32 memopen_threshold; -+ bool enhanced_iwake; -+}; -+ -+struct ipu6_isys_pdata { -+ void __iomem *base; -+ const struct ipu6_isys_internal_pdata *ipdata; -+}; -+ -+struct ipu6_psys_internal_pdata { -+ struct ipu6_hw_variants hw_variant; -+}; -+ -+struct ipu6_psys_pdata { -+ void __iomem *base; -+ const struct ipu6_psys_internal_pdata *ipdata; -+}; -+ -+int ipu6_fw_authenticate(void *data, u64 val); -+void ipu6_configure_spc(struct ipu6_device *isp, -+ const struct ipu6_hw_variants *hw_variant, -+ int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, -+ dma_addr_t pkg_dir_dma_addr); -+#endif /* IPU6_H */ --- -2.43.2 - - -From f52c1b80222269f99d52b0af5937995e22c9ed6d Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:16 +0800 -Subject: [PATCH 09/33] media: intel/ipu6: add IPU auxiliary devices - -Even the IPU input system and processing system are in a single PCI -device, each system has its own power sequence, the processing system -power up depends on the input system power up. - -Besides, input system and processing system have their own MMU -hardware for IPU DMA address mapping. - -Register the IS/PS devices on auxiliary bus and attach power domain -to implement the power sequence dependency. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-bus.c | 165 ++++++++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-bus.h | 58 +++++++++ - 2 files changed, 223 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c -new file mode 100644 -index 000000000000..e81b9a6518a1 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c -@@ -0,0 +1,165 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/auxiliary_bus.h> -+#include <linux/device.h> -+#include <linux/dma-mapping.h> -+#include <linux/err.h> -+#include <linux/list.h> -+#include <linux/mutex.h> -+#include <linux/pci.h> -+#include <linux/pm_domain.h> -+#include <linux/pm_runtime.h> -+#include <linux/slab.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-buttress.h" -+#include "ipu6-dma.h" -+ -+static int bus_pm_runtime_suspend(struct device *dev) -+{ -+ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); -+ int ret; -+ -+ ret = pm_generic_runtime_suspend(dev); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_buttress_power(dev, adev->ctrl, false); -+ if (!ret) -+ return 0; -+ -+ dev_err(dev, "power down failed!\n"); -+ -+ /* Powering down failed, attempt to resume device now */ -+ ret = pm_generic_runtime_resume(dev); -+ if (!ret) -+ return -EBUSY; -+ -+ return -EIO; -+} -+ -+static int bus_pm_runtime_resume(struct device *dev) -+{ -+ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); -+ int ret; -+ -+ ret = ipu6_buttress_power(dev, adev->ctrl, true); -+ if (ret) -+ return ret; -+ -+ ret = pm_generic_runtime_resume(dev); -+ if (ret) -+ goto out_err; -+ -+ return 0; -+ -+out_err: -+ ipu6_buttress_power(dev, adev->ctrl, false); -+ -+ return -EBUSY; -+} -+ -+static struct dev_pm_domain ipu6_bus_pm_domain = { -+ .ops = { -+ .runtime_suspend = bus_pm_runtime_suspend, -+ .runtime_resume = bus_pm_runtime_resume, -+ }, -+}; -+ -+static DEFINE_MUTEX(ipu6_bus_mutex); -+ -+static void ipu6_bus_release(struct device *dev) -+{ -+ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); -+ -+ kfree(adev->pdata); -+ kfree(adev); -+} -+ -+struct ipu6_bus_device * -+ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, -+ void *pdata, struct ipu6_buttress_ctrl *ctrl, -+ char *name) -+{ -+ struct auxiliary_device *auxdev; -+ struct ipu6_bus_device *adev; -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ int ret; -+ -+ adev = kzalloc(sizeof(*adev), GFP_KERNEL); -+ if (!adev) -+ return ERR_PTR(-ENOMEM); -+ -+ adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : -+ IPU6_MMU_ADDR_BITS_NON_SECURE); -+ adev->isp = isp; -+ adev->ctrl = ctrl; -+ adev->pdata = pdata; -+ auxdev = &adev->auxdev; -+ auxdev->name = name; -+ auxdev->id = (pci_domain_nr(pdev->bus) << 16) | -+ PCI_DEVID(pdev->bus->number, pdev->devfn); -+ -+ auxdev->dev.parent = parent; -+ auxdev->dev.release = ipu6_bus_release; -+ auxdev->dev.dma_ops = &ipu6_dma_ops; -+ auxdev->dev.dma_mask = &adev->dma_mask; -+ auxdev->dev.dma_parms = pdev->dev.dma_parms; -+ auxdev->dev.coherent_dma_mask = adev->dma_mask; -+ -+ ret = auxiliary_device_init(auxdev); -+ if (ret < 0) { -+ dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n", -+ ret); -+ kfree(adev); -+ return ERR_PTR(ret); -+ } -+ -+ dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain); -+ -+ pm_runtime_forbid(&adev->auxdev.dev); -+ pm_runtime_enable(&adev->auxdev.dev); -+ -+ return adev; -+} -+ -+int ipu6_bus_add_device(struct ipu6_bus_device *adev) -+{ -+ struct auxiliary_device *auxdev = &adev->auxdev; -+ int ret; -+ -+ ret = auxiliary_device_add(auxdev); -+ if (ret) { -+ auxiliary_device_uninit(auxdev); -+ return ret; -+ } -+ -+ mutex_lock(&ipu6_bus_mutex); -+ list_add(&adev->list, &adev->isp->devices); -+ mutex_unlock(&ipu6_bus_mutex); -+ -+ pm_runtime_allow(&auxdev->dev); -+ -+ return 0; -+} -+ -+void ipu6_bus_del_devices(struct pci_dev *pdev) -+{ -+ struct ipu6_device *isp = pci_get_drvdata(pdev); -+ struct ipu6_bus_device *adev, *save; -+ -+ mutex_lock(&ipu6_bus_mutex); -+ -+ list_for_each_entry_safe(adev, save, &isp->devices, list) { -+ pm_runtime_disable(&adev->auxdev.dev); -+ list_del(&adev->list); -+ auxiliary_device_delete(&adev->auxdev); -+ auxiliary_device_uninit(&adev->auxdev); -+ } -+ -+ mutex_unlock(&ipu6_bus_mutex); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h -new file mode 100644 -index 000000000000..d46181354836 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h -@@ -0,0 +1,58 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_BUS_H -+#define IPU6_BUS_H -+ -+#include <linux/auxiliary_bus.h> -+#include <linux/container_of.h> -+#include <linux/device.h> -+#include <linux/irqreturn.h> -+#include <linux/list.h> -+#include <linux/scatterlist.h> -+#include <linux/types.h> -+ -+struct firmware; -+struct pci_dev; -+ -+#define IPU6_BUS_NAME IPU6_NAME "-bus" -+ -+struct ipu6_buttress_ctrl; -+ -+struct ipu6_bus_device { -+ struct auxiliary_device auxdev; -+ struct auxiliary_driver *auxdrv; -+ const struct ipu6_auxdrv_data *auxdrv_data; -+ struct list_head list; -+ void *pdata; -+ struct ipu6_mmu *mmu; -+ struct ipu6_device *isp; -+ struct ipu6_buttress_ctrl *ctrl; -+ u64 dma_mask; -+ const struct firmware *fw; -+ struct sg_table fw_sgt; -+ u64 *pkg_dir; -+ dma_addr_t pkg_dir_dma_addr; -+ unsigned int pkg_dir_size; -+}; -+ -+struct ipu6_auxdrv_data { -+ irqreturn_t (*isr)(struct ipu6_bus_device *adev); -+ irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); -+ bool wake_isr_thread; -+}; -+ -+#define to_ipu6_bus_device(_dev) \ -+ container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev) -+#define auxdev_to_adev(_auxdev) \ -+ container_of(_auxdev, struct ipu6_bus_device, auxdev) -+#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) -+ -+struct ipu6_bus_device * -+ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, -+ void *pdata, struct ipu6_buttress_ctrl *ctrl, -+ char *name); -+int ipu6_bus_add_device(struct ipu6_bus_device *adev); -+void ipu6_bus_del_devices(struct pci_dev *pdev); -+ -+#endif --- -2.43.2 - - -From a74d85716ec13ff2f55997c73c9f06367174d7a6 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:17 +0800 -Subject: [PATCH 10/33] media: intel/ipu6: add IPU6 buttress interface driver - -The IPU6 buttress is the interface between IPU device (input system -and processing system) with rest of the SoC. It contains overall IPU -hardware control registers, these control registers are used as the -interfaces with the Intel Converged Security Engine and Punit to do -firmware authentication and power management. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-buttress.c | 912 ++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-buttress.h | 102 ++ - .../intel/ipu6/ipu6-platform-buttress-regs.h | 232 +++++ - 3 files changed, 1246 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c -new file mode 100644 -index 000000000000..2f73302812f3 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c -@@ -0,0 +1,912 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/completion.h> -+#include <linux/delay.h> -+#include <linux/device.h> -+#include <linux/dma-mapping.h> -+#include <linux/firmware.h> -+#include <linux/interrupt.h> -+#include <linux/iopoll.h> -+#include <linux/math64.h> -+#include <linux/mm.h> -+#include <linux/mutex.h> -+#include <linux/pci.h> -+#include <linux/pfn.h> -+#include <linux/pm_runtime.h> -+#include <linux/scatterlist.h> -+#include <linux/slab.h> -+#include <linux/time64.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-buttress.h" -+#include "ipu6-platform-buttress-regs.h" -+ -+#define BOOTLOADER_STATUS_OFFSET 0x15c -+ -+#define BOOTLOADER_MAGIC_KEY 0xb00710ad -+ -+#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 -+#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 -+#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE -+ -+#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10 -+ -+#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) -+ -+#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) -+#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) -+#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) -+ -+#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC -+#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC -+#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) -+#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) -+ -+#define BUTTRESS_IPC_RESET_RETRY 2000 -+#define BUTTRESS_CSE_IPC_RESET_RETRY 4 -+#define BUTTRESS_IPC_CMD_SEND_RETRY 1 -+ -+#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 -+ -+static const u32 ipu6_adev_irq_mask[2] = { -+ BUTTRESS_ISR_IS_IRQ, -+ BUTTRESS_ISR_PS_IRQ -+}; -+ -+int ipu6_buttress_ipc_reset(struct ipu6_device *isp, -+ struct ipu6_buttress_ipc *ipc) -+{ -+ unsigned int retries = BUTTRESS_IPC_RESET_RETRY; -+ struct ipu6_buttress *b = &isp->buttress; -+ u32 val = 0, csr_in_clr; -+ -+ if (!isp->secure_mode) { -+ dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode"); -+ return 0; -+ } -+ -+ mutex_lock(&b->ipc_mutex); -+ -+ /* Clear-by-1 CSR (all bits), corresponding internal states. */ -+ val = readl(isp->base + ipc->csr_in); -+ writel(val, isp->base + ipc->csr_in); -+ -+ /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ -+ writel(ENTRY, isp->base + ipc->csr_out); -+ /* -+ * Clear-by-1 all CSR bits EXCEPT following -+ * bits: -+ * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. -+ * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. -+ * C. Possibly custom bits, depending on -+ * their role. -+ */ -+ csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | -+ BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | -+ BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; -+ -+ do { -+ usleep_range(400, 500); -+ val = readl(isp->base + ipc->csr_in); -+ switch (val) { -+ case ENTRY | EXIT: -+ case ENTRY | EXIT | QUERY: -+ /* -+ * 1) Clear-by-1 CSR bits -+ * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, -+ * IPC_PEER_COMP_ACTIONS_RST_PHASE2). -+ * 2) Set peer CSR bit -+ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. -+ */ -+ writel(ENTRY | EXIT, isp->base + ipc->csr_in); -+ writel(QUERY, isp->base + ipc->csr_out); -+ break; -+ case ENTRY: -+ case ENTRY | QUERY: -+ /* -+ * 1) Clear-by-1 CSR bits -+ * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, -+ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). -+ * 2) Set peer CSR bit -+ * IPC_PEER_COMP_ACTIONS_RST_PHASE1. -+ */ -+ writel(ENTRY | QUERY, isp->base + ipc->csr_in); -+ writel(ENTRY, isp->base + ipc->csr_out); -+ break; -+ case EXIT: -+ case EXIT | QUERY: -+ /* -+ * Clear-by-1 CSR bit -+ * IPC_PEER_COMP_ACTIONS_RST_PHASE2. -+ * 1) Clear incoming doorbell. -+ * 2) Clear-by-1 all CSR bits EXCEPT following -+ * bits: -+ * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. -+ * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. -+ * C. Possibly custom bits, depending on -+ * their role. -+ * 3) Set peer CSR bit -+ * IPC_PEER_COMP_ACTIONS_RST_PHASE2. -+ */ -+ writel(EXIT, isp->base + ipc->csr_in); -+ writel(0, isp->base + ipc->db0_in); -+ writel(csr_in_clr, isp->base + ipc->csr_in); -+ writel(EXIT, isp->base + ipc->csr_out); -+ -+ /* -+ * Read csr_in again to make sure if RST_PHASE2 is done. -+ * If csr_in is QUERY, it should be handled again. -+ */ -+ usleep_range(200, 300); -+ val = readl(isp->base + ipc->csr_in); -+ if (val & QUERY) { -+ dev_dbg(&isp->pdev->dev, -+ "RST_PHASE2 retry csr_in = %x\n", val); -+ break; -+ } -+ mutex_unlock(&b->ipc_mutex); -+ return 0; -+ case QUERY: -+ /* -+ * 1) Clear-by-1 CSR bit -+ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. -+ * 2) Set peer CSR bit -+ * IPC_PEER_COMP_ACTIONS_RST_PHASE1 -+ */ -+ writel(QUERY, isp->base + ipc->csr_in); -+ writel(ENTRY, isp->base + ipc->csr_out); -+ break; -+ default: -+ dev_warn_ratelimited(&isp->pdev->dev, -+ "Unexpected CSR 0x%x\n", val); -+ break; -+ } -+ } while (retries--); -+ -+ mutex_unlock(&b->ipc_mutex); -+ dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n"); -+ -+ return -ETIMEDOUT; -+} -+ -+static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, -+ struct ipu6_buttress_ipc *ipc) -+{ -+ writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, -+ isp->base + ipc->csr_out); -+} -+ -+static int -+ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, -+ struct ipu6_buttress_ipc *ipc) -+{ -+ unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; -+ void __iomem *addr; -+ int ret; -+ u32 val; -+ -+ writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, -+ isp->base + ipc->csr_out); -+ -+ addr = isp->base + ipc->csr_in; -+ ret = readl_poll_timeout(addr, val, val & mask, 200, -+ BUTTRESS_IPC_VALIDITY_TIMEOUT_US); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); -+ ipu6_buttress_ipc_validity_close(isp, ipc); -+ } -+ -+ return ret; -+} -+ -+static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, -+ struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) -+{ -+ if (ipc_msg) -+ *ipc_msg = readl(isp->base + ipc->data0_in); -+ writel(0, isp->base + ipc->db0_in); -+} -+ -+static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, -+ enum ipu6_buttress_ipc_domain ipc_domain, -+ struct ipu6_ipc_buttress_bulk_msg *msgs, -+ u32 size) -+{ -+ unsigned long tx_timeout_jiffies, rx_timeout_jiffies; -+ unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; -+ struct ipu6_buttress *b = &isp->buttress; -+ struct ipu6_buttress_ipc *ipc; -+ u32 val; -+ int ret; -+ int tout; -+ -+ ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish; -+ -+ mutex_lock(&b->ipc_mutex); -+ -+ ret = ipu6_buttress_ipc_validity_open(isp, ipc); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "IPC validity open failed\n"); -+ goto out; -+ } -+ -+ tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); -+ rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); -+ -+ for (i = 0; i < size; i++) { -+ reinit_completion(&ipc->send_complete); -+ if (msgs[i].require_resp) -+ reinit_completion(&ipc->recv_complete); -+ -+ dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", -+ msgs[i].cmd); -+ writel(msgs[i].cmd, isp->base + ipc->data0_out); -+ val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; -+ writel(val, isp->base + ipc->db0_out); -+ -+ tout = wait_for_completion_timeout(&ipc->send_complete, -+ tx_timeout_jiffies); -+ if (!tout) { -+ dev_err(&isp->pdev->dev, "send IPC response timeout\n"); -+ if (!retry--) { -+ ret = -ETIMEDOUT; -+ goto out; -+ } -+ -+ /* Try again if CSE is not responding on first try */ -+ writel(0, isp->base + ipc->db0_out); -+ i--; -+ continue; -+ } -+ -+ retry = BUTTRESS_IPC_CMD_SEND_RETRY; -+ -+ if (!msgs[i].require_resp) -+ continue; -+ -+ tout = wait_for_completion_timeout(&ipc->recv_complete, -+ rx_timeout_jiffies); -+ if (!tout) { -+ dev_err(&isp->pdev->dev, "recv IPC response timeout\n"); -+ ret = -ETIMEDOUT; -+ goto out; -+ } -+ -+ if (ipc->nack_mask && -+ (ipc->recv_data & ipc->nack_mask) == ipc->nack) { -+ dev_err(&isp->pdev->dev, -+ "IPC NACK for cmd 0x%x\n", msgs[i].cmd); -+ ret = -EIO; -+ goto out; -+ } -+ -+ if (ipc->recv_data != msgs[i].expected_resp) { -+ dev_err(&isp->pdev->dev, -+ "expected resp: 0x%x, IPC response: 0x%x ", -+ msgs[i].expected_resp, ipc->recv_data); -+ ret = -EIO; -+ goto out; -+ } -+ } -+ -+ dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); -+ -+out: -+ ipu6_buttress_ipc_validity_close(isp, ipc); -+ mutex_unlock(&b->ipc_mutex); -+ return ret; -+} -+ -+static int -+ipu6_buttress_ipc_send(struct ipu6_device *isp, -+ enum ipu6_buttress_ipc_domain ipc_domain, -+ u32 ipc_msg, u32 size, bool require_resp, -+ u32 expected_resp) -+{ -+ struct ipu6_ipc_buttress_bulk_msg msg = { -+ .cmd = ipc_msg, -+ .cmd_size = size, -+ .require_resp = require_resp, -+ .expected_resp = expected_resp, -+ }; -+ -+ return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1); -+} -+ -+static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) -+{ -+ irqreturn_t ret = IRQ_WAKE_THREAD; -+ -+ if (!adev || !adev->auxdrv || !adev->auxdrv_data) -+ return IRQ_NONE; -+ -+ if (adev->auxdrv_data->isr) -+ ret = adev->auxdrv_data->isr(adev); -+ -+ if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) -+ ret = IRQ_NONE; -+ -+ return ret; -+} -+ -+irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) -+{ -+ struct ipu6_device *isp = isp_ptr; -+ struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; -+ struct ipu6_buttress *b = &isp->buttress; -+ u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; -+ irqreturn_t ret = IRQ_NONE; -+ u32 disable_irqs = 0; -+ u32 irq_status; -+ u32 i, count = 0; -+ -+ pm_runtime_get_noresume(&isp->pdev->dev); -+ -+ irq_status = readl(isp->base + reg_irq_sts); -+ if (!irq_status) { -+ pm_runtime_put_noidle(&isp->pdev->dev); -+ return IRQ_NONE; -+ } -+ -+ do { -+ writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); -+ -+ for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { -+ irqreturn_t r = ipu6_buttress_call_isr(adev[i]); -+ -+ if (!(irq_status & ipu6_adev_irq_mask[i])) -+ continue; -+ -+ if (r == IRQ_WAKE_THREAD) { -+ ret = IRQ_WAKE_THREAD; -+ disable_irqs |= ipu6_adev_irq_mask[i]; -+ } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { -+ ret = IRQ_HANDLED; -+ } -+ } -+ -+ if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) -+ ret = IRQ_HANDLED; -+ -+ if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { -+ dev_dbg(&isp->pdev->dev, -+ "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); -+ ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); -+ complete(&b->cse.recv_complete); -+ } -+ -+ if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) { -+ dev_dbg(&isp->pdev->dev, -+ "BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n"); -+ ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data); -+ complete(&b->ish.recv_complete); -+ } -+ -+ if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { -+ dev_dbg(&isp->pdev->dev, -+ "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); -+ complete(&b->cse.send_complete); -+ } -+ -+ if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) { -+ dev_dbg(&isp->pdev->dev, -+ "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); -+ complete(&b->ish.send_complete); -+ } -+ -+ if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && -+ ipu6_buttress_get_secure_mode(isp)) -+ dev_err(&isp->pdev->dev, -+ "BUTTRESS_ISR_SAI_VIOLATION\n"); -+ -+ if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR | -+ BUTTRESS_ISR_PS_FATAL_MEM_ERR)) -+ dev_err(&isp->pdev->dev, -+ "BUTTRESS_ISR_FATAL_MEM_ERR\n"); -+ -+ if (irq_status & BUTTRESS_ISR_UFI_ERROR) -+ dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n"); -+ -+ if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) { -+ dev_err(&isp->pdev->dev, "too many consecutive IRQs\n"); -+ ret = IRQ_NONE; -+ break; -+ } -+ -+ irq_status = readl(isp->base + reg_irq_sts); -+ } while (irq_status); -+ -+ if (disable_irqs) -+ writel(BUTTRESS_IRQS & ~disable_irqs, -+ isp->base + BUTTRESS_REG_ISR_ENABLE); -+ -+ pm_runtime_put(&isp->pdev->dev); -+ -+ return ret; -+} -+ -+irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) -+{ -+ struct ipu6_device *isp = isp_ptr; -+ struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; -+ const struct ipu6_auxdrv_data *drv_data = NULL; -+ irqreturn_t ret = IRQ_NONE; -+ unsigned int i; -+ -+ for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { -+ drv_data = adev[i]->auxdrv_data; -+ if (!drv_data) -+ continue; -+ -+ if (drv_data->wake_isr_thread && -+ drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) -+ ret = IRQ_HANDLED; -+ } -+ -+ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); -+ -+ return ret; -+} -+ -+int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, -+ bool on) -+{ -+ struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; -+ u32 pwr_sts, val; -+ int ret; -+ -+ if (!ctrl) -+ return 0; -+ -+ mutex_lock(&isp->buttress.power_mutex); -+ -+ if (!on) { -+ val = 0; -+ pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; -+ } else { -+ val = BUTTRESS_FREQ_CTL_START | -+ FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, -+ ctrl->ratio) | -+ FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, -+ ctrl->qos_floor) | -+ BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; -+ -+ pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; -+ } -+ -+ writel(val, isp->base + ctrl->freq_ctl); -+ -+ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, -+ val, (val & ctrl->pwr_sts_mask) == pwr_sts, -+ 100, BUTTRESS_POWER_TIMEOUT_US); -+ if (ret) -+ dev_err(&isp->pdev->dev, -+ "Change power status timeout with 0x%x\n", val); -+ -+ ctrl->started = !ret && on; -+ -+ mutex_unlock(&isp->buttress.power_mutex); -+ -+ return ret; -+} -+ -+bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) -+{ -+ u32 val; -+ -+ val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); -+ -+ return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; -+} -+ -+bool ipu6_buttress_auth_done(struct ipu6_device *isp) -+{ -+ u32 val; -+ -+ if (!isp->secure_mode) -+ return true; -+ -+ val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); -+ val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); -+ -+ return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); -+ -+int ipu6_buttress_reset_authentication(struct ipu6_device *isp) -+{ -+ int ret; -+ u32 val; -+ -+ if (!isp->secure_mode) { -+ dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); -+ return 0; -+ } -+ -+ writel(BUTTRESS_FW_RESET_CTL_START, isp->base + -+ BUTTRESS_REG_FW_RESET_CTL); -+ -+ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, -+ val & BUTTRESS_FW_RESET_CTL_DONE, 500, -+ BUTTRESS_CSE_FWRESET_TIMEOUT_US); -+ if (ret) { -+ dev_err(&isp->pdev->dev, -+ "Time out while resetting authentication state\n"); -+ return ret; -+ } -+ -+ dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); -+ writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); -+ /* leave some time for HW restore */ -+ usleep_range(800, 1000); -+ -+ return 0; -+} -+ -+int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, -+ const struct firmware *fw, struct sg_table *sgt) -+{ -+ struct page **pages; -+ const void *addr; -+ unsigned long n_pages; -+ unsigned int i; -+ int ret; -+ -+ n_pages = PHYS_PFN(PAGE_ALIGN(fw->size)); -+ -+ pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); -+ if (!pages) -+ return -ENOMEM; -+ -+ addr = fw->data; -+ for (i = 0; i < n_pages; i++) { -+ struct page *p = vmalloc_to_page(addr); -+ -+ if (!p) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ pages[i] = p; -+ addr += PAGE_SIZE; -+ } -+ -+ ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, -+ GFP_KERNEL); -+ if (ret) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ ret = dma_map_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); -+ if (ret < 0) { -+ ret = -ENOMEM; -+ sg_free_table(sgt); -+ goto out; -+ } -+ -+ dma_sync_sgtable_for_device(&sys->auxdev.dev, sgt, DMA_TO_DEVICE); -+ -+out: -+ kfree(pages); -+ -+ return ret; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); -+ -+void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, -+ struct sg_table *sgt) -+{ -+ dma_unmap_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); -+ sg_free_table(sgt); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); -+ -+int ipu6_buttress_authenticate(struct ipu6_device *isp) -+{ -+ struct ipu6_buttress *b = &isp->buttress; -+ struct ipu6_psys_pdata *psys_pdata; -+ u32 data, mask, done, fail; -+ int ret; -+ -+ if (!isp->secure_mode) { -+ dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); -+ return 0; -+ } -+ -+ psys_pdata = isp->psys->pdata; -+ -+ mutex_lock(&b->auth_mutex); -+ -+ if (ipu6_buttress_auth_done(isp)) { -+ ret = 0; -+ goto out_unlock; -+ } -+ -+ /* -+ * Write address of FIT table to FW_SOURCE register -+ * Let's use fw address. I.e. not using FIT table yet -+ */ -+ data = lower_32_bits(isp->psys->pkg_dir_dma_addr); -+ writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); -+ -+ data = upper_32_bits(isp->psys->pkg_dir_dma_addr); -+ writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); -+ -+ /* -+ * Write boot_load into IU2CSEDATA0 -+ * Write sizeof(boot_load) | 0x2 << CLIENT_ID to -+ * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as -+ */ -+ dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); -+ -+ ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, -+ BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, -+ 1, true, -+ BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); -+ goto out_unlock; -+ } -+ -+ mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; -+ done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; -+ fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; -+ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, -+ ((data & mask) == done || -+ (data & mask) == fail), 500, -+ BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "CSE boot_load timeout\n"); -+ goto out_unlock; -+ } -+ -+ if ((data & mask) == fail) { -+ dev_err(&isp->pdev->dev, "CSE auth failed\n"); -+ ret = -EINVAL; -+ goto out_unlock; -+ } -+ -+ ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, -+ data, data == BOOTLOADER_MAGIC_KEY, 500, -+ BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", -+ data); -+ goto out_unlock; -+ } -+ -+ /* -+ * Write authenticate_run into IU2CSEDATA0 -+ * Write sizeof(boot_load) | 0x2 << CLIENT_ID to -+ * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as -+ */ -+ dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); -+ ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, -+ BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, -+ 1, true, -+ BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n"); -+ goto out_unlock; -+ } -+ -+ done = BUTTRESS_SECURITY_CTL_AUTH_DONE; -+ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, -+ ((data & mask) == done || -+ (data & mask) == fail), 500, -+ BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "CSE authenticate timeout\n"); -+ goto out_unlock; -+ } -+ -+ if ((data & mask) == fail) { -+ dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); -+ ret = -EINVAL; -+ goto out_unlock; -+ } -+ -+ dev_info(&isp->pdev->dev, "CSE authenticate_run done\n"); -+ -+out_unlock: -+ mutex_unlock(&b->auth_mutex); -+ -+ return ret; -+} -+ -+static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) -+{ -+ u32 val, mask, done; -+ int ret; -+ -+ mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; -+ -+ writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, -+ isp->base + BUTTRESS_REG_FABRIC_CMD); -+ -+ val = readl(isp->base + BUTTRESS_REG_PWR_STATE); -+ val = FIELD_GET(mask, val); -+ if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { -+ dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); -+ return -EINVAL; -+ } -+ -+ done = BUTTRESS_PWR_STATE_HH_STATE_DONE; -+ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, -+ FIELD_GET(mask, val) == done, 500, -+ BUTTRESS_TSC_SYNC_TIMEOUT_US); -+ if (ret) -+ dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); -+ -+ return ret; -+} -+ -+int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { -+ u32 val; -+ int ret; -+ -+ ret = ipu6_buttress_send_tsc_request(isp); -+ if (ret != -ETIMEDOUT) -+ return ret; -+ -+ val = readl(isp->base + BUTTRESS_REG_TSW_CTL); -+ val = val | BUTTRESS_TSW_CTL_SOFT_RESET; -+ writel(val, isp->base + BUTTRESS_REG_TSW_CTL); -+ val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; -+ writel(val, isp->base + BUTTRESS_REG_TSW_CTL); -+ } -+ -+ dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); -+ -+ return -ETIMEDOUT; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); -+ -+void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) -+{ -+ u32 tsc_hi_1, tsc_hi_2, tsc_lo; -+ unsigned long flags; -+ -+ local_irq_save(flags); -+ tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); -+ tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); -+ tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); -+ if (tsc_hi_1 == tsc_hi_2) { -+ *val = (u64)tsc_hi_1 << 32 | tsc_lo; -+ } else { -+ /* Check if TSC has rolled over */ -+ if (tsc_lo & BIT(31)) -+ *val = (u64)tsc_hi_1 << 32 | tsc_lo; -+ else -+ *val = (u64)tsc_hi_2 << 32 | tsc_lo; -+ } -+ local_irq_restore(flags); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); -+ -+u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) -+{ -+ u64 ns = ticks * 10000; -+ -+ /* -+ * converting TSC tick count to ns is calculated by: -+ * Example (TSC clock frequency is 19.2MHz): -+ * ns = ticks * 1000 000 000 / 19.2Mhz -+ * = ticks * 1000 000 000 / 19200000Hz -+ * = ticks * 10000 / 192 ns -+ */ -+ return div_u64(ns, isp->buttress.ref_clk); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); -+ -+void ipu6_buttress_restore(struct ipu6_device *isp) -+{ -+ struct ipu6_buttress *b = &isp->buttress; -+ -+ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); -+ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); -+ writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); -+} -+ -+int ipu6_buttress_init(struct ipu6_device *isp) -+{ -+ int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; -+ struct ipu6_buttress *b = &isp->buttress; -+ u32 val; -+ -+ mutex_init(&b->power_mutex); -+ mutex_init(&b->auth_mutex); -+ mutex_init(&b->cons_mutex); -+ mutex_init(&b->ipc_mutex); -+ init_completion(&b->ish.send_complete); -+ init_completion(&b->cse.send_complete); -+ init_completion(&b->ish.recv_complete); -+ init_completion(&b->cse.recv_complete); -+ -+ b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; -+ b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; -+ b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; -+ b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; -+ b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; -+ b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; -+ b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; -+ b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; -+ -+ /* no ISH on IPU6 */ -+ memset(&b->ish, 0, sizeof(b->ish)); -+ INIT_LIST_HEAD(&b->constraints); -+ -+ isp->secure_mode = ipu6_buttress_get_secure_mode(isp); -+ dev_info(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", -+ isp->secure_mode ? "secure" : "non-secure", -+ readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), -+ readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); -+ -+ b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); -+ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); -+ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); -+ -+ /* get ref_clk frequency by reading the indication in btrs control */ -+ val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); -+ val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); -+ -+ switch (val) { -+ case 0x0: -+ b->ref_clk = 240; -+ break; -+ case 0x1: -+ b->ref_clk = 192; -+ break; -+ case 0x2: -+ b->ref_clk = 384; -+ break; -+ default: -+ dev_warn(&isp->pdev->dev, -+ "Unsupported ref clock, use 19.2Mhz by default.\n"); -+ b->ref_clk = 192; -+ break; -+ } -+ -+ /* Retry couple of times in case of CSE initialization is delayed */ -+ do { -+ ret = ipu6_buttress_ipc_reset(isp, &b->cse); -+ if (ret) { -+ dev_warn(&isp->pdev->dev, -+ "IPC reset protocol failed, retrying\n"); -+ } else { -+ dev_dbg(&isp->pdev->dev, "IPC reset done\n"); -+ return 0; -+ } -+ } while (ipc_reset_retry--); -+ -+ dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); -+ -+ mutex_destroy(&b->power_mutex); -+ mutex_destroy(&b->auth_mutex); -+ mutex_destroy(&b->cons_mutex); -+ mutex_destroy(&b->ipc_mutex); -+ -+ return ret; -+} -+ -+void ipu6_buttress_exit(struct ipu6_device *isp) -+{ -+ struct ipu6_buttress *b = &isp->buttress; -+ -+ writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); -+ -+ mutex_destroy(&b->power_mutex); -+ mutex_destroy(&b->auth_mutex); -+ mutex_destroy(&b->cons_mutex); -+ mutex_destroy(&b->ipc_mutex); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h -new file mode 100644 -index 000000000000..558e1d70f4af ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h -@@ -0,0 +1,102 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_BUTTRESS_H -+#define IPU6_BUTTRESS_H -+ -+#include <linux/completion.h> -+#include <linux/irqreturn.h> -+#include <linux/list.h> -+#include <linux/mutex.h> -+ -+struct device; -+struct firmware; -+struct ipu6_device; -+struct ipu6_bus_device; -+ -+#define BUTTRESS_PS_FREQ_STEP 25U -+#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8) -+#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32) -+ -+#define BUTTRESS_IS_FREQ_STEP 25U -+#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8) -+#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 22) -+ -+struct ipu6_buttress_ctrl { -+ u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; -+ unsigned int ratio; -+ unsigned int qos_floor; -+ bool started; -+}; -+ -+struct ipu6_buttress_ipc { -+ struct completion send_complete; -+ struct completion recv_complete; -+ u32 nack; -+ u32 nack_mask; -+ u32 recv_data; -+ u32 csr_out; -+ u32 csr_in; -+ u32 db0_in; -+ u32 db0_out; -+ u32 data0_out; -+ u32 data0_in; -+}; -+ -+struct ipu6_buttress { -+ struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; -+ struct ipu6_buttress_ipc cse; -+ struct ipu6_buttress_ipc ish; -+ struct list_head constraints; -+ u32 wdt_cached_value; -+ bool force_suspend; -+ u32 ref_clk; -+}; -+ -+struct ipu6_buttress_sensor_clk_freq { -+ unsigned int rate; -+ unsigned int val; -+}; -+ -+enum ipu6_buttress_ipc_domain { -+ IPU6_BUTTRESS_IPC_CSE, -+ IPU6_BUTTRESS_IPC_ISH, -+}; -+ -+struct ipu6_buttress_constraint { -+ struct list_head list; -+ unsigned int min_freq; -+}; -+ -+struct ipu6_ipc_buttress_bulk_msg { -+ u32 cmd; -+ u32 expected_resp; -+ bool require_resp; -+ u8 cmd_size; -+}; -+ -+int ipu6_buttress_ipc_reset(struct ipu6_device *isp, -+ struct ipu6_buttress_ipc *ipc); -+int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, -+ const struct firmware *fw, -+ struct sg_table *sgt); -+void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, -+ struct sg_table *sgt); -+int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, -+ bool on); -+bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); -+int ipu6_buttress_authenticate(struct ipu6_device *isp); -+int ipu6_buttress_reset_authentication(struct ipu6_device *isp); -+bool ipu6_buttress_auth_done(struct ipu6_device *isp); -+int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp); -+void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val); -+u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp); -+ -+irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr); -+irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr); -+int ipu6_buttress_init(struct ipu6_device *isp); -+void ipu6_buttress_exit(struct ipu6_device *isp); -+void ipu6_buttress_csi_port_config(struct ipu6_device *isp, -+ u32 legacy, u32 combo); -+void ipu6_buttress_restore(struct ipu6_device *isp); -+#endif /* IPU6_BUTTRESS_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h -new file mode 100644 -index 000000000000..87239af96502 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h -@@ -0,0 +1,232 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2023 Intel Corporation */ -+ -+#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H -+#define IPU6_PLATFORM_BUTTRESS_REGS_H -+ -+#include <linux/bits.h> -+ -+/* IS_WORKPOINT_REQ */ -+#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34 -+/* PS_WORKPOINT_REQ */ -+#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38 -+ -+#define IPU6_IS_FREQ_MAX 533 -+#define IPU6_IS_FREQ_MIN 200 -+#define IPU6_PS_FREQ_MAX 450 -+#define IPU6_IS_FREQ_RATIO_BASE 25 -+#define IPU6_PS_FREQ_RATIO_BASE 25 -+ -+/* should be tuned for real silicon */ -+#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08 -+#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a -+#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d -+ -+#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10 -+#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708 -+ -+#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3 -+#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3) -+ -+#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6 -+#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6) -+ -+#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0 -+#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1 -+#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2 -+#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3 -+ -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288 -+#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c -+ -+#define BUTTRESS_REG_WDT 0x8 -+#define BUTTRESS_REG_BTRS_CTRL 0xc -+#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0) -+#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1) -+#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8) -+ -+#define BUTTRESS_REG_FW_RESET_CTL 0x30 -+#define BUTTRESS_FW_RESET_CTL_START BIT(0) -+#define BUTTRESS_FW_RESET_CTL_DONE BIT(1) -+ -+#define BUTTRESS_REG_IS_FREQ_CTL 0x34 -+#define BUTTRESS_REG_PS_FREQ_CTL 0x38 -+ -+#define BUTTRESS_FREQ_CTL_START BIT(31) -+#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16) -+#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8) -+#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) -+ -+#define BUTTRESS_REG_PWR_STATE 0x5c -+ -+#define BUTTRESS_PWR_STATE_RESET 0x0 -+#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1 -+#define BUTTRESS_PWR_STATE_PWR_RDY 0x3 -+#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4 -+ -+#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11) -+ -+enum { -+ BUTTRESS_PWR_STATE_HH_STATE_IDLE, -+ BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS, -+ BUTTRESS_PWR_STATE_HH_STATE_DONE, -+ BUTTRESS_PWR_STATE_HH_STATE_ERR, -+}; -+ -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19) -+ -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9 -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe -+#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf -+ -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24) -+ -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15 -+#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16 -+ -+#define BUTTRESS_REG_SECURITY_CTL 0x300 -+#define BUTTRESS_REG_SKU 0x314 -+#define BUTTRESS_REG_SECURITY_TOUCH 0x318 -+#define BUTTRESS_REG_CAMERA_MASK 0x84 -+ -+#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16) -+#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0) -+ -+#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0) -+#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1) -+#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3) -+ -+#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78 -+#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C -+#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80 -+ -+#define BUTTRESS_REG_ISR_STATUS 0x90 -+#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94 -+#define BUTTRESS_REG_ISR_ENABLE 0x98 -+#define BUTTRESS_REG_ISR_CLEAR 0x9C -+ -+#define BUTTRESS_ISR_IS_IRQ BIT(0) -+#define BUTTRESS_ISR_PS_IRQ BIT(1) -+#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2) -+#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3) -+#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4) -+#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5) -+#define BUTTRESS_ISR_CSE_CSR_SET BIT(6) -+#define BUTTRESS_ISR_ISH_CSR_SET BIT(7) -+#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8) -+#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9) -+#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10) -+#define BUTTRESS_ISR_SAI_VIOLATION BIT(11) -+#define BUTTRESS_ISR_HW_ASSERTION BIT(12) -+#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13) -+#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14) -+#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15) -+#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16) -+#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17) -+#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18) -+#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19) -+#define BUTTRESS_ISR_UFI_ERROR BIT(20) -+ -+#define BUTTRESS_REG_IU2CSEDB0 0x100 -+ -+#define BUTTRESS_IU2CSEDB0_BUSY BIT(31) -+#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2 -+ -+#define BUTTRESS_REG_IU2CSEDATA0 0x104 -+ -+#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1 -+#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2 -+#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3 -+#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16 -+ -+#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0) -+#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1) -+#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2) -+#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4) -+ -+#define BUTTRESS_REG_IU2CSECSR 0x108 -+ -+#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0) -+#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1) -+#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2) -+#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3) -+#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4) -+#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5) -+ -+#define BUTTRESS_REG_CSE2IUDB0 0x304 -+#define BUTTRESS_REG_CSE2IUCSR 0x30C -+#define BUTTRESS_REG_CSE2IUDATA0 0x308 -+ -+/* 0x20 == NACK, 0xf == unknown command */ -+#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20 -+#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0) -+ -+#define BUTTRESS_REG_ISH2IUCSR 0x50 -+#define BUTTRESS_REG_ISH2IUDB0 0x54 -+#define BUTTRESS_REG_ISH2IUDATA0 0x58 -+ -+#define BUTTRESS_REG_IU2ISHDB0 0x10C -+#define BUTTRESS_REG_IU2ISHDATA0 0x110 -+#define BUTTRESS_REG_IU2ISHDATA1 0x114 -+#define BUTTRESS_REG_IU2ISHCSR 0x118 -+ -+#define BUTTRESS_REG_FABRIC_CMD 0x88 -+ -+#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0) -+#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4) -+ -+#define BUTTRESS_REG_TSW_CTL 0x120 -+#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) -+ -+#define BUTTRESS_REG_TSC_LO 0x164 -+#define BUTTRESS_REG_TSC_HI 0x168 -+ -+#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ -+ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ -+ BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ) -+ -+#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ -+ BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING | \ -+ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ -+ BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH | \ -+ BUTTRESS_ISR_SAI_VIOLATION) -+#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ --- -2.43.2 - - -From 12bd5bdd53a7c829cc5cd61a6887f89ffb036f8f Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:18 +0800 -Subject: [PATCH 11/33] media: intel/ipu6: CPD parsing for get firmware - components - -For IPU6, firmware is generated and released as signed -Code Partition Directory (CPD) format file, which is aligned with -the SPI flash code partition definition. CPD format include CPD -header, manifest, metadata and module data. Driver can parse them -according to the CPD layout to acquire each component. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-cpd.c | 362 ++++++++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-cpd.h | 105 +++++++ - 2 files changed, 467 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c -new file mode 100644 -index 000000000000..b0ffd04c4cd3 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c -@@ -0,0 +1,362 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/err.h> -+#include <linux/dma-mapping.h> -+#include <linux/gfp_types.h> -+#include <linux/math64.h> -+#include <linux/sizes.h> -+#include <linux/types.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-cpd.h" -+ -+/* 15 entries + header*/ -+#define MAX_PKG_DIR_ENT_CNT 16 -+/* 2 qword per entry/header */ -+#define PKG_DIR_ENT_LEN 2 -+/* PKG_DIR size in bytes */ -+#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \ -+ (PKG_DIR_ENT_LEN) * sizeof(u64)) -+/* _IUPKDR_ */ -+#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL -+ -+/* $CPD */ -+#define CPD_HDR_MARK 0x44504324 -+ -+#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32)) -+#define MAX_METADATA_SIZE SZ_64K -+ -+#define MAX_COMPONENT_ID 127 -+#define MAX_COMPONENT_VERSION 0xffff -+ -+#define MANIFEST_IDX 0 -+#define METADATA_IDX 1 -+#define MODULEDATA_IDX 2 -+/* -+ * PKG_DIR Entry (type == id) -+ * 63:56 55 54:48 47:32 31:24 23:0 -+ * Rsvd Rsvd Type Version Rsvd Size -+ */ -+#define PKG_DIR_SIZE_MASK GENMASK(23, 0) -+#define PKG_DIR_VERSION_MASK GENMASK(47, 32) -+#define PKG_DIR_TYPE_MASK GENMASK(54, 48) -+ -+static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, -+ u8 idx) -+{ -+ const struct ipu6_cpd_hdr *cpd_hdr = cpd; -+ const struct ipu6_cpd_ent *ent; -+ -+ ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len); -+ return ent + idx; -+} -+ -+#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX) -+#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) -+#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) -+ -+static const struct ipu6_cpd_metadata_cmpnt_hdr * -+ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, -+ unsigned int metadata_size, u8 idx) -+{ -+ size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn); -+ size_t cmpnt_count = metadata_size - extn_size; -+ -+ cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size); -+ -+ if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) { -+ dev_err(&isp->pdev->dev, "Component index out of range (%d)\n", -+ idx); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size; -+} -+ -+static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp, -+ const void *metadata, -+ unsigned int metadata_size, u8 idx) -+{ -+ const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; -+ -+ cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); -+ if (IS_ERR(cmpnt)) -+ return PTR_ERR(cmpnt); -+ -+ return cmpnt->ver; -+} -+ -+static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp, -+ const void *metadata, -+ unsigned int metadata_size, u8 idx) -+{ -+ const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; -+ -+ cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); -+ if (IS_ERR(cmpnt)) -+ return PTR_ERR(cmpnt); -+ -+ return cmpnt->id; -+} -+ -+static int ipu6_cpd_parse_module_data(struct ipu6_device *isp, -+ const void *module_data, -+ unsigned int module_data_size, -+ dma_addr_t dma_addr_module_data, -+ u64 *pkg_dir, const void *metadata, -+ unsigned int metadata_size) -+{ -+ const struct ipu6_cpd_module_data_hdr *module_data_hdr; -+ const struct ipu6_cpd_hdr *dir_hdr; -+ const struct ipu6_cpd_ent *dir_ent; -+ unsigned int i; -+ u8 len; -+ -+ if (!module_data) -+ return -EINVAL; -+ -+ module_data_hdr = module_data; -+ dir_hdr = module_data + module_data_hdr->hdr_len; -+ len = dir_hdr->hdr_len; -+ dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len); -+ -+ pkg_dir[0] = PKG_DIR_HDR_MARK; -+ /* pkg_dir entry count = component count + pkg_dir header */ -+ pkg_dir[1] = dir_hdr->ent_cnt + 1; -+ -+ for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) { -+ u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)]; -+ int ver, id; -+ -+ *p++ = dma_addr_module_data + dir_ent->offset; -+ id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata, -+ metadata_size, i); -+ if (id < 0 || id > MAX_COMPONENT_ID) { -+ dev_err(&isp->pdev->dev, "Invalid CPD component id\n"); -+ return -EINVAL; -+ } -+ -+ ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata, -+ metadata_size, i); -+ if (ver < 0 || ver > MAX_COMPONENT_VERSION) { -+ dev_err(&isp->pdev->dev, -+ "Invalid CPD component version\n"); -+ return -EINVAL; -+ } -+ -+ *p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) | -+ FIELD_PREP(PKG_DIR_TYPE_MASK, id) | -+ FIELD_PREP(PKG_DIR_VERSION_MASK, ver); -+ } -+ -+ return 0; -+} -+ -+int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src) -+{ -+ dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl); -+ const struct ipu6_cpd_ent *ent, *man_ent, *met_ent; -+ struct device *dev = &adev->auxdev.dev; -+ struct ipu6_device *isp = adev->isp; -+ unsigned int man_sz, met_sz; -+ void *pkg_dir_pos; -+ int ret; -+ -+ man_ent = ipu6_cpd_get_manifest(src); -+ man_sz = man_ent->len; -+ -+ met_ent = ipu6_cpd_get_metadata(src); -+ met_sz = met_ent->len; -+ -+ adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz; -+ adev->pkg_dir = dma_alloc_attrs(dev, adev->pkg_dir_size, -+ &adev->pkg_dir_dma_addr, GFP_KERNEL, 0); -+ if (!adev->pkg_dir) -+ return -ENOMEM; -+ -+ /* -+ * pkg_dir entry/header: -+ * qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0 -+ * N Address/Offset/"_IUPKDR_" -+ * N + 1 | rsvd | rsvd | type | ver | rsvd | size -+ * -+ * We can ignore other fields that size in N + 1 qword as they -+ * are 0 anyway. Just setting size for now. -+ */ -+ -+ ent = ipu6_cpd_get_moduledata(src); -+ -+ ret = ipu6_cpd_parse_module_data(isp, src + ent->offset, -+ ent->len, dma_addr_src + ent->offset, -+ adev->pkg_dir, src + met_ent->offset, -+ met_ent->len); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "Failed to parse module data\n"); -+ dma_free_attrs(dev, adev->pkg_dir_size, -+ adev->pkg_dir, adev->pkg_dir_dma_addr, 0); -+ return ret; -+ } -+ -+ /* Copy manifest after pkg_dir */ -+ pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT; -+ memcpy(pkg_dir_pos, src + man_ent->offset, man_sz); -+ -+ /* Copy metadata after manifest */ -+ pkg_dir_pos += man_sz; -+ memcpy(pkg_dir_pos, src + met_ent->offset, met_sz); -+ -+ dma_sync_single_range_for_device(dev, adev->pkg_dir_dma_addr, -+ 0, adev->pkg_dir_size, DMA_TO_DEVICE); -+ -+ return 0; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); -+ -+void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) -+{ -+ dma_free_attrs(&adev->auxdev.dev, adev->pkg_dir_size, adev->pkg_dir, -+ adev->pkg_dir_dma_addr, 0); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); -+ -+static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd, -+ unsigned long cpd_size, -+ unsigned long data_size) -+{ -+ const struct ipu6_cpd_hdr *cpd_hdr = cpd; -+ const struct ipu6_cpd_ent *ent; -+ unsigned int i; -+ u8 len; -+ -+ len = cpd_hdr->hdr_len; -+ -+ /* Ensure cpd hdr is within moduledata */ -+ if (cpd_size < len) { -+ dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); -+ return -EINVAL; -+ } -+ -+ /* Sanity check for CPD header */ -+ if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) { -+ dev_err(&isp->pdev->dev, "Invalid CPD header\n"); -+ return -EINVAL; -+ } -+ -+ /* Ensure that all entries are within moduledata */ -+ ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len); -+ for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) { -+ if (data_size < ent->offset || -+ data_size - ent->offset < ent->len) { -+ dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i); -+ return -EINVAL; -+ } -+ } -+ -+ return 0; -+} -+ -+static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, -+ const void *moduledata, -+ u32 moduledata_size) -+{ -+ const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; -+ int ret; -+ -+ /* Ensure moduledata hdr is within moduledata */ -+ if (moduledata_size < sizeof(*mod_hdr) || -+ moduledata_size < mod_hdr->hdr_len) { -+ dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); -+ return -EINVAL; -+ } -+ -+ dev_info(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); -+ ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, -+ moduledata_size - mod_hdr->hdr_len, -+ moduledata_size); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, -+ const void *metadata, u32 meta_size) -+{ -+ const struct ipu6_cpd_metadata_extn *extn = metadata; -+ -+ /* Sanity check for metadata size */ -+ if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) { -+ dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); -+ return -EINVAL; -+ } -+ -+ /* Validate extension and image types */ -+ if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT || -+ extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) { -+ dev_err(&isp->pdev->dev, -+ "Invalid CPD metadata descriptor img_type (%d)\n", -+ extn->img_type); -+ return -EINVAL; -+ } -+ -+ /* Validate metadata size multiple of metadata components */ -+ if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) { -+ dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n"); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, -+ unsigned long cpd_file_size) -+{ -+ const struct ipu6_cpd_hdr *hdr = cpd_file; -+ const struct ipu6_cpd_ent *ent; -+ int ret; -+ -+ ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, -+ cpd_file_size); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); -+ return ret; -+ } -+ -+ /* Check for CPD file marker */ -+ if (hdr->hdr_mark != CPD_HDR_MARK) { -+ dev_err(&isp->pdev->dev, "Invalid CPD header\n"); -+ return -EINVAL; -+ } -+ -+ /* Sanity check for manifest size */ -+ ent = ipu6_cpd_get_manifest(cpd_file); -+ if (ent->len > MAX_MANIFEST_SIZE) { -+ dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); -+ return -EINVAL; -+ } -+ -+ /* Validate metadata */ -+ ent = ipu6_cpd_get_metadata(cpd_file); -+ ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); -+ if (ret) { -+ dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); -+ return ret; -+ } -+ -+ /* Validate moduledata */ -+ ent = ipu6_cpd_get_moduledata(cpd_file); -+ ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, -+ ent->len); -+ if (ret) -+ dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); -+ -+ return ret; -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h -new file mode 100644 -index 000000000000..37465d507386 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h -@@ -0,0 +1,105 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2015 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_CPD_H -+#define IPU6_CPD_H -+ -+struct ipu6_device; -+struct ipu6_bus_device; -+ -+#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION 7 -+#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION 11 -+#define IPU6_CPD_SIZE_OF_COMPONENT_NAME 12 -+ -+#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT 0x10 -+ -+#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED 0 -+#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER 1 -+#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE 2 -+ -+#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX 0 -+#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX 1 -+ -+#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE 3 -+ -+#define IPU6_CPD_METADATA_HASH_KEY_SIZE 48 -+#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE 32 -+ -+struct ipu6_cpd_module_data_hdr { -+ u32 hdr_len; -+ u32 endian; -+ u32 fw_pkg_date; -+ u32 hive_sdk_date; -+ u32 compiler_date; -+ u32 target_platform_type; -+ u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION]; -+ u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION]; -+ u8 rsvd[2]; -+} __packed; -+ -+/* -+ * ipu6_cpd_hdr structure updated as the chksum and -+ * sub_partition_name is unused on host side -+ * CSE layout version 1.6 for IPU6SE (hdr_len = 0x10) -+ * CSE layout version 1.7 for IPU6 (hdr_len = 0x14) -+ */ -+struct ipu6_cpd_hdr { -+ u32 hdr_mark; -+ u32 ent_cnt; -+ u8 hdr_ver; -+ u8 ent_ver; -+ u8 hdr_len; -+} __packed; -+ -+struct ipu6_cpd_ent { -+ u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME]; -+ u32 offset; -+ u32 len; -+ u8 rsvd[4]; -+} __packed; -+ -+struct ipu6_cpd_metadata_cmpnt_hdr { -+ u32 id; -+ u32 size; -+ u32 ver; -+} __packed; -+ -+struct ipu6_cpd_metadata_cmpnt { -+ struct ipu6_cpd_metadata_cmpnt_hdr hdr; -+ u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE]; -+ u32 entry_point; -+ u32 icache_base_offs; -+ u8 attrs[16]; -+} __packed; -+ -+struct ipu6se_cpd_metadata_cmpnt { -+ struct ipu6_cpd_metadata_cmpnt_hdr hdr; -+ u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE]; -+ u32 entry_point; -+ u32 icache_base_offs; -+ u8 attrs[16]; -+} __packed; -+ -+struct ipu6_cpd_metadata_extn { -+ u32 extn_type; -+ u32 len; -+ u32 img_type; -+ u8 rsvd[16]; -+} __packed; -+ -+struct ipu6_cpd_client_pkg_hdr { -+ u32 prog_list_offs; -+ u32 prog_list_size; -+ u32 prog_desc_offs; -+ u32 prog_desc_size; -+ u32 pg_manifest_offs; -+ u32 pg_manifest_size; -+ u32 prog_bin_offs; -+ u32 prog_bin_size; -+} __packed; -+ -+int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src); -+void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev); -+int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, -+ unsigned long cpd_file_size); -+#endif /* IPU6_CPD_H */ --- -2.43.2 - - -From 86b4cd540a9cb10de7a521882495f5eba6cc919f Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:19 +0800 -Subject: [PATCH 12/33] media: intel/ipu6: add IPU6 DMA mapping API and MMU - table - -he Intel IPU6 has an internal microcontroller (scalar processor, SP) which -is used to execute the firmware. The SP can access IPU internal memory and -map system DRAM to its an internal 32-bit virtual address space. - -This patch adds a driver for the IPU MMU and a DMA mapping implementation -using the internal MMU. The system IOMMU may be used besides the IPU MMU. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-dma.c | 502 ++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-dma.h | 19 + - drivers/media/pci/intel/ipu6/ipu6-mmu.c | 845 ++++++++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-mmu.h | 73 ++ - 4 files changed, 1439 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c -new file mode 100644 -index 000000000000..3d77c6e5a45e ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c -@@ -0,0 +1,502 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/cacheflush.h> -+#include <linux/dma-mapping.h> -+#include <linux/iova.h> -+#include <linux/list.h> -+#include <linux/mm.h> -+#include <linux/vmalloc.h> -+#include <linux/scatterlist.h> -+#include <linux/slab.h> -+#include <linux/types.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-dma.h" -+#include "ipu6-mmu.h" -+ -+struct vm_info { -+ struct list_head list; -+ struct page **pages; -+ dma_addr_t ipu6_iova; -+ void *vaddr; -+ unsigned long size; -+}; -+ -+static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova) -+{ -+ struct vm_info *info, *save; -+ -+ list_for_each_entry_safe(info, save, &mmu->vma_list, list) { -+ if (iova >= info->ipu6_iova && -+ iova < (info->ipu6_iova + info->size)) -+ return info; -+ } -+ -+ return NULL; -+} -+ -+static void __dma_clear_buffer(struct page *page, size_t size, -+ unsigned long attrs) -+{ -+ void *ptr; -+ -+ if (!page) -+ return; -+ /* -+ * Ensure that the allocated pages are zeroed, and that any data -+ * lurking in the kernel direct-mapped region is invalidated. -+ */ -+ ptr = page_address(page); -+ memset(ptr, 0, size); -+ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) -+ clflush_cache_range(ptr, size); -+} -+ -+static struct page **__dma_alloc_buffer(struct device *dev, size_t size, -+ gfp_t gfp, unsigned long attrs) -+{ -+ int count = PHYS_PFN(size); -+ int array_size = count * sizeof(struct page *); -+ struct page **pages; -+ int i = 0; -+ -+ pages = kvzalloc(array_size, GFP_KERNEL); -+ if (!pages) -+ return NULL; -+ -+ gfp |= __GFP_NOWARN; -+ -+ while (count) { -+ int j, order = __fls(count); -+ -+ pages[i] = alloc_pages(gfp, order); -+ while (!pages[i] && order) -+ pages[i] = alloc_pages(gfp, --order); -+ if (!pages[i]) -+ goto error; -+ -+ if (order) { -+ split_page(pages[i], order); -+ j = 1 << order; -+ while (j--) -+ pages[i + j] = pages[i] + j; -+ } -+ -+ __dma_clear_buffer(pages[i], PAGE_SIZE << order, attrs); -+ i += 1 << order; -+ count -= 1 << order; -+ } -+ -+ return pages; -+error: -+ while (i--) -+ if (pages[i]) -+ __free_pages(pages[i], 0); -+ kvfree(pages); -+ return NULL; -+} -+ -+static void __dma_free_buffer(struct device *dev, struct page **pages, -+ size_t size, unsigned long attrs) -+{ -+ int count = PHYS_PFN(size); -+ unsigned int i; -+ -+ for (i = 0; i < count && pages[i]; i++) { -+ __dma_clear_buffer(pages[i], PAGE_SIZE, attrs); -+ __free_pages(pages[i], 0); -+ } -+ -+ kvfree(pages); -+} -+ -+static void ipu6_dma_sync_single_for_cpu(struct device *dev, -+ dma_addr_t dma_handle, -+ size_t size, -+ enum dma_data_direction dir) -+{ -+ void *vaddr; -+ u32 offset; -+ struct vm_info *info; -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ -+ info = get_vm_info(mmu, dma_handle); -+ if (WARN_ON(!info)) -+ return; -+ -+ offset = dma_handle - info->ipu6_iova; -+ if (WARN_ON(size > (info->size - offset))) -+ return; -+ -+ vaddr = info->vaddr + offset; -+ clflush_cache_range(vaddr, size); -+} -+ -+static void ipu6_dma_sync_sg_for_cpu(struct device *dev, -+ struct scatterlist *sglist, -+ int nents, enum dma_data_direction dir) -+{ -+ struct scatterlist *sg; -+ int i; -+ -+ for_each_sg(sglist, sg, nents, i) -+ clflush_cache_range(page_to_virt(sg_page(sg)), sg->length); -+} -+ -+static void *ipu6_dma_alloc(struct device *dev, size_t size, -+ dma_addr_t *dma_handle, gfp_t gfp, -+ unsigned long attrs) -+{ -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; -+ dma_addr_t pci_dma_addr, ipu6_iova; -+ struct vm_info *info; -+ unsigned long count; -+ struct page **pages; -+ struct iova *iova; -+ unsigned int i; -+ int ret; -+ -+ info = kzalloc(sizeof(*info), GFP_KERNEL); -+ if (!info) -+ return NULL; -+ -+ size = PAGE_ALIGN(size); -+ count = PHYS_PFN(size); -+ -+ iova = alloc_iova(&mmu->dmap->iovad, count, -+ PHYS_PFN(dma_get_mask(dev)), 0); -+ if (!iova) -+ goto out_kfree; -+ -+ pages = __dma_alloc_buffer(dev, size, gfp, attrs); -+ if (!pages) -+ goto out_free_iova; -+ -+ dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n", -+ size, iova->pfn_lo, iova->pfn_hi); -+ for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) { -+ pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0, -+ PAGE_SIZE, DMA_BIDIRECTIONAL, -+ attrs); -+ dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n", -+ &pci_dma_addr); -+ if (dma_mapping_error(&pdev->dev, pci_dma_addr)) { -+ dev_err(dev, "pci_dma_mapping for page[%d] failed", i); -+ goto out_unmap; -+ } -+ -+ ret = ipu6_mmu_map(mmu->dmap->mmu_info, -+ PFN_PHYS(iova->pfn_lo + i), pci_dma_addr, -+ PAGE_SIZE); -+ if (ret) { -+ dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed", -+ i, &pci_dma_addr); -+ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, -+ PAGE_SIZE, DMA_BIDIRECTIONAL, -+ attrs); -+ goto out_unmap; -+ } -+ } -+ -+ info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL); -+ if (!info->vaddr) -+ goto out_unmap; -+ -+ *dma_handle = PFN_PHYS(iova->pfn_lo); -+ -+ info->pages = pages; -+ info->ipu6_iova = *dma_handle; -+ info->size = size; -+ list_add(&info->list, &mmu->vma_list); -+ -+ return info->vaddr; -+ -+out_unmap: -+ while (i--) { -+ ipu6_iova = PFN_PHYS(iova->pfn_lo + i); -+ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, -+ ipu6_iova); -+ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, -+ DMA_BIDIRECTIONAL, attrs); -+ -+ ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE); -+ } -+ -+ __dma_free_buffer(dev, pages, size, attrs); -+ -+out_free_iova: -+ __free_iova(&mmu->dmap->iovad, iova); -+out_kfree: -+ kfree(info); -+ -+ return NULL; -+} -+ -+static void ipu6_dma_free(struct device *dev, size_t size, void *vaddr, -+ dma_addr_t dma_handle, -+ unsigned long attrs) -+{ -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; -+ struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle)); -+ dma_addr_t pci_dma_addr, ipu6_iova; -+ struct vm_info *info; -+ struct page **pages; -+ unsigned int i; -+ -+ if (WARN_ON(!iova)) -+ return; -+ -+ info = get_vm_info(mmu, dma_handle); -+ if (WARN_ON(!info)) -+ return; -+ -+ if (WARN_ON(!info->vaddr)) -+ return; -+ -+ if (WARN_ON(!info->pages)) -+ return; -+ -+ list_del(&info->list); -+ -+ size = PAGE_ALIGN(size); -+ -+ pages = info->pages; -+ -+ vunmap(vaddr); -+ -+ for (i = 0; i < PHYS_PFN(size); i++) { -+ ipu6_iova = PFN_PHYS(iova->pfn_lo + i); -+ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, -+ ipu6_iova); -+ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, -+ DMA_BIDIRECTIONAL, attrs); -+ } -+ -+ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), -+ PFN_PHYS(iova_size(iova))); -+ -+ __dma_free_buffer(dev, pages, size, attrs); -+ -+ mmu->tlb_invalidate(mmu); -+ -+ __free_iova(&mmu->dmap->iovad, iova); -+ -+ kfree(info); -+} -+ -+static int ipu6_dma_mmap(struct device *dev, struct vm_area_struct *vma, -+ void *addr, dma_addr_t iova, size_t size, -+ unsigned long attrs) -+{ -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ size_t count = PHYS_PFN(PAGE_ALIGN(size)); -+ struct vm_info *info; -+ size_t i; -+ int ret; -+ -+ info = get_vm_info(mmu, iova); -+ if (!info) -+ return -EFAULT; -+ -+ if (!info->vaddr) -+ return -EFAULT; -+ -+ if (vma->vm_start & ~PAGE_MASK) -+ return -EINVAL; -+ -+ if (size > info->size) -+ return -EFAULT; -+ -+ for (i = 0; i < count; i++) { -+ ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i), -+ info->pages[i]); -+ if (ret < 0) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void ipu6_dma_unmap_sg(struct device *dev, -+ struct scatterlist *sglist, -+ int nents, enum dma_data_direction dir, -+ unsigned long attrs) -+{ -+ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ struct iova *iova = find_iova(&mmu->dmap->iovad, -+ PHYS_PFN(sg_dma_address(sglist))); -+ int i, npages, count; -+ struct scatterlist *sg; -+ dma_addr_t pci_dma_addr; -+ -+ if (!nents) -+ return; -+ -+ if (WARN_ON(!iova)) -+ return; -+ -+ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) -+ ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); -+ -+ /* get the nents as orig_nents given by caller */ -+ count = 0; -+ npages = iova_size(iova); -+ for_each_sg(sglist, sg, nents, i) { -+ if (sg_dma_len(sg) == 0 || -+ sg_dma_address(sg) == DMA_MAPPING_ERROR) -+ break; -+ -+ npages -= PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); -+ count++; -+ if (npages <= 0) -+ break; -+ } -+ -+ /* -+ * Before IPU6 mmu unmap, return the pci dma address back to sg -+ * assume the nents is less than orig_nents as the least granule -+ * is 1 SZ_4K page -+ */ -+ dev_dbg(dev, "trying to unmap concatenated %u ents\n", count); -+ for_each_sg(sglist, sg, count, i) { -+ dev_dbg(dev, "ipu unmap sg[%d] %pad\n", i, &sg_dma_address(sg)); -+ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, -+ sg_dma_address(sg)); -+ dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n", -+ &pci_dma_addr, i); -+ sg_dma_address(sg) = pci_dma_addr; -+ } -+ -+ dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n", -+ iova->pfn_lo, iova->pfn_hi); -+ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), -+ PFN_PHYS(iova_size(iova))); -+ -+ mmu->tlb_invalidate(mmu); -+ -+ dma_unmap_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); -+ -+ __free_iova(&mmu->dmap->iovad, iova); -+} -+ -+static int ipu6_dma_map_sg(struct device *dev, struct scatterlist *sglist, -+ int nents, enum dma_data_direction dir, -+ unsigned long attrs) -+{ -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; -+ struct scatterlist *sg; -+ struct iova *iova; -+ size_t npages = 0; -+ unsigned long iova_addr; -+ int i, count; -+ -+ for_each_sg(sglist, sg, nents, i) { -+ if (sg->offset) { -+ dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n", -+ i, sg->offset); -+ return -EFAULT; -+ } -+ } -+ -+ dev_dbg(dev, "pci_dma_map_sg trying to map %d ents\n", nents); -+ count = dma_map_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); -+ if (count <= 0) { -+ dev_err(dev, "pci_dma_map_sg %d ents failed\n", nents); -+ return 0; -+ } -+ -+ dev_dbg(dev, "pci_dma_map_sg %d ents mapped\n", count); -+ -+ for_each_sg(sglist, sg, count, i) -+ npages += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); -+ -+ iova = alloc_iova(&mmu->dmap->iovad, npages, -+ PHYS_PFN(dma_get_mask(dev)), 0); -+ if (!iova) -+ return 0; -+ -+ dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, -+ iova->pfn_hi); -+ -+ iova_addr = iova->pfn_lo; -+ for_each_sg(sglist, sg, count, i) { -+ int ret; -+ -+ dev_dbg(dev, "mapping entry %d: iova 0x%llx phy %pad size %d\n", -+ i, PFN_PHYS(iova_addr), &sg_dma_address(sg), -+ sg_dma_len(sg)); -+ -+ ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), -+ sg_dma_address(sg), -+ PAGE_ALIGN(sg_dma_len(sg))); -+ if (ret) -+ goto out_fail; -+ -+ sg_dma_address(sg) = PFN_PHYS(iova_addr); -+ -+ iova_addr += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); -+ } -+ -+ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) -+ ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); -+ -+ return count; -+ -+out_fail: -+ ipu6_dma_unmap_sg(dev, sglist, i, dir, attrs); -+ -+ return 0; -+} -+ -+/* -+ * Create scatter-list for the already allocated DMA buffer -+ */ -+static int ipu6_dma_get_sgtable(struct device *dev, struct sg_table *sgt, -+ void *cpu_addr, dma_addr_t handle, size_t size, -+ unsigned long attrs) -+{ -+ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; -+ struct vm_info *info; -+ int n_pages; -+ int ret = 0; -+ -+ info = get_vm_info(mmu, handle); -+ if (!info) -+ return -EFAULT; -+ -+ if (!info->vaddr) -+ return -EFAULT; -+ -+ if (WARN_ON(!info->pages)) -+ return -ENOMEM; -+ -+ n_pages = PHYS_PFN(PAGE_ALIGN(size)); -+ -+ ret = sg_alloc_table_from_pages(sgt, info->pages, n_pages, 0, size, -+ GFP_KERNEL); -+ if (ret) -+ dev_warn(dev, "IPU6 get sgt table failed\n"); -+ -+ return ret; -+} -+ -+const struct dma_map_ops ipu6_dma_ops = { -+ .alloc = ipu6_dma_alloc, -+ .free = ipu6_dma_free, -+ .mmap = ipu6_dma_mmap, -+ .map_sg = ipu6_dma_map_sg, -+ .unmap_sg = ipu6_dma_unmap_sg, -+ .sync_single_for_cpu = ipu6_dma_sync_single_for_cpu, -+ .sync_single_for_device = ipu6_dma_sync_single_for_cpu, -+ .sync_sg_for_cpu = ipu6_dma_sync_sg_for_cpu, -+ .sync_sg_for_device = ipu6_dma_sync_sg_for_cpu, -+ .get_sgtable = ipu6_dma_get_sgtable, -+}; -diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h -new file mode 100644 -index 000000000000..c75ad2462368 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h -@@ -0,0 +1,19 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_DMA_H -+#define IPU6_DMA_H -+ -+#include <linux/dma-map-ops.h> -+#include <linux/iova.h> -+ -+struct ipu6_mmu_info; -+ -+struct ipu6_dma_mapping { -+ struct ipu6_mmu_info *mmu_info; -+ struct iova_domain iovad; -+}; -+ -+extern const struct dma_map_ops ipu6_dma_ops; -+ -+#endif /* IPU6_DMA_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c -new file mode 100644 -index 000000000000..dc16d45187a8 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c -@@ -0,0 +1,845 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+#include <asm/barrier.h> -+ -+#include <linux/align.h> -+#include <linux/atomic.h> -+#include <linux/bitops.h> -+#include <linux/bits.h> -+#include <linux/bug.h> -+#include <linux/cacheflush.h> -+#include <linux/dma-mapping.h> -+#include <linux/err.h> -+#include <linux/gfp.h> -+#include <linux/io.h> -+#include <linux/iova.h> -+#include <linux/math.h> -+#include <linux/minmax.h> -+#include <linux/mm.h> -+#include <linux/pfn.h> -+#include <linux/slab.h> -+#include <linux/spinlock.h> -+#include <linux/types.h> -+ -+#include "ipu6.h" -+#include "ipu6-dma.h" -+#include "ipu6-mmu.h" -+#include "ipu6-platform-regs.h" -+ -+#define ISP_PAGE_SHIFT 12 -+#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) -+#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1)) -+ -+#define ISP_L1PT_SHIFT 22 -+#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) -+ -+#define ISP_L2PT_SHIFT 12 -+#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) -+ -+#define ISP_L1PT_PTES 1024 -+#define ISP_L2PT_PTES 1024 -+ -+#define ISP_PADDR_SHIFT 12 -+ -+#define REG_TLB_INVALIDATE 0x0000 -+ -+#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ -+#define REG_INFO 0x0008 -+ -+#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) -+ -+static void tlb_invalidate(struct ipu6_mmu *mmu) -+{ -+ unsigned long flags; -+ unsigned int i; -+ -+ spin_lock_irqsave(&mmu->ready_lock, flags); -+ if (!mmu->ready) { -+ spin_unlock_irqrestore(&mmu->ready_lock, flags); -+ return; -+ } -+ -+ for (i = 0; i < mmu->nr_mmus; i++) { -+ /* -+ * To avoid the HW bug induced dead lock in some of the IPU6 -+ * MMUs on successive invalidate calls, we need to first do a -+ * read to the page table base before writing the invalidate -+ * register. MMUs which need to implement this WA, will have -+ * the insert_read_before_invalidate flags set as true. -+ * Disregard the return value of the read. -+ */ -+ if (mmu->mmu_hw[i].insert_read_before_invalidate) -+ readl(mmu->mmu_hw[i].base + REG_L1_PHYS); -+ -+ writel(0xffffffff, mmu->mmu_hw[i].base + -+ REG_TLB_INVALIDATE); -+ /* -+ * The TLB invalidation is a "single cycle" (IOMMU clock cycles) -+ * When the actual MMIO write reaches the IPU6 TLB Invalidate -+ * register, wmb() will force the TLB invalidate out if the CPU -+ * attempts to update the IOMMU page table (or sooner). -+ */ -+ wmb(); -+ } -+ spin_unlock_irqrestore(&mmu->ready_lock, flags); -+} -+ -+#ifdef DEBUG -+static void page_table_dump(struct ipu6_mmu_info *mmu_info) -+{ -+ u32 l1_idx; -+ -+ dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n"); -+ -+ for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { -+ u32 l2_idx; -+ u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT; -+ -+ if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) -+ continue; -+ dev_dbg(mmu_info->dev, -+ "l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pa\n", -+ l1_idx, iova, iova + ISP_PAGE_SIZE, -+ TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx])); -+ -+ for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) { -+ u32 *l2_pt = mmu_info->l2_pts[l1_idx]; -+ u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT); -+ -+ if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval) -+ continue; -+ -+ dev_dbg(mmu_info->dev, -+ "\tl2 entry %u; iova 0x%8.8x, phys %pa\n", -+ l2_idx, iova2, -+ TBL_PHYS_ADDR(l2_pt[l2_idx])); -+ } -+ } -+ -+ dev_dbg(mmu_info->dev, "end IOMMU page table dump\n"); -+} -+#endif /* DEBUG */ -+ -+static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr) -+{ -+ dma_addr_t dma; -+ -+ dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(mmu_info->dev, dma)) -+ return 0; -+ -+ return dma; -+} -+ -+static int get_dummy_page(struct ipu6_mmu_info *mmu_info) -+{ -+ void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); -+ dma_addr_t dma; -+ -+ if (!pt) -+ return -ENOMEM; -+ -+ dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt); -+ -+ dma = map_single(mmu_info, pt); -+ if (!dma) { -+ dev_err(mmu_info->dev, "Failed to map dummy page\n"); -+ goto err_free_page; -+ } -+ -+ mmu_info->dummy_page = pt; -+ mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT; -+ -+ return 0; -+ -+err_free_page: -+ free_page((unsigned long)pt); -+ return -ENOMEM; -+} -+ -+static void free_dummy_page(struct ipu6_mmu_info *mmu_info) -+{ -+ dma_unmap_single(mmu_info->dev, -+ TBL_PHYS_ADDR(mmu_info->dummy_page_pteval), -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ free_page((unsigned long)mmu_info->dummy_page); -+} -+ -+static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) -+{ -+ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); -+ dma_addr_t dma; -+ unsigned int i; -+ -+ if (!pt) -+ return -ENOMEM; -+ -+ dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt); -+ -+ dma = map_single(mmu_info, pt); -+ if (!dma) { -+ dev_err(mmu_info->dev, "Failed to map l2pt page\n"); -+ goto err_free_page; -+ } -+ -+ for (i = 0; i < ISP_L2PT_PTES; i++) -+ pt[i] = mmu_info->dummy_page_pteval; -+ -+ mmu_info->dummy_l2_pt = pt; -+ mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT; -+ -+ return 0; -+ -+err_free_page: -+ free_page((unsigned long)pt); -+ return -ENOMEM; -+} -+ -+static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) -+{ -+ dma_unmap_single(mmu_info->dev, -+ TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval), -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ free_page((unsigned long)mmu_info->dummy_l2_pt); -+} -+ -+static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info) -+{ -+ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); -+ dma_addr_t dma; -+ unsigned int i; -+ -+ if (!pt) -+ return NULL; -+ -+ dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt); -+ -+ for (i = 0; i < ISP_L1PT_PTES; i++) -+ pt[i] = mmu_info->dummy_l2_pteval; -+ -+ dma = map_single(mmu_info, pt); -+ if (!dma) { -+ dev_err(mmu_info->dev, "Failed to map l1pt page\n"); -+ goto err_free_page; -+ } -+ -+ mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT; -+ dev_dbg(mmu_info->dev, "l1 pt %p mapped at %llx\n", pt, dma); -+ -+ return pt; -+ -+err_free_page: -+ free_page((unsigned long)pt); -+ return NULL; -+} -+ -+static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info) -+{ -+ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); -+ unsigned int i; -+ -+ if (!pt) -+ return NULL; -+ -+ dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); -+ -+ for (i = 0; i < ISP_L1PT_PTES; i++) -+ pt[i] = mmu_info->dummy_page_pteval; -+ -+ return pt; -+} -+ -+static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ phys_addr_t paddr, size_t size) -+{ -+ u32 l1_idx = iova >> ISP_L1PT_SHIFT; -+ u32 iova_start = iova; -+ u32 *l2_pt, *l2_virt; -+ unsigned int l2_idx; -+ unsigned long flags; -+ dma_addr_t dma; -+ u32 l1_entry; -+ -+ dev_dbg(mmu_info->dev, -+ "mapping l2 page table for l1 index %u (iova %8.8x)\n", -+ l1_idx, (u32)iova); -+ -+ spin_lock_irqsave(&mmu_info->lock, flags); -+ l1_entry = mmu_info->l1_pt[l1_idx]; -+ if (l1_entry == mmu_info->dummy_l2_pteval) { -+ l2_virt = mmu_info->l2_pts[l1_idx]; -+ if (likely(!l2_virt)) { -+ l2_virt = alloc_l2_pt(mmu_info); -+ if (!l2_virt) { -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ return -ENOMEM; -+ } -+ } -+ -+ dma = map_single(mmu_info, l2_virt); -+ if (!dma) { -+ dev_err(mmu_info->dev, "Failed to map l2pt page\n"); -+ free_page((unsigned long)l2_virt); -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ return -EINVAL; -+ } -+ -+ l1_entry = dma >> ISP_PADDR_SHIFT; -+ -+ dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n", -+ l1_idx, l2_virt); -+ mmu_info->l1_pt[l1_idx] = l1_entry; -+ mmu_info->l2_pts[l1_idx] = l2_virt; -+ clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx], -+ sizeof(mmu_info->l1_pt[l1_idx])); -+ } -+ -+ l2_pt = mmu_info->l2_pts[l1_idx]; -+ -+ dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry); -+ -+ paddr = ALIGN(paddr, ISP_PAGE_SIZE); -+ -+ l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; -+ -+ dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx, -+ l2_pt[l2_idx]); -+ if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) { -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ return -EINVAL; -+ } -+ -+ l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT; -+ -+ clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx])); -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ -+ dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx, -+ l2_pt[l2_idx]); -+ -+ return 0; -+} -+ -+static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ phys_addr_t paddr, size_t size) -+{ -+ u32 iova_start = round_down(iova, ISP_PAGE_SIZE); -+ u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE); -+ -+ dev_dbg(mmu_info->dev, -+ "mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr 0x%10.10llx\n", -+ iova_start, iova_end, size, paddr); -+ -+ return l2_map(mmu_info, iova_start, paddr, size); -+} -+ -+static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ phys_addr_t dummy, size_t size) -+{ -+ u32 l1_idx = iova >> ISP_L1PT_SHIFT; -+ u32 iova_start = iova; -+ unsigned int l2_idx; -+ size_t unmapped = 0; -+ unsigned long flags; -+ u32 *l2_pt; -+ -+ dev_dbg(mmu_info->dev, "unmapping l2 page table for l1 index %u (iova 0x%8.8lx)\n", -+ l1_idx, iova); -+ -+ spin_lock_irqsave(&mmu_info->lock, flags); -+ if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) { -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ dev_err(mmu_info->dev, -+ "unmap iova 0x%8.8lx l1 idx %u which was not mapped\n", -+ iova, l1_idx); -+ return 0; -+ } -+ -+ for (l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; -+ (iova_start & ISP_L1PT_MASK) + (l2_idx << ISP_PAGE_SHIFT) -+ < iova_start + size && l2_idx < ISP_L2PT_PTES; l2_idx++) { -+ l2_pt = mmu_info->l2_pts[l1_idx]; -+ dev_dbg(mmu_info->dev, -+ "unmap l2 index %u with pteval 0x%10.10llx\n", -+ l2_idx, TBL_PHYS_ADDR(l2_pt[l2_idx])); -+ l2_pt[l2_idx] = mmu_info->dummy_page_pteval; -+ -+ clflush_cache_range((void *)&l2_pt[l2_idx], -+ sizeof(l2_pt[l2_idx])); -+ unmapped++; -+ } -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ -+ return unmapped << ISP_PAGE_SHIFT; -+} -+ -+static size_t __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, -+ unsigned long iova, size_t size) -+{ -+ return l2_unmap(mmu_info, iova, 0, size); -+} -+ -+static int allocate_trash_buffer(struct ipu6_mmu *mmu) -+{ -+ unsigned int n_pages = PHYS_PFN(PAGE_ALIGN(IPU6_MMUV2_TRASH_RANGE)); -+ struct iova *iova; -+ unsigned int i; -+ dma_addr_t dma; -+ unsigned long iova_addr; -+ int ret; -+ -+ /* Allocate 8MB in iova range */ -+ iova = alloc_iova(&mmu->dmap->iovad, n_pages, -+ PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); -+ if (!iova) { -+ dev_err(mmu->dev, "cannot allocate iova range for trash\n"); -+ return -ENOMEM; -+ } -+ -+ dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0, -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) { -+ dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n"); -+ ret = -ENOMEM; -+ goto out_free_iova; -+ } -+ -+ mmu->pci_trash_page = dma; -+ -+ /* -+ * Map the 8MB iova address range to the same physical trash page -+ * mmu->trash_page which is already reserved at the probe -+ */ -+ iova_addr = iova->pfn_lo; -+ for (i = 0; i < n_pages; i++) { -+ ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), -+ mmu->pci_trash_page, PAGE_SIZE); -+ if (ret) { -+ dev_err(mmu->dev, -+ "mapping trash buffer range failed\n"); -+ goto out_unmap; -+ } -+ -+ iova_addr++; -+ } -+ -+ mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo); -+ dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n", -+ mmu->mmid, (unsigned int)mmu->iova_trash_page); -+ return 0; -+ -+out_unmap: -+ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), -+ PFN_PHYS(iova_size(iova))); -+ dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page, -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+out_free_iova: -+ __free_iova(&mmu->dmap->iovad, iova); -+ return ret; -+} -+ -+int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) -+{ -+ struct ipu6_mmu_info *mmu_info; -+ unsigned long flags; -+ unsigned int i; -+ -+ mmu_info = mmu->dmap->mmu_info; -+ -+ /* Initialise the each MMU HW block */ -+ for (i = 0; i < mmu->nr_mmus; i++) { -+ struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; -+ unsigned int j; -+ u16 block_addr; -+ -+ /* Write page table address per MMU */ -+ writel((phys_addr_t)mmu_info->l1_pt_dma, -+ mmu->mmu_hw[i].base + REG_L1_PHYS); -+ -+ /* Set info bits per MMU */ -+ writel(mmu->mmu_hw[i].info_bits, -+ mmu->mmu_hw[i].base + REG_INFO); -+ -+ /* Configure MMU TLB stream configuration for L1 */ -+ for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams; -+ block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) { -+ if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { -+ dev_err(mmu->dev, "invalid L1 configuration\n"); -+ return -EINVAL; -+ } -+ -+ /* Write block start address for each streams */ -+ writel(block_addr, mmu_hw->base + -+ mmu_hw->l1_stream_id_reg_offset + 4 * j); -+ } -+ -+ /* Configure MMU TLB stream configuration for L2 */ -+ for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams; -+ block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) { -+ if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { -+ dev_err(mmu->dev, "invalid L2 configuration\n"); -+ return -EINVAL; -+ } -+ -+ writel(block_addr, mmu_hw->base + -+ mmu_hw->l2_stream_id_reg_offset + 4 * j); -+ } -+ } -+ -+ if (!mmu->trash_page) { -+ int ret; -+ -+ mmu->trash_page = alloc_page(GFP_KERNEL); -+ if (!mmu->trash_page) { -+ dev_err(mmu->dev, "insufficient memory for trash buffer\n"); -+ return -ENOMEM; -+ } -+ -+ ret = allocate_trash_buffer(mmu); -+ if (ret) { -+ __free_page(mmu->trash_page); -+ mmu->trash_page = NULL; -+ dev_err(mmu->dev, "trash buffer allocation failed\n"); -+ return ret; -+ } -+ } -+ -+ spin_lock_irqsave(&mmu->ready_lock, flags); -+ mmu->ready = true; -+ spin_unlock_irqrestore(&mmu->ready_lock, flags); -+ -+ return 0; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6); -+ -+static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp) -+{ -+ struct ipu6_mmu_info *mmu_info; -+ int ret; -+ -+ mmu_info = kzalloc(sizeof(*mmu_info), GFP_KERNEL); -+ if (!mmu_info) -+ return NULL; -+ -+ mmu_info->aperture_start = 0; -+ mmu_info->aperture_end = DMA_BIT_MASK(isp->secure_mode ? -+ IPU6_MMU_ADDR_BITS : -+ IPU6_MMU_ADDR_BITS_NON_SECURE); -+ mmu_info->pgsize_bitmap = SZ_4K; -+ mmu_info->dev = &isp->pdev->dev; -+ -+ ret = get_dummy_page(mmu_info); -+ if (ret) -+ goto err_free_info; -+ -+ ret = alloc_dummy_l2_pt(mmu_info); -+ if (ret) -+ goto err_free_dummy_page; -+ -+ mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts)); -+ if (!mmu_info->l2_pts) -+ goto err_free_dummy_l2_pt; -+ -+ /* -+ * We always map the L1 page table (a single page as well as -+ * the L2 page tables). -+ */ -+ mmu_info->l1_pt = alloc_l1_pt(mmu_info); -+ if (!mmu_info->l1_pt) -+ goto err_free_l2_pts; -+ -+ spin_lock_init(&mmu_info->lock); -+ -+ dev_dbg(mmu_info->dev, "domain initialised\n"); -+ -+ return mmu_info; -+ -+err_free_l2_pts: -+ vfree(mmu_info->l2_pts); -+err_free_dummy_l2_pt: -+ free_dummy_l2_pt(mmu_info); -+err_free_dummy_page: -+ free_dummy_page(mmu_info); -+err_free_info: -+ kfree(mmu_info); -+ -+ return NULL; -+} -+ -+void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&mmu->ready_lock, flags); -+ mmu->ready = false; -+ spin_unlock_irqrestore(&mmu->ready_lock, flags); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6); -+ -+static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) -+{ -+ struct ipu6_dma_mapping *dmap; -+ -+ dmap = kzalloc(sizeof(*dmap), GFP_KERNEL); -+ if (!dmap) -+ return NULL; -+ -+ dmap->mmu_info = ipu6_mmu_alloc(isp); -+ if (!dmap->mmu_info) { -+ kfree(dmap); -+ return NULL; -+ } -+ -+ init_iova_domain(&dmap->iovad, SZ_4K, 1); -+ dmap->mmu_info->dmap = dmap; -+ -+ dev_dbg(&isp->pdev->dev, "alloc mapping\n"); -+ -+ iova_cache_get(); -+ -+ return dmap; -+} -+ -+phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, -+ dma_addr_t iova) -+{ -+ phys_addr_t phy_addr; -+ unsigned long flags; -+ u32 *l2_pt; -+ -+ spin_lock_irqsave(&mmu_info->lock, flags); -+ l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT]; -+ phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT]; -+ phy_addr <<= ISP_PAGE_SHIFT; -+ spin_unlock_irqrestore(&mmu_info->lock, flags); -+ -+ return phy_addr; -+} -+ -+static size_t ipu6_mmu_pgsize(unsigned long pgsize_bitmap, -+ unsigned long addr_merge, size_t size) -+{ -+ unsigned int pgsize_idx; -+ size_t pgsize; -+ -+ /* Max page size that still fits into 'size' */ -+ pgsize_idx = __fls(size); -+ -+ if (likely(addr_merge)) { -+ /* Max page size allowed by address */ -+ unsigned int align_pgsize_idx = __ffs(addr_merge); -+ -+ pgsize_idx = min(pgsize_idx, align_pgsize_idx); -+ } -+ -+ pgsize = (1UL << (pgsize_idx + 1)) - 1; -+ pgsize &= pgsize_bitmap; -+ -+ WARN_ON(!pgsize); -+ -+ /* pick the biggest page */ -+ pgsize_idx = __fls(pgsize); -+ pgsize = 1UL << pgsize_idx; -+ -+ return pgsize; -+} -+ -+size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ size_t size) -+{ -+ size_t unmapped_page, unmapped = 0; -+ unsigned int min_pagesz; -+ -+ /* find out the minimum page size supported */ -+ min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); -+ -+ /* -+ * The virtual address and the size of the mapping must be -+ * aligned (at least) to the size of the smallest page supported -+ * by the hardware -+ */ -+ if (!IS_ALIGNED(iova | size, min_pagesz)) { -+ dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n", -+ iova, size, min_pagesz); -+ return -EINVAL; -+ } -+ -+ /* -+ * Keep iterating until we either unmap 'size' bytes (or more) -+ * or we hit an area that isn't mapped. -+ */ -+ while (unmapped < size) { -+ size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap, -+ iova, size - unmapped); -+ -+ unmapped_page = __ipu6_mmu_unmap(mmu_info, iova, pgsize); -+ if (!unmapped_page) -+ break; -+ -+ dev_dbg(mmu_info->dev, "unmapped: iova 0x%lx size 0x%zx\n", -+ iova, unmapped_page); -+ -+ iova += unmapped_page; -+ unmapped += unmapped_page; -+ } -+ -+ return unmapped; -+} -+ -+int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ phys_addr_t paddr, size_t size) -+{ -+ unsigned long orig_iova = iova; -+ unsigned int min_pagesz; -+ size_t orig_size = size; -+ int ret = 0; -+ -+ if (mmu_info->pgsize_bitmap == 0UL) -+ return -ENODEV; -+ -+ /* find out the minimum page size supported */ -+ min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); -+ -+ /* -+ * both the virtual address and the physical one, as well as -+ * the size of the mapping, must be aligned (at least) to the -+ * size of the smallest page supported by the hardware -+ */ -+ if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) { -+ dev_err(mmu_info->dev, -+ "unaligned: iova %lx pa %pa size %zx min_pagesz %x\n", -+ iova, &paddr, size, min_pagesz); -+ return -EINVAL; -+ } -+ -+ dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n", -+ iova, &paddr, size); -+ -+ while (size) { -+ size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap, -+ iova | paddr, size); -+ -+ dev_dbg(mmu_info->dev, -+ "mapping: iova 0x%lx pa %pa pgsize 0x%zx\n", -+ iova, &paddr, pgsize); -+ -+ ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize); -+ if (ret) -+ break; -+ -+ iova += pgsize; -+ paddr += pgsize; -+ size -= pgsize; -+ } -+ -+ /* unroll mapping in case something went wrong */ -+ if (ret) -+ ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size); -+ -+ return ret; -+} -+ -+static void ipu6_mmu_destroy(struct ipu6_mmu *mmu) -+{ -+ struct ipu6_dma_mapping *dmap = mmu->dmap; -+ struct ipu6_mmu_info *mmu_info = dmap->mmu_info; -+ struct iova *iova; -+ u32 l1_idx; -+ -+ if (mmu->iova_trash_page) { -+ iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page)); -+ if (iova) { -+ /* unmap and free the trash buffer iova */ -+ ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo), -+ PFN_PHYS(iova_size(iova))); -+ __free_iova(&dmap->iovad, iova); -+ } else { -+ dev_err(mmu->dev, "trash buffer iova not found.\n"); -+ } -+ -+ mmu->iova_trash_page = 0; -+ dma_unmap_page(mmu_info->dev, mmu->pci_trash_page, -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ mmu->pci_trash_page = 0; -+ __free_page(mmu->trash_page); -+ } -+ -+ for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { -+ if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) { -+ dma_unmap_single(mmu_info->dev, -+ TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]), -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ free_page((unsigned long)mmu_info->l2_pts[l1_idx]); -+ } -+ } -+ -+ vfree(mmu_info->l2_pts); -+ free_dummy_page(mmu_info); -+ dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma), -+ PAGE_SIZE, DMA_BIDIRECTIONAL); -+ free_page((unsigned long)mmu_info->dummy_l2_pt); -+ free_page((unsigned long)mmu_info->l1_pt); -+ kfree(mmu_info); -+} -+ -+struct ipu6_mmu *ipu6_mmu_init(struct device *dev, -+ void __iomem *base, int mmid, -+ const struct ipu6_hw_variants *hw) -+{ -+ struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); -+ struct ipu6_mmu_pdata *pdata; -+ struct ipu6_mmu *mmu; -+ unsigned int i; -+ -+ if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) -+ return ERR_PTR(-EINVAL); -+ -+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); -+ if (!pdata) -+ return ERR_PTR(-ENOMEM); -+ -+ for (i = 0; i < hw->nr_mmus; i++) { -+ struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; -+ const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; -+ -+ if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || -+ src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) -+ return ERR_PTR(-EINVAL); -+ -+ *pdata_mmu = *src_mmu; -+ pdata_mmu->base = base + src_mmu->offset; -+ } -+ -+ mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); -+ if (!mmu) -+ return ERR_PTR(-ENOMEM); -+ -+ mmu->mmid = mmid; -+ mmu->mmu_hw = pdata->mmu_hw; -+ mmu->nr_mmus = hw->nr_mmus; -+ mmu->tlb_invalidate = tlb_invalidate; -+ mmu->ready = false; -+ INIT_LIST_HEAD(&mmu->vma_list); -+ spin_lock_init(&mmu->ready_lock); -+ -+ mmu->dmap = alloc_dma_mapping(isp); -+ if (!mmu->dmap) { -+ dev_err(dev, "can't alloc dma mapping\n"); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ return mmu; -+} -+ -+void ipu6_mmu_cleanup(struct ipu6_mmu *mmu) -+{ -+ struct ipu6_dma_mapping *dmap = mmu->dmap; -+ -+ ipu6_mmu_destroy(mmu); -+ mmu->dmap = NULL; -+ iova_cache_put(); -+ put_iova_domain(&dmap->iovad); -+ kfree(dmap); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h -new file mode 100644 -index 000000000000..95df7931a2e5 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h -@@ -0,0 +1,73 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_MMU_H -+#define IPU6_MMU_H -+ -+#define ISYS_MMID 1 -+#define PSYS_MMID 0 -+ -+#include <linux/list.h> -+#include <linux/spinlock_types.h> -+#include <linux/types.h> -+ -+struct device; -+struct page; -+struct ipu6_hw_variants; -+ -+struct ipu6_mmu_info { -+ struct device *dev; -+ -+ u32 *l1_pt; -+ u32 l1_pt_dma; -+ u32 **l2_pts; -+ -+ u32 *dummy_l2_pt; -+ u32 dummy_l2_pteval; -+ void *dummy_page; -+ u32 dummy_page_pteval; -+ -+ dma_addr_t aperture_start; -+ dma_addr_t aperture_end; -+ unsigned long pgsize_bitmap; -+ -+ spinlock_t lock; /* Serialize access to users */ -+ struct ipu6_dma_mapping *dmap; -+}; -+ -+struct ipu6_mmu { -+ struct list_head node; -+ -+ struct ipu6_mmu_hw *mmu_hw; -+ unsigned int nr_mmus; -+ unsigned int mmid; -+ -+ phys_addr_t pgtbl; -+ struct device *dev; -+ -+ struct ipu6_dma_mapping *dmap; -+ struct list_head vma_list; -+ -+ struct page *trash_page; -+ dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */ -+ dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */ -+ -+ bool ready; -+ spinlock_t ready_lock; /* Serialize access to bool ready */ -+ -+ void (*tlb_invalidate)(struct ipu6_mmu *mmu); -+}; -+ -+struct ipu6_mmu *ipu6_mmu_init(struct device *dev, -+ void __iomem *base, int mmid, -+ const struct ipu6_hw_variants *hw); -+void ipu6_mmu_cleanup(struct ipu6_mmu *mmu); -+int ipu6_mmu_hw_init(struct ipu6_mmu *mmu); -+void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu); -+int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ phys_addr_t paddr, size_t size); -+size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, -+ size_t size); -+phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, -+ dma_addr_t iova); -+#endif --- -2.43.2 - - -From a4363013580d8a552e8084faedbb98bd2482c057 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:20 +0800 -Subject: [PATCH 13/33] media: intel/ipu6: add syscom interfaces between - firmware and driver - -Syscom is an inter-process(or) communication mechanism between an IPU -and host. Syscom uses message queues for message exchange between IPU -and host. Each message queue has its consumer and producer, host queue -messages to firmware as the producer and then firmware to dequeue the -messages as consumer and vice versa. IPU and host use shared registers -or memory to reside the read and write indices which are updated by -consumer and producer. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-fw-com.c | 413 +++++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-fw-com.h | 47 +++ - 2 files changed, 460 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c -new file mode 100644 -index 000000000000..0f893f44e04c ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c -@@ -0,0 +1,413 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/device.h> -+#include <linux/dma-mapping.h> -+#include <linux/io.h> -+#include <linux/math.h> -+#include <linux/overflow.h> -+#include <linux/slab.h> -+#include <linux/types.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-fw-com.h" -+ -+/* -+ * FWCOM layer is a shared resource between FW and driver. It consist -+ * of token queues to both send and receive directions. Queue is simply -+ * an array of structures with read and write indexes to the queue. -+ * There are 1...n queues to both directions. Queues locates in -+ * system RAM and are mapped to ISP MMU so that both CPU and ISP can -+ * see the same buffer. Indexes are located in ISP DMEM so that FW code -+ * can poll those with very low latency and cost. CPU access to indexes is -+ * more costly but that happens only at message sending time and -+ * interrupt triggered message handling. CPU doesn't need to poll indexes. -+ * wr_reg / rd_reg are offsets to those dmem location. They are not -+ * the indexes itself. -+ */ -+ -+/* Shared structure between driver and FW - do not modify */ -+struct ipu6_fw_sys_queue { -+ u64 host_address; -+ u32 vied_address; -+ u32 size; -+ u32 token_size; -+ u32 wr_reg; /* reg number in subsystem's regmem */ -+ u32 rd_reg; -+ u32 _align; -+} __packed; -+ -+struct ipu6_fw_sys_queue_res { -+ u64 host_address; -+ u32 vied_address; -+ u32 reg; -+} __packed; -+ -+enum syscom_state { -+ /* Program load or explicit host setting should init to this */ -+ SYSCOM_STATE_UNINIT = 0x57a7e000, -+ /* SP Syscom sets this when it is ready for use */ -+ SYSCOM_STATE_READY = 0x57a7e001, -+ /* SP Syscom sets this when no more syscom accesses will happen */ -+ SYSCOM_STATE_INACTIVE = 0x57a7e002, -+}; -+ -+enum syscom_cmd { -+ /* Program load or explicit host setting should init to this */ -+ SYSCOM_COMMAND_UNINIT = 0x57a7f000, -+ /* Host Syscom requests syscom to become inactive */ -+ SYSCOM_COMMAND_INACTIVE = 0x57a7f001, -+}; -+ -+/* firmware config: data that sent from the host to SP via DDR */ -+/* Cell copies data into a context */ -+ -+struct ipu6_fw_syscom_config { -+ u32 firmware_address; -+ -+ u32 num_input_queues; -+ u32 num_output_queues; -+ -+ /* ISP pointers to an array of ipu6_fw_sys_queue structures */ -+ u32 input_queue; -+ u32 output_queue; -+ -+ /* ISYS / PSYS private data */ -+ u32 specific_addr; -+ u32 specific_size; -+}; -+ -+struct ipu6_fw_com_context { -+ struct ipu6_bus_device *adev; -+ void __iomem *dmem_addr; -+ int (*cell_ready)(struct ipu6_bus_device *adev); -+ void (*cell_start)(struct ipu6_bus_device *adev); -+ -+ void *dma_buffer; -+ dma_addr_t dma_addr; -+ unsigned int dma_size; -+ unsigned long attrs; -+ -+ struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */ -+ struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */ -+ -+ u32 config_vied_addr; -+ -+ unsigned int buttress_boot_offset; -+ void __iomem *base_addr; -+}; -+ -+#define FW_COM_WR_REG 0 -+#define FW_COM_RD_REG 4 -+ -+#define REGMEM_OFFSET 0 -+#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a -+ -+enum regmem_id { -+ /* pass pkg_dir address to SPC in non-secure mode */ -+ PKG_DIR_ADDR_REG = 0, -+ /* Tunit CFG blob for secure - provided by host.*/ -+ TUNIT_CFG_DWR_REG = 1, -+ /* syscom commands - modified by the host */ -+ SYSCOM_COMMAND_REG = 2, -+ /* Store interrupt status - updated by SP */ -+ SYSCOM_IRQ_REG = 3, -+ /* first syscom queue pointer register */ -+ SYSCOM_QPR_BASE_REG = 4 -+}; -+ -+#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000 -+#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \ -+ ((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4) -+ -+enum buttress_syscom_id { -+ /* pass syscom configuration to SPC */ -+ SYSCOM_CONFIG_ID = 0, -+ /* syscom state - modified by SP */ -+ SYSCOM_STATE_ID = 1, -+ /* syscom vtl0 addr mask */ -+ SYSCOM_VTL0_ADDR_MASK_ID = 2, -+ SYSCOM_ID_MAX -+}; -+ -+static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size, -+ unsigned int token_size, -+ struct ipu6_fw_sys_queue_res *res) -+{ -+ unsigned int buf_size = (size + 1) * token_size; -+ -+ q->size = size + 1; -+ q->token_size = token_size; -+ -+ /* acquire the shared buffer space */ -+ q->host_address = res->host_address; -+ res->host_address += buf_size; -+ q->vied_address = res->vied_address; -+ res->vied_address += buf_size; -+ -+ /* acquire the shared read and writer pointers */ -+ q->wr_reg = res->reg; -+ res->reg++; -+ q->rd_reg = res->reg; -+ res->reg++; -+} -+ -+void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, -+ struct ipu6_bus_device *adev, void __iomem *base) -+{ -+ size_t conf_size, inq_size, outq_size, specific_size; -+ struct ipu6_fw_syscom_config *config_host_addr; -+ unsigned int sizeinput = 0, sizeoutput = 0; -+ struct ipu6_fw_sys_queue_res res; -+ struct ipu6_fw_com_context *ctx; -+ struct device *dev = &adev->auxdev.dev; -+ size_t sizeall, offset; -+ unsigned long attrs = 0; -+ void *specific_host_addr; -+ unsigned int i; -+ -+ if (!cfg || !cfg->cell_start || !cfg->cell_ready) -+ return NULL; -+ -+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); -+ if (!ctx) -+ return NULL; -+ ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET; -+ ctx->adev = adev; -+ ctx->cell_start = cfg->cell_start; -+ ctx->cell_ready = cfg->cell_ready; -+ ctx->buttress_boot_offset = cfg->buttress_boot_offset; -+ ctx->base_addr = base; -+ -+ /* -+ * Allocate DMA mapped memory. Allocate one big chunk. -+ */ -+ /* Base cfg for FW */ -+ conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8); -+ /* Descriptions of the queues */ -+ inq_size = size_mul(cfg->num_input_queues, -+ sizeof(struct ipu6_fw_sys_queue)); -+ outq_size = size_mul(cfg->num_output_queues, -+ sizeof(struct ipu6_fw_sys_queue)); -+ /* FW specific information structure */ -+ specific_size = roundup(cfg->specific_size, 8); -+ -+ sizeall = conf_size + inq_size + outq_size + specific_size; -+ -+ for (i = 0; i < cfg->num_input_queues; i++) -+ sizeinput += size_mul(cfg->input[i].queue_size + 1, -+ cfg->input[i].token_size); -+ -+ for (i = 0; i < cfg->num_output_queues; i++) -+ sizeoutput += size_mul(cfg->output[i].queue_size + 1, -+ cfg->output[i].token_size); -+ -+ sizeall += sizeinput + sizeoutput; -+ -+ ctx->dma_buffer = dma_alloc_attrs(dev, sizeall, &ctx->dma_addr, -+ GFP_KERNEL, attrs); -+ ctx->attrs = attrs; -+ if (!ctx->dma_buffer) { -+ dev_err(dev, "failed to allocate dma memory\n"); -+ kfree(ctx); -+ return NULL; -+ } -+ -+ ctx->dma_size = sizeall; -+ -+ config_host_addr = ctx->dma_buffer; -+ ctx->config_vied_addr = ctx->dma_addr; -+ -+ offset = conf_size; -+ ctx->input_queue = ctx->dma_buffer + offset; -+ config_host_addr->input_queue = ctx->dma_addr + offset; -+ config_host_addr->num_input_queues = cfg->num_input_queues; -+ -+ offset += inq_size; -+ ctx->output_queue = ctx->dma_buffer + offset; -+ config_host_addr->output_queue = ctx->dma_addr + offset; -+ config_host_addr->num_output_queues = cfg->num_output_queues; -+ -+ /* copy firmware specific data */ -+ offset += outq_size; -+ specific_host_addr = ctx->dma_buffer + offset; -+ config_host_addr->specific_addr = ctx->dma_addr + offset; -+ config_host_addr->specific_size = cfg->specific_size; -+ if (cfg->specific_addr && cfg->specific_size) -+ memcpy(specific_host_addr, cfg->specific_addr, -+ cfg->specific_size); -+ -+ /* initialize input queues */ -+ offset += specific_size; -+ res.reg = SYSCOM_QPR_BASE_REG; -+ res.host_address = (u64)(ctx->dma_buffer + offset); -+ res.vied_address = ctx->dma_addr + offset; -+ for (i = 0; i < cfg->num_input_queues; i++) -+ ipu6_sys_queue_init(ctx->input_queue + i, -+ cfg->input[i].queue_size, -+ cfg->input[i].token_size, &res); -+ -+ /* initialize output queues */ -+ offset += sizeinput; -+ res.host_address = (u64)(ctx->dma_buffer + offset); -+ res.vied_address = ctx->dma_addr + offset; -+ for (i = 0; i < cfg->num_output_queues; i++) { -+ ipu6_sys_queue_init(ctx->output_queue + i, -+ cfg->output[i].queue_size, -+ cfg->output[i].token_size, &res); -+ } -+ -+ return ctx; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); -+ -+int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx) -+{ -+ /* write magic pattern to disable the tunit trace */ -+ writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4); -+ /* Check if SP is in valid state */ -+ if (!ctx->cell_ready(ctx->adev)) -+ return -EIO; -+ -+ /* store syscom uninitialized command */ -+ writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4); -+ -+ /* store syscom uninitialized state */ -+ writel(SYSCOM_STATE_UNINIT, -+ BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, -+ ctx->buttress_boot_offset, -+ SYSCOM_STATE_ID)); -+ -+ /* store firmware configuration address */ -+ writel(ctx->config_vied_addr, -+ BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, -+ ctx->buttress_boot_offset, -+ SYSCOM_CONFIG_ID)); -+ ctx->cell_start(ctx->adev); -+ -+ return 0; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); -+ -+int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx) -+{ -+ int state; -+ -+ state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, -+ ctx->buttress_boot_offset, -+ SYSCOM_STATE_ID)); -+ if (state != SYSCOM_STATE_READY) -+ return -EBUSY; -+ -+ /* set close request flag */ -+ writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr + -+ SYSCOM_COMMAND_REG * 4); -+ -+ return 0; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); -+ -+int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force) -+{ -+ /* check if release is forced, an verify cell state if it is not */ -+ if (!force && !ctx->cell_ready(ctx->adev)) -+ return -EBUSY; -+ -+ dma_free_attrs(&ctx->adev->auxdev.dev, ctx->dma_size, -+ ctx->dma_buffer, ctx->dma_addr, ctx->attrs); -+ kfree(ctx); -+ return 0; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); -+ -+bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx) -+{ -+ int state; -+ -+ state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, -+ ctx->buttress_boot_offset, -+ SYSCOM_STATE_ID)); -+ -+ return state == SYSCOM_STATE_READY; -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); -+ -+void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) -+{ -+ struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; -+ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; -+ unsigned int wr, rd; -+ unsigned int packets; -+ unsigned int index; -+ -+ wr = readl(q_dmem + FW_COM_WR_REG); -+ rd = readl(q_dmem + FW_COM_RD_REG); -+ -+ if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) -+ return NULL; -+ -+ if (wr < rd) -+ packets = rd - wr - 1; -+ else -+ packets = q->size - (wr - rd + 1); -+ -+ if (!packets) -+ return NULL; -+ -+ index = readl(q_dmem + FW_COM_WR_REG); -+ -+ return (void *)(q->host_address + index * q->token_size); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); -+ -+void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) -+{ -+ struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; -+ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; -+ unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1; -+ -+ if (wr >= q->size) -+ wr = 0; -+ -+ writel(wr, q_dmem + FW_COM_WR_REG); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); -+ -+void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) -+{ -+ struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; -+ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; -+ unsigned int wr, rd; -+ unsigned int packets; -+ -+ wr = readl(q_dmem + FW_COM_WR_REG); -+ rd = readl(q_dmem + FW_COM_RD_REG); -+ -+ if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) -+ return NULL; -+ -+ if (wr < rd) -+ wr += q->size; -+ -+ packets = wr - rd; -+ if (!packets) -+ return NULL; -+ -+ return (void *)(q->host_address + rd * q->token_size); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); -+ -+void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) -+{ -+ struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; -+ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; -+ unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1; -+ -+ if (rd >= q->size) -+ rd = 0; -+ -+ writel(rd, q_dmem + FW_COM_RD_REG); -+} -+EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); -diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.h b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h -new file mode 100644 -index 000000000000..660c406b3ac9 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_FW_COM_H -+#define IPU6_FW_COM_H -+ -+struct ipu6_fw_com_context; -+struct ipu6_bus_device; -+ -+struct ipu6_fw_syscom_queue_config { -+ unsigned int queue_size; /* tokens per queue */ -+ unsigned int token_size; /* bytes per token */ -+}; -+ -+#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0 -+ -+struct ipu6_fw_com_cfg { -+ unsigned int num_input_queues; -+ unsigned int num_output_queues; -+ struct ipu6_fw_syscom_queue_config *input; -+ struct ipu6_fw_syscom_queue_config *output; -+ -+ unsigned int dmem_addr; -+ -+ /* firmware-specific configuration data */ -+ void *specific_addr; -+ unsigned int specific_size; -+ int (*cell_ready)(struct ipu6_bus_device *adev); -+ void (*cell_start)(struct ipu6_bus_device *adev); -+ -+ unsigned int buttress_boot_offset; -+}; -+ -+void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, -+ struct ipu6_bus_device *adev, void __iomem *base); -+ -+int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx); -+bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx); -+int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx); -+int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force); -+ -+void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); -+void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); -+void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); -+void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); -+ -+#endif --- -2.43.2 - - -From e690b8a96eb4fbe1d948ad80047a9af7c601b761 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:21 +0800 -Subject: [PATCH 14/33] media: intel/ipu6: input system ABI between firmware - and driver - -Implement the input system firmware ABIs between the firmware and -driver - include stream configuration, control command, capture -request and response, etc. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 487 +++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 573 ++++++++++++++++++++ - 2 files changed, 1060 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c -new file mode 100644 -index 000000000000..e06c1c955d38 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c -@@ -0,0 +1,487 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/cacheflush.h> -+#include <linux/delay.h> -+#include <linux/device.h> -+#include <linux/io.h> -+#include <linux/spinlock.h> -+#include <linux/types.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-fw-com.h" -+#include "ipu6-isys.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+#include "ipu6-platform-regs.h" -+ -+static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { -+ "STREAM_OPEN", -+ "STREAM_START", -+ "STREAM_START_AND_CAPTURE", -+ "STREAM_CAPTURE", -+ "STREAM_STOP", -+ "STREAM_FLUSH", -+ "STREAM_CLOSE" -+}; -+ -+static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_fw_isys_proxy_resp_info_abi *resp; -+ int ret; -+ -+ resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); -+ if (!resp) -+ return 1; -+ -+ dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n", -+ resp->request_id, resp->error_info.error, -+ resp->error_info.error_details); -+ -+ ret = req_id == resp->request_id ? 0 : -EIO; -+ -+ ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); -+ -+ return ret; -+} -+ -+int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, -+ unsigned int req_id, -+ unsigned int index, -+ unsigned int offset, u32 value) -+{ -+ struct ipu6_fw_com_context *ctx = isys->fwcom; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_fw_proxy_send_queue_token *token; -+ unsigned int timeout = 1000; -+ int ret; -+ -+ dev_dbg(dev, -+ "proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n", -+ req_id, index, offset, value); -+ -+ token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); -+ if (!token) -+ return -EBUSY; -+ -+ token->request_id = req_id; -+ token->region_index = index; -+ token->offset = offset; -+ token->value = value; -+ ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); -+ -+ do { -+ usleep_range(100, 110); -+ ret = handle_proxy_response(isys, req_id); -+ if (!ret) -+ break; -+ if (ret == -EIO) { -+ dev_err(dev, "Proxy respond with unexpected id\n"); -+ break; -+ } -+ timeout--; -+ } while (ret && timeout); -+ -+ if (!timeout) -+ dev_err(dev, "Proxy response timed out\n"); -+ -+ return ret; -+} -+ -+int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, -+ const unsigned int stream_handle, -+ void *cpu_mapped_buf, -+ dma_addr_t dma_mapped_buf, -+ size_t size, u16 send_type) -+{ -+ struct ipu6_fw_com_context *ctx = isys->fwcom; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_fw_send_queue_token *token; -+ -+ if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE) -+ return -EINVAL; -+ -+ dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]); -+ -+ /* -+ * Time to flush cache in case we have some payload. Not all messages -+ * have that -+ */ -+ if (cpu_mapped_buf) -+ clflush_cache_range(cpu_mapped_buf, size); -+ -+ token = ipu6_send_get_token(ctx, -+ stream_handle + IPU6_BASE_MSG_SEND_QUEUES); -+ if (!token) -+ return -EBUSY; -+ -+ token->payload = dma_mapped_buf; -+ token->buf_handle = (unsigned long)cpu_mapped_buf; -+ token->send_type = send_type; -+ -+ ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES); -+ -+ return 0; -+} -+ -+int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, -+ const unsigned int stream_handle, u16 send_type) -+{ -+ return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, -+ send_type); -+} -+ -+int ipu6_fw_isys_close(struct ipu6_isys *isys) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ int retry = IPU6_ISYS_CLOSE_RETRY; -+ unsigned long flags; -+ void *fwcom; -+ int ret; -+ -+ /* -+ * Stop the isys fw. Actual close takes -+ * some time as the FW must stop its actions including code fetch -+ * to SP icache. -+ * spinlock to wait the interrupt handler to be finished -+ */ -+ spin_lock_irqsave(&isys->power_lock, flags); -+ ret = ipu6_fw_com_close(isys->fwcom); -+ fwcom = isys->fwcom; -+ isys->fwcom = NULL; -+ spin_unlock_irqrestore(&isys->power_lock, flags); -+ if (ret) -+ dev_err(dev, "Device close failure: %d\n", ret); -+ -+ /* release probably fails if the close failed. Let's try still */ -+ do { -+ usleep_range(400, 500); -+ ret = ipu6_fw_com_release(fwcom, 0); -+ retry--; -+ } while (ret && retry); -+ -+ if (ret) { -+ dev_err(dev, "Device release time out %d\n", ret); -+ spin_lock_irqsave(&isys->power_lock, flags); -+ isys->fwcom = fwcom; -+ spin_unlock_irqrestore(&isys->power_lock, flags); -+ } -+ -+ return ret; -+} -+ -+void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) -+{ -+ int ret; -+ -+ ret = ipu6_fw_com_release(isys->fwcom, 1); -+ if (ret < 0) -+ dev_warn(&isys->adev->auxdev.dev, -+ "Device busy, fw_com release failed."); -+ isys->fwcom = NULL; -+} -+ -+static void start_sp(struct ipu6_bus_device *adev) -+{ -+ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); -+ void __iomem *spc_regs_base = isys->pdata->base + -+ isys->pdata->ipdata->hw_variant.spc_offset; -+ u32 val = IPU6_ISYS_SPC_STATUS_START | -+ IPU6_ISYS_SPC_STATUS_RUN | -+ IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; -+ -+ val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0; -+ -+ writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); -+} -+ -+static int query_sp(struct ipu6_bus_device *adev) -+{ -+ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); -+ void __iomem *spc_regs_base = isys->pdata->base + -+ isys->pdata->ipdata->hw_variant.spc_offset; -+ u32 val; -+ -+ val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); -+ /* return true when READY == 1, START == 0 */ -+ val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START; -+ -+ return val == IPU6_ISYS_SPC_STATUS_READY; -+} -+ -+static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, -+ struct ipu6_fw_com_cfg *fwcom, -+ unsigned int num_streams) -+{ -+ unsigned int max_send_queues, max_sram_blocks, max_devq_size; -+ struct ipu6_fw_syscom_queue_config *input_queue_cfg; -+ struct ipu6_fw_syscom_queue_config *output_queue_cfg; -+ struct device *dev = &isys->adev->auxdev.dev; -+ int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY; -+ int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV; -+ int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG; -+ int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES; -+ int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES; -+ int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES; -+ struct ipu6_fw_isys_fw_config *isys_fw_cfg; -+ u32 num_in_message_queues; -+ unsigned int max_streams; -+ unsigned int size; -+ unsigned int i; -+ -+ max_streams = isys->pdata->ipdata->max_streams; -+ max_send_queues = isys->pdata->ipdata->max_send_queues; -+ max_sram_blocks = isys->pdata->ipdata->max_sram_blocks; -+ max_devq_size = isys->pdata->ipdata->max_devq_size; -+ num_in_message_queues = clamp(num_streams, 1U, max_streams); -+ isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL); -+ if (!isys_fw_cfg) -+ return -ENOMEM; -+ -+ isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES; -+ isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES; -+ isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues; -+ isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES; -+ /* Common msg/dev return queue */ -+ isys_fw_cfg->num_recv_queues[type_dev] = 0; -+ isys_fw_cfg->num_recv_queues[type_msg] = 1; -+ -+ size = sizeof(*input_queue_cfg) * max_send_queues; -+ input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); -+ if (!input_queue_cfg) -+ return -ENOMEM; -+ -+ size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES; -+ output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); -+ if (!output_queue_cfg) -+ return -ENOMEM; -+ -+ fwcom->input = input_queue_cfg; -+ fwcom->output = output_queue_cfg; -+ -+ fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + -+ isys_fw_cfg->num_send_queues[type_dev] + -+ isys_fw_cfg->num_send_queues[type_msg]; -+ -+ fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + -+ isys_fw_cfg->num_recv_queues[type_dev] + -+ isys_fw_cfg->num_recv_queues[type_msg]; -+ -+ /* SRAM partitioning. Equal partitioning is set. */ -+ for (i = 0; i < max_sram_blocks; i++) { -+ if (i < num_in_message_queues) -+ isys_fw_cfg->buffer_partition.num_gda_pages[i] = -+ (IPU6_DEVICE_GDA_NR_PAGES * -+ IPU6_DEVICE_GDA_VIRT_FACTOR) / -+ num_in_message_queues; -+ else -+ isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; -+ } -+ -+ /* FW assumes proxy interface at fwcom queue 0 */ -+ for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { -+ input_queue_cfg[i].token_size = -+ sizeof(struct ipu6_fw_proxy_send_queue_token); -+ input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE; -+ } -+ -+ for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) { -+ input_queue_cfg[base_dev_send + i].token_size = -+ sizeof(struct ipu6_fw_send_queue_token); -+ input_queue_cfg[base_dev_send + i].queue_size = max_devq_size; -+ } -+ -+ for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) { -+ input_queue_cfg[base_msg_send + i].token_size = -+ sizeof(struct ipu6_fw_send_queue_token); -+ input_queue_cfg[base_msg_send + i].queue_size = -+ IPU6_ISYS_SIZE_SEND_QUEUE; -+ } -+ -+ for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) { -+ output_queue_cfg[i].token_size = -+ sizeof(struct ipu6_fw_proxy_resp_queue_token); -+ output_queue_cfg[i].queue_size = -+ IPU6_ISYS_SIZE_PROXY_RECV_QUEUE; -+ } -+ /* There is no recv DEV queue */ -+ for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) { -+ output_queue_cfg[base_msg_recv + i].token_size = -+ sizeof(struct ipu6_fw_resp_queue_token); -+ output_queue_cfg[base_msg_recv + i].queue_size = -+ IPU6_ISYS_SIZE_RECV_QUEUE; -+ } -+ -+ fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; -+ fwcom->specific_addr = isys_fw_cfg; -+ fwcom->specific_size = sizeof(*isys_fw_cfg); -+ -+ return 0; -+} -+ -+int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ int retry = IPU6_ISYS_OPEN_RETRY; -+ struct ipu6_fw_com_cfg fwcom = { -+ .cell_start = start_sp, -+ .cell_ready = query_sp, -+ .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, -+ }; -+ int ret; -+ -+ ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); -+ -+ isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, -+ isys->pdata->base); -+ if (!isys->fwcom) { -+ dev_err(dev, "isys fw com prepare failed\n"); -+ return -EIO; -+ } -+ -+ ret = ipu6_fw_com_open(isys->fwcom); -+ if (ret) { -+ dev_err(dev, "isys fw com open failed %d\n", ret); -+ return ret; -+ } -+ -+ do { -+ usleep_range(400, 500); -+ if (ipu6_fw_com_ready(isys->fwcom)) -+ break; -+ retry--; -+ } while (retry > 0); -+ -+ if (!retry) { -+ dev_err(dev, "isys port open ready failed %d\n", ret); -+ ipu6_fw_isys_close(isys); -+ ret = -EIO; -+ } -+ -+ return ret; -+} -+ -+struct ipu6_fw_isys_resp_info_abi * -+ipu6_fw_isys_get_resp(void *context, unsigned int queue) -+{ -+ return ipu6_recv_get_token(context, queue); -+} -+ -+void ipu6_fw_isys_put_resp(void *context, unsigned int queue) -+{ -+ ipu6_recv_put_token(context, queue); -+} -+ -+void ipu6_fw_isys_dump_stream_cfg(struct device *dev, -+ struct ipu6_fw_isys_stream_cfg_data_abi *cfg) -+{ -+ unsigned int i; -+ -+ dev_dbg(dev, "-----------------------------------------------------\n"); -+ dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n"); -+ -+ dev_dbg(dev, "compfmt = %d\n", cfg->vc); -+ dev_dbg(dev, "src = %d\n", cfg->src); -+ dev_dbg(dev, "vc = %d\n", cfg->vc); -+ dev_dbg(dev, "isl_use = %d\n", cfg->isl_use); -+ dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type); -+ -+ dev_dbg(dev, "send_irq_sof_discarded = %d\n", -+ cfg->send_irq_sof_discarded); -+ dev_dbg(dev, "send_irq_eof_discarded = %d\n", -+ cfg->send_irq_eof_discarded); -+ dev_dbg(dev, "send_resp_sof_discarded = %d\n", -+ cfg->send_resp_sof_discarded); -+ dev_dbg(dev, "send_resp_eof_discarded = %d\n", -+ cfg->send_resp_eof_discarded); -+ -+ dev_dbg(dev, "crop:\n"); -+ dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset, -+ cfg->crop.top_offset); -+ dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset, -+ cfg->crop.bottom_offset); -+ -+ dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins); -+ for (i = 0; i < cfg->nof_input_pins; i++) { -+ dev_dbg(dev, "input pin[%d]:\n", i); -+ dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt); -+ dev_dbg(dev, "\t.mipi_store_mode = %d\n", -+ cfg->input_pins[i].mipi_store_mode); -+ dev_dbg(dev, "\t.bits_per_pix = %d\n", -+ cfg->input_pins[i].bits_per_pix); -+ dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", -+ cfg->input_pins[i].mapped_dt); -+ dev_dbg(dev, "\t.input_res = %dx%d\n", -+ cfg->input_pins[i].input_res.width, -+ cfg->input_pins[i].input_res.height); -+ dev_dbg(dev, "\t.mipi_decompression = %d\n", -+ cfg->input_pins[i].mipi_decompression); -+ dev_dbg(dev, "\t.capture_mode = %d\n", -+ cfg->input_pins[i].capture_mode); -+ } -+ -+ dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins); -+ for (i = 0; i < cfg->nof_output_pins; i++) { -+ dev_dbg(dev, "output_pin[%d]:\n", i); -+ dev_dbg(dev, "\t.input_pin_id = %d\n", -+ cfg->output_pins[i].input_pin_id); -+ dev_dbg(dev, "\t.output_res = %dx%d\n", -+ cfg->output_pins[i].output_res.width, -+ cfg->output_pins[i].output_res.height); -+ dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); -+ dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt); -+ dev_dbg(dev, "\t.payload_buf_size = %d\n", -+ cfg->output_pins[i].payload_buf_size); -+ dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); -+ dev_dbg(dev, "\t.watermark_in_lines = %d\n", -+ cfg->output_pins[i].watermark_in_lines); -+ dev_dbg(dev, "\t.send_irq = %d\n", -+ cfg->output_pins[i].send_irq); -+ dev_dbg(dev, "\t.reserve_compression = %d\n", -+ cfg->output_pins[i].reserve_compression); -+ dev_dbg(dev, "\t.snoopable = %d\n", -+ cfg->output_pins[i].snoopable); -+ dev_dbg(dev, "\t.error_handling_enable = %d\n", -+ cfg->output_pins[i].error_handling_enable); -+ dev_dbg(dev, "\t.sensor_type = %d\n", -+ cfg->output_pins[i].sensor_type); -+ } -+ dev_dbg(dev, "-----------------------------------------------------\n"); -+} -+ -+void -+ipu6_fw_isys_dump_frame_buff_set(struct device *dev, -+ struct ipu6_fw_isys_frame_buff_set_abi *buf, -+ unsigned int outputs) -+{ -+ unsigned int i; -+ -+ dev_dbg(dev, "-----------------------------------------------------\n"); -+ dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); -+ -+ for (i = 0; i < outputs; i++) { -+ dev_dbg(dev, "output_pin[%d]:\n", i); -+ dev_dbg(dev, "\t.out_buf_id = %llu\n", -+ buf->output_pins[i].out_buf_id); -+ dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); -+ dev_dbg(dev, "\t.compress = %d\n", -+ buf->output_pins[i].compress); -+ } -+ -+ dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof); -+ dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof); -+ dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof); -+ dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof); -+ dev_dbg(dev, "send_irq_capture_ack = 0x%x\n", -+ buf->send_irq_capture_ack); -+ dev_dbg(dev, "send_irq_capture_done = 0x%x\n", -+ buf->send_irq_capture_done); -+ dev_dbg(dev, "send_resp_capture_ack = 0x%x\n", -+ buf->send_resp_capture_ack); -+ dev_dbg(dev, "send_resp_capture_done = 0x%x\n", -+ buf->send_resp_capture_done); -+ -+ dev_dbg(dev, "-----------------------------------------------------\n"); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h -new file mode 100644 -index 000000000000..a7ffa0e22bf0 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h -@@ -0,0 +1,573 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_FW_ISYS_H -+#define IPU6_FW_ISYS_H -+ -+#include <linux/types.h> -+ -+struct device; -+struct ipu6_isys; -+ -+/* Max number of Input/Output Pins */ -+#define IPU6_MAX_IPINS 4 -+ -+#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1) -+ -+#define IPU6_STREAM_ID_MAX 16 -+#define IPU6_NONSECURE_STREAM_ID_MAX 12 -+#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX) -+#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX) -+#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX) -+#define IPU6SE_STREAM_ID_MAX 8 -+#define IPU6SE_NONSECURE_STREAM_ID_MAX 4 -+#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX) -+#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX) -+#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX) -+ -+/* Single return queue for all streams/commands type */ -+#define IPU6_N_MAX_MSG_RECV_QUEUES 1 -+/* Single device queue for high priority commands (bypass in-order queue) */ -+#define IPU6_N_MAX_DEV_SEND_QUEUES 1 -+/* Single dedicated send queue for proxy interface */ -+#define IPU6_N_MAX_PROXY_SEND_QUEUES 1 -+/* Single dedicated recv queue for proxy interface */ -+#define IPU6_N_MAX_PROXY_RECV_QUEUES 1 -+/* Send queues layout */ -+#define IPU6_BASE_PROXY_SEND_QUEUES 0 -+#define IPU6_BASE_DEV_SEND_QUEUES \ -+ (IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES) -+#define IPU6_BASE_MSG_SEND_QUEUES \ -+ (IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES) -+/* Recv queues layout */ -+#define IPU6_BASE_PROXY_RECV_QUEUES 0 -+#define IPU6_BASE_MSG_RECV_QUEUES \ -+ (IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES) -+#define IPU6_N_MAX_RECV_QUEUES \ -+ (IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES) -+ -+#define IPU6_N_MAX_SEND_QUEUES \ -+ (IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES) -+#define IPU6SE_N_MAX_SEND_QUEUES \ -+ (IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES) -+ -+/* Max number of supported input pins routed in ISL */ -+#define IPU6_MAX_IPINS_IN_ISL 2 -+ -+/* Max number of planes for frame formats supported by the FW */ -+#define IPU6_PIN_PLANES_MAX 4 -+ -+#define IPU6_FW_ISYS_SENSOR_TYPE_START 14 -+#define IPU6_FW_ISYS_SENSOR_TYPE_END 19 -+#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6 -+#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11 -+/* -+ * Device close takes some time from last ack message to actual stopping -+ * of the SP processor. As long as the SP processor runs we can't proceed with -+ * clean up of resources. -+ */ -+#define IPU6_ISYS_OPEN_RETRY 2000 -+#define IPU6_ISYS_CLOSE_RETRY 2000 -+#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000) -+ -+enum ipu6_fw_isys_resp_type { -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, -+ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, -+ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK, -+ IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, -+ IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, -+ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED, -+ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED, -+ IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED, -+ IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED, -+ IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, -+ N_IPU6_FW_ISYS_RESP_TYPE -+}; -+ -+enum ipu6_fw_isys_send_type { -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_START, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE, -+ N_IPU6_FW_ISYS_SEND_TYPE -+}; -+ -+enum ipu6_fw_isys_queue_type { -+ IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0, -+ IPU6_FW_ISYS_QUEUE_TYPE_DEV, -+ IPU6_FW_ISYS_QUEUE_TYPE_MSG, -+ N_IPU6_FW_ISYS_QUEUE_TYPE -+}; -+ -+enum ipu6_fw_isys_stream_source { -+ IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_1, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_2, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_3, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_4, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_5, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_6, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_7, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_8, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_9, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_10, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_11, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_12, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_13, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_14, -+ IPU6_FW_ISYS_STREAM_SRC_PORT_15, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8, -+ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9, -+ N_IPU6_FW_ISYS_STREAM_SRC -+}; -+ -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3 -+ -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \ -+ IPU6_FW_ISYS_STREAM_SRC_PORT_6 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \ -+ IPU6_FW_ISYS_STREAM_SRC_PORT_7 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \ -+ IPU6_FW_ISYS_STREAM_SRC_PORT_8 -+#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \ -+ IPU6_FW_ISYS_STREAM_SRC_PORT_9 -+ -+#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0 -+#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1 -+ -+/** -+ * enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec -+ * supports up to 4 virtual per physical channel -+ */ -+enum ipu6_fw_isys_mipi_vc { -+ IPU6_FW_ISYS_MIPI_VC_0 = 0, -+ IPU6_FW_ISYS_MIPI_VC_1, -+ IPU6_FW_ISYS_MIPI_VC_2, -+ IPU6_FW_ISYS_MIPI_VC_3, -+ N_IPU6_FW_ISYS_MIPI_VC -+}; -+ -+enum ipu6_fw_isys_frame_format_type { -+ IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */ -+ /* 12 bit YUV 420, Intel proprietary tiled format */ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY, -+ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */ -+ IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */ -+ /* Internal format, 2 y lines followed by a uvinterleaved line */ -+ IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */ -+ /** -+ * 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit -+ * value, 5 bits for R, 6 bits for G and 5 bits for B. -+ */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RGB565, -+ IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */ -+ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */ -+ IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */ -+ IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */ -+ N_IPU6_FW_ISYS_FRAME_FORMAT -+}; -+ -+#define IPU6_FW_ISYS_FRAME_FORMAT_RAW (IPU6_FW_ISYS_FRAME_FORMAT_RAW16) -+ -+enum ipu6_fw_isys_pin_type { -+ /* captured as MIPI packets */ -+ IPU6_FW_ISYS_PIN_TYPE_MIPI = 0, -+ /* captured through the SoC path */ -+ IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3, -+}; -+ -+/** -+ * enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach -+ * MIPI SRAM with the long packet header or -+ * if not, then only option is to capture it with pin type MIPI. -+ */ -+enum ipu6_fw_isys_mipi_store_mode { -+ IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0, -+ IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER, -+ N_IPU6_FW_ISYS_MIPI_STORE_MODE -+}; -+ -+enum ipu6_fw_isys_capture_mode { -+ IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0, -+ IPU6_FW_ISYS_CAPTURE_MODE_BURST, -+ N_IPU6_FW_ISYS_CAPTURE_MODE, -+}; -+ -+enum ipu6_fw_isys_sensor_mode { -+ IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0, -+ IPU6_FW_ISYS_SENSOR_MODE_TOBII, -+ N_IPU6_FW_ISYS_SENSOR_MODE, -+}; -+ -+enum ipu6_fw_isys_error { -+ IPU6_FW_ISYS_ERROR_NONE = 0, -+ IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY, -+ IPU6_FW_ISYS_ERROR_HW_CONSISTENCY, -+ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE, -+ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION, -+ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION, -+ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION, -+ IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES, -+ IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO, -+ IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO, -+ IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC, -+ IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION, -+ IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL, -+ N_IPU6_FW_ISYS_ERROR -+}; -+ -+enum ipu6_fw_proxy_error { -+ IPU6_FW_PROXY_ERROR_NONE = 0, -+ IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION, -+ IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET, -+ N_IPU6_FW_PROXY_ERROR -+}; -+ -+/* firmware ABI structure below are aligned in firmware, no need pack */ -+struct ipu6_fw_isys_buffer_partition_abi { -+ u32 num_gda_pages[IPU6_STREAM_ID_MAX]; -+}; -+ -+struct ipu6_fw_isys_fw_config { -+ struct ipu6_fw_isys_buffer_partition_abi buffer_partition; -+ u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; -+ u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; -+}; -+ -+/** -+ * struct ipu6_fw_isys_resolution_abi: Generic resolution structure. -+ */ -+struct ipu6_fw_isys_resolution_abi { -+ u32 width; -+ u32 height; -+}; -+ -+/** -+ * struct ipu6_fw_isys_output_pin_payload_abi -+ * @out_buf_id: Points to output pin buffer - buffer identifier -+ * @addr: Points to output pin buffer - CSS Virtual Address -+ * @compress: Request frame compression (1), or not (0) -+ */ -+struct ipu6_fw_isys_output_pin_payload_abi { -+ u64 out_buf_id; -+ u32 addr; -+ u32 compress; -+}; -+ -+/** -+ * struct ipu6_fw_isys_output_pin_info_abi -+ * @output_res: output pin resolution -+ * @stride: output stride in Bytes (not valid for statistics) -+ * @watermark_in_lines: pin watermark level in lines -+ * @payload_buf_size: minimum size in Bytes of all buffers that will be -+ * supplied for capture on this pin -+ * @send_irq: assert if pin event should trigger irq -+ * @pt: pin type -real format "enum ipu6_fw_isys_pin_type" -+ * @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type" -+ * @input_pin_id: related input pin id -+ * @reserve_compression: reserve compression resources for pin -+ */ -+struct ipu6_fw_isys_output_pin_info_abi { -+ struct ipu6_fw_isys_resolution_abi output_res; -+ u32 stride; -+ u32 watermark_in_lines; -+ u32 payload_buf_size; -+ u32 ts_offsets[IPU6_PIN_PLANES_MAX]; -+ u32 s2m_pixel_soc_pixel_remapping; -+ u32 csi_be_soc_pixel_remapping; -+ u8 send_irq; -+ u8 input_pin_id; -+ u8 pt; -+ u8 ft; -+ u8 reserved; -+ u8 reserve_compression; -+ u8 snoopable; -+ u8 error_handling_enable; -+ u32 sensor_type; -+}; -+ -+/** -+ * struct ipu6_fw_isys_input_pin_info_abi -+ * @input_res: input resolution -+ * @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type) -+ * @mipi_store_mode: defines if legacy long packet header will be stored or -+ * discarded if discarded, output pin type for this -+ * input pin can only be MIPI -+ * (enum ipu6_fw_isys_mipi_store_mode) -+ * @bits_per_pix: native bits per pixel -+ * @mapped_dt: actual data type from sensor -+ * @mipi_decompression: defines which compression will be in mipi backend -+ * @crop_first_and_last_lines Control whether to crop the -+ * first and last line of the -+ * input image. Crop done by HW -+ * device. -+ * @capture_mode: mode of capture, regular or burst, default value is regular -+ */ -+struct ipu6_fw_isys_input_pin_info_abi { -+ struct ipu6_fw_isys_resolution_abi input_res; -+ u8 dt; -+ u8 mipi_store_mode; -+ u8 bits_per_pix; -+ u8 mapped_dt; -+ u8 mipi_decompression; -+ u8 crop_first_and_last_lines; -+ u8 capture_mode; -+ u8 reserved; -+}; -+ -+/** -+ * struct ipu6_fw_isys_cropping_abi - cropping coordinates -+ */ -+struct ipu6_fw_isys_cropping_abi { -+ s32 top_offset; -+ s32 left_offset; -+ s32 bottom_offset; -+ s32 right_offset; -+}; -+ -+/** -+ * struct ipu6_fw_isys_stream_cfg_data_abi -+ * ISYS stream configuration data structure -+ * @crop: for extended use and is not used in FW currently -+ * @input_pins: input pin descriptors -+ * @output_pins: output pin descriptors -+ * @compfmt: de-compression setting for User Defined Data -+ * @nof_input_pins: number of input pins -+ * @nof_output_pins: number of output pins -+ * @send_irq_sof_discarded: send irq on discarded frame sof response -+ * - if '1' it will override the send_resp_sof_discarded -+ * and send the response -+ * - if '0' the send_resp_sof_discarded will determine -+ * whether to send the response -+ * @send_irq_eof_discarded: send irq on discarded frame eof response -+ * - if '1' it will override the send_resp_eof_discarded -+ * and send the response -+ * - if '0' the send_resp_eof_discarded will determine -+ * whether to send the response -+ * @send_resp_sof_discarded: send response for discarded frame sof detected, -+ * used only when send_irq_sof_discarded is '0' -+ * @send_resp_eof_discarded: send response for discarded frame eof detected, -+ * used only when send_irq_eof_discarded is '0' -+ * @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1 -+ * @vc: MIPI Virtual Channel (up to 4 virtual per physical channel) -+ * @isl_use: indicates whether stream requires ISL and how -+ * @sensor_type: type of connected sensor, tobii or others, default is 0 -+ */ -+struct ipu6_fw_isys_stream_cfg_data_abi { -+ struct ipu6_fw_isys_cropping_abi crop; -+ struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS]; -+ struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS]; -+ u32 compfmt; -+ u8 nof_input_pins; -+ u8 nof_output_pins; -+ u8 send_irq_sof_discarded; -+ u8 send_irq_eof_discarded; -+ u8 send_resp_sof_discarded; -+ u8 send_resp_eof_discarded; -+ u8 src; -+ u8 vc; -+ u8 isl_use; -+ u8 sensor_type; -+ u8 reserved; -+ u8 reserved2; -+}; -+ -+/** -+ * struct ipu6_fw_isys_frame_buff_set - frame buffer set -+ * @output_pins: output pin addresses -+ * @send_irq_sof: send irq on frame sof response -+ * - if '1' it will override the send_resp_sof and -+ * send the response -+ * - if '0' the send_resp_sof will determine whether to -+ * send the response -+ * @send_irq_eof: send irq on frame eof response -+ * - if '1' it will override the send_resp_eof and -+ * send the response -+ * - if '0' the send_resp_eof will determine whether to -+ * send the response -+ * @send_resp_sof: send response for frame sof detected, -+ * used only when send_irq_sof is '0' -+ * @send_resp_eof: send response for frame eof detected, -+ * used only when send_irq_eof is '0' -+ * @send_resp_capture_ack: send response for capture ack event -+ * @send_resp_capture_done: send response for capture done event -+ */ -+struct ipu6_fw_isys_frame_buff_set_abi { -+ struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS]; -+ u8 send_irq_sof; -+ u8 send_irq_eof; -+ u8 send_irq_capture_ack; -+ u8 send_irq_capture_done; -+ u8 send_resp_sof; -+ u8 send_resp_eof; -+ u8 send_resp_capture_ack; -+ u8 send_resp_capture_done; -+ u8 reserved[8]; -+}; -+ -+/** -+ * struct ipu6_fw_isys_error_info_abi -+ * @error: error code if something went wrong -+ * @error_details: depending on error code, it may contain additional error info -+ */ -+struct ipu6_fw_isys_error_info_abi { -+ u32 error; -+ u32 error_details; -+}; -+ -+/** -+ * struct ipu6_fw_isys_resp_info_comm -+ * @pin: this var is only valid for pin event related responses, -+ * contains pin addresses -+ * @error_info: error information from the FW -+ * @timestamp: Time information for event if available -+ * @stream_handle: stream id the response corresponds to -+ * @type: response type (enum ipu6_fw_isys_resp_type) -+ * @pin_id: pin id that the pin payload corresponds to -+ */ -+struct ipu6_fw_isys_resp_info_abi { -+ u64 buf_id; -+ struct ipu6_fw_isys_output_pin_payload_abi pin; -+ struct ipu6_fw_isys_error_info_abi error_info; -+ u32 timestamp[2]; -+ u8 stream_handle; -+ u8 type; -+ u8 pin_id; -+ u8 reserved; -+ u32 reserved2; -+}; -+ -+/** -+ * struct ipu6_fw_isys_proxy_error_info_comm -+ * @proxy_error: error code if something went wrong -+ * @proxy_error_details: depending on error code, it may contain additional -+ * error info -+ */ -+struct ipu6_fw_isys_proxy_error_info_abi { -+ u32 error; -+ u32 error_details; -+}; -+ -+struct ipu6_fw_isys_proxy_resp_info_abi { -+ u32 request_id; -+ struct ipu6_fw_isys_proxy_error_info_abi error_info; -+}; -+ -+/** -+ * struct ipu6_fw_proxy_write_queue_token -+ * @request_id: update id for the specific proxy write request -+ * @region_index: Region id for the proxy write request -+ * @offset: Offset of the write request according to the base address -+ * of the region -+ * @value: Value that is requested to be written with the proxy write request -+ */ -+struct ipu6_fw_proxy_write_queue_token { -+ u32 request_id; -+ u32 region_index; -+ u32 offset; -+ u32 value; -+}; -+ -+/** -+ * struct ipu6_fw_resp_queue_token -+ */ -+struct ipu6_fw_resp_queue_token { -+ struct ipu6_fw_isys_resp_info_abi resp_info; -+}; -+ -+/** -+ * struct ipu6_fw_send_queue_token -+ */ -+struct ipu6_fw_send_queue_token { -+ u64 buf_handle; -+ u32 payload; -+ u16 send_type; -+ u16 stream_id; -+}; -+ -+/** -+ * struct ipu6_fw_proxy_resp_queue_token -+ */ -+struct ipu6_fw_proxy_resp_queue_token { -+ struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info; -+}; -+ -+/** -+ * struct ipu6_fw_proxy_send_queue_token -+ */ -+struct ipu6_fw_proxy_send_queue_token { -+ u32 request_id; -+ u32 region_index; -+ u32 offset; -+ u32 value; -+}; -+ -+void -+ipu6_fw_isys_dump_stream_cfg(struct device *dev, -+ struct ipu6_fw_isys_stream_cfg_data_abi *cfg); -+void -+ipu6_fw_isys_dump_frame_buff_set(struct device *dev, -+ struct ipu6_fw_isys_frame_buff_set_abi *buf, -+ unsigned int outputs); -+int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); -+int ipu6_fw_isys_close(struct ipu6_isys *isys); -+int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, -+ const unsigned int stream_handle, u16 send_type); -+int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, -+ const unsigned int stream_handle, -+ void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, -+ size_t size, u16 send_type); -+int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, -+ unsigned int req_id, -+ unsigned int index, -+ unsigned int offset, u32 value); -+void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); -+struct ipu6_fw_isys_resp_info_abi * -+ipu6_fw_isys_get_resp(void *context, unsigned int queue); -+void ipu6_fw_isys_put_resp(void *context, unsigned int queue); -+#endif --- -2.43.2 - - -From e69a245fa4db99e5983170aab73f962dc99d3149 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:22 +0800 -Subject: [PATCH 15/33] media: intel/ipu6: add IPU6 CSI2 receiver v4l2 - sub-device - -Input system CSI2 receiver is exposed as a v4l2 sub-device. -Each CSI2 sub-device represent one single CSI2 hardware port -which be linked with external sub-device such camera sensor -by linked with ISYS CSI2's sink pad. CSI2 source pad is linked -to the sink pad of video capture device. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 666 ++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 81 +++ - .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 381 ++++++++++ - .../media/pci/intel/ipu6/ipu6-isys-subdev.h | 61 ++ - .../intel/ipu6/ipu6-platform-isys-csi2-reg.h | 189 +++++ - 5 files changed, 1378 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -new file mode 100644 -index 000000000000..ac9fa3e0d7ab ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -@@ -0,0 +1,666 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/atomic.h> -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/delay.h> -+#include <linux/device.h> -+#include <linux/err.h> -+#include <linux/io.h> -+#include <linux/minmax.h> -+#include <linux/sprintf.h> -+ -+#include <media/media-entity.h> -+#include <media/v4l2-ctrls.h> -+#include <media/v4l2-device.h> -+#include <media/v4l2-event.h> -+#include <media/v4l2-subdev.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-isys-subdev.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+ -+static const u32 csi2_supported_codes[] = { -+ MEDIA_BUS_FMT_RGB565_1X16, -+ MEDIA_BUS_FMT_RGB888_1X24, -+ MEDIA_BUS_FMT_UYVY8_1X16, -+ MEDIA_BUS_FMT_YUYV8_1X16, -+ MEDIA_BUS_FMT_SBGGR10_1X10, -+ MEDIA_BUS_FMT_SGBRG10_1X10, -+ MEDIA_BUS_FMT_SGRBG10_1X10, -+ MEDIA_BUS_FMT_SRGGB10_1X10, -+ MEDIA_BUS_FMT_SBGGR12_1X12, -+ MEDIA_BUS_FMT_SGBRG12_1X12, -+ MEDIA_BUS_FMT_SGRBG12_1X12, -+ MEDIA_BUS_FMT_SRGGB12_1X12, -+ MEDIA_BUS_FMT_SBGGR8_1X8, -+ MEDIA_BUS_FMT_SGBRG8_1X8, -+ MEDIA_BUS_FMT_SGRBG8_1X8, -+ MEDIA_BUS_FMT_SRGGB8_1X8, -+ 0 -+}; -+ -+/* -+ * Strings corresponding to CSI-2 receiver errors are here. -+ * Corresponding macros are defined in the header file. -+ */ -+static const struct ipu6_csi2_error dphy_rx_errors[] = { -+ { "Single packet header error corrected", true }, -+ { "Multiple packet header errors detected", true }, -+ { "Payload checksum (CRC) error", true }, -+ { "Transfer FIFO overflow", false }, -+ { "Reserved short packet data type detected", true }, -+ { "Reserved long packet data type detected", true }, -+ { "Incomplete long packet detected", false }, -+ { "Frame sync error", false }, -+ { "Line sync error", false }, -+ { "DPHY recoverable synchronization error", true }, -+ { "DPHY fatal error", false }, -+ { "DPHY elastic FIFO overflow", false }, -+ { "Inter-frame short packet discarded", true }, -+ { "Inter-frame long packet discarded", true }, -+ { "MIPI pktgen overflow", false }, -+ { "MIPI pktgen data loss", false }, -+ { "FIFO overflow", false }, -+ { "Lane deskew", false }, -+ { "SOT sync error", false }, -+ { "HSIDLE detected", false } -+}; -+ -+s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2) -+{ -+ struct media_pad *src_pad; -+ struct v4l2_subdev *ext_sd; -+ struct device *dev; -+ -+ if (!csi2) -+ return -EINVAL; -+ -+ dev = &csi2->isys->adev->auxdev.dev; -+ src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); -+ if (IS_ERR_OR_NULL(src_pad)) { -+ dev_err(dev, "can't get source pad of %s\n", csi2->asd.sd.name); -+ return -ENOLINK; -+ } -+ -+ ext_sd = media_entity_to_v4l2_subdev(src_pad->entity); -+ if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name)) -+ return -ENODEV; -+ -+ return v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0); -+} -+ -+static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, -+ struct v4l2_event_subscription *sub) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); -+ struct device *dev = &csi2->isys->adev->auxdev.dev; -+ -+ dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n", -+ sub->type, sub->id); -+ -+ switch (sub->type) { -+ case V4L2_EVENT_FRAME_SYNC: -+ return v4l2_event_subscribe(fh, sub, 10, NULL); -+ case V4L2_EVENT_CTRL: -+ return v4l2_ctrl_subscribe_event(fh, sub); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_subdev_core_ops csi2_sd_core_ops = { -+ .subscribe_event = csi2_subscribe_event, -+ .unsubscribe_event = v4l2_event_subdev_unsubscribe, -+}; -+ -+/* -+ * The input system CSI2+ receiver has several -+ * parameters affecting the receiver timings. These depend -+ * on the MIPI bus frequency F in Hz (sensor transmitter rate) -+ * as follows: -+ * register value = (A/1e9 + B * UI) / COUNT_ACC -+ * where -+ * UI = 1 / (2 * F) in seconds -+ * COUNT_ACC = counter accuracy in seconds -+ * COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8. -+ * -+ * A and B are coefficients from the table below, -+ * depending whether the register minimum or maximum value is -+ * calculated. -+ * Minimum Maximum -+ * Clock lane A B A B -+ * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0 -+ * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16 -+ * Data lanes -+ * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4 -+ * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6 -+ * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4 -+ * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6 -+ * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4 -+ * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6 -+ * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4 -+ * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6 -+ * -+ * We use the minimum values of both A and B. -+ */ -+ -+#define DIV_SHIFT 8 -+#define CSI2_ACCINV 8 -+ -+static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv) -+{ -+ return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT) -+ / (s32)(link_freq >> DIV_SHIFT)); -+} -+ -+static int -+ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2, -+ struct ipu6_isys_csi2_timing *timing, s32 accinv) -+{ -+ struct device *dev = &csi2->isys->adev->auxdev.dev; -+ s64 link_freq; -+ -+ link_freq = ipu6_isys_csi2_get_link_freq(csi2); -+ if (link_freq < 0) -+ return link_freq; -+ -+ timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A, -+ CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B, -+ link_freq, accinv); -+ timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A, -+ CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B, -+ link_freq, accinv); -+ timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A, -+ CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B, -+ link_freq, accinv); -+ timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A, -+ CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B, -+ link_freq, accinv); -+ -+ dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n", -+ timing->ctermen, timing->csettle, -+ timing->dtermen, timing->dsettle); -+ -+ return 0; -+} -+ -+void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2) -+{ -+ u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); -+ struct ipu6_isys *isys = csi2->isys; -+ u32 mask; -+ -+ mask = isys->pdata->ipdata->csi2.irq_mask; -+ writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ csi2->receiver_errors |= irq & mask; -+} -+ -+void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2) -+{ -+ struct device *dev = &csi2->isys->adev->auxdev.dev; -+ const struct ipu6_csi2_error *errors; -+ u32 status; -+ u32 i; -+ -+ /* register errors once more in case of interrupts are disabled */ -+ ipu6_isys_register_errors(csi2); -+ status = csi2->receiver_errors; -+ csi2->receiver_errors = 0; -+ errors = dphy_rx_errors; -+ -+ for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) { -+ if (status & BIT(i)) -+ dev_err_ratelimited(dev, "csi2-%i error: %s\n", -+ csi2->port, errors[i].error_string); -+ } -+} -+ -+static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, -+ const struct ipu6_isys_csi2_timing *timing, -+ unsigned int nlanes, int enable) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); -+ struct ipu6_isys *isys = csi2->isys; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_isys_csi2_config cfg; -+ unsigned int nports; -+ int ret = 0; -+ u32 mask = 0; -+ u32 i; -+ -+ dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off", -+ csi2->port, nlanes); -+ -+ cfg.port = csi2->port; -+ cfg.nlanes = nlanes; -+ -+ mask = isys->pdata->ipdata->csi2.irq_mask; -+ nports = isys->pdata->ipdata->csi2.nports; -+ -+ if (!enable) { -+ writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); -+ writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); -+ -+ writel(0, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ writel(0, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); -+ writel(0xffffffff, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ -+ isys->phy_set_power(isys, &cfg, timing, false); -+ -+ writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT -+ (isys->pdata->ipdata->csi2.fw_access_port_ofs, -+ csi2->port)); -+ writel(0, isys->pdata->base + -+ CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); -+ -+ return ret; -+ } -+ -+ /* reset port reset */ -+ writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST); -+ usleep_range(100, 200); -+ writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST); -+ -+ /* enable port clock */ -+ for (i = 0; i < nports; i++) { -+ writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i)); -+ writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT -+ (isys->pdata->ipdata->csi2.fw_access_port_ofs, i)); -+ } -+ -+ /* enable all error related irq */ -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); -+ writel(mask, -+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + -+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); -+ -+ /* -+ * Using event from firmware instead of irq to handle CSI2 sync event -+ * which can reduce system wakeups. If CSI2 sync irq enabled, we need -+ * disable the firmware CSI2 sync event to avoid duplicate handling. -+ */ -+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); -+ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); -+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); -+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); -+ -+ /* configure to enable FE and PPI2CSI */ -+ writel(0, csi2->base + CSI_REG_CSI_FE_MODE); -+ writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL); -+ writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID, -+ csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL); -+ writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1), -+ csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF); -+ -+ writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); -+ writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); -+ -+ ret = isys->phy_set_power(isys, &cfg, timing, true); -+ if (ret) -+ dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port, -+ ret); -+ -+ return ret; -+} -+ -+static int set_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); -+ struct device *dev = &csi2->isys->adev->auxdev.dev; -+ struct ipu6_isys_csi2_timing timing = { }; -+ unsigned int nlanes; -+ int ret; -+ -+ dev_dbg(dev, "csi2 stream %s callback\n", enable ? "on" : "off"); -+ -+ if (!enable) { -+ csi2->stream_count--; -+ if (csi2->stream_count) -+ return 0; -+ -+ ipu6_isys_csi2_set_stream(sd, &timing, 0, enable); -+ return 0; -+ } -+ -+ if (csi2->stream_count) { -+ csi2->stream_count++; -+ return 0; -+ } -+ -+ nlanes = csi2->nlanes; -+ -+ ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_isys_csi2_set_stream(sd, &timing, nlanes, enable); -+ if (ret) -+ return ret; -+ -+ csi2->stream_count++; -+ -+ return 0; -+} -+ -+static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ struct device *dev = &asd->isys->adev->auxdev.dev; -+ struct v4l2_mbus_framefmt *sink_ffmt; -+ struct v4l2_mbus_framefmt *src_ffmt; -+ struct v4l2_rect *crop; -+ -+ if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP) -+ return -EINVAL; -+ -+ sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, -+ sel->pad, -+ sel->stream); -+ if (!sink_ffmt) -+ return -EINVAL; -+ -+ src_ffmt = v4l2_subdev_state_get_stream_format(state, sel->pad, -+ sel->stream); -+ if (!src_ffmt) -+ return -EINVAL; -+ -+ crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); -+ if (!crop) -+ return -EINVAL; -+ -+ /* Only vertical cropping is supported */ -+ sel->r.left = 0; -+ sel->r.width = sink_ffmt->width; -+ /* Non-bayer formats can't be single line cropped */ -+ if (!ipu6_isys_is_bayer_format(sink_ffmt->code)) -+ sel->r.top &= ~1; -+ sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT, -+ sink_ffmt->height - sel->r.top); -+ *crop = sel->r; -+ -+ /* update source pad format */ -+ src_ffmt->width = sel->r.width; -+ src_ffmt->height = sel->r.height; -+ if (ipu6_isys_is_bayer_format(sink_ffmt->code)) -+ src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code, -+ sel->r.left, -+ sel->r.top); -+ dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n", -+ sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height, -+ src_ffmt->code); -+ -+ return 0; -+} -+ -+static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct v4l2_mbus_framefmt *sink_ffmt; -+ struct v4l2_rect *crop; -+ int ret = 0; -+ -+ if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK) -+ return -EINVAL; -+ -+ sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, -+ sel->pad, -+ sel->stream); -+ if (!sink_ffmt) -+ return -EINVAL; -+ -+ crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); -+ if (!crop) -+ return -EINVAL; -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ sel->r.left = 0; -+ sel->r.top = 0; -+ sel->r.width = sink_ffmt->width; -+ sel->r.height = sink_ffmt->height; -+ break; -+ case V4L2_SEL_TGT_CROP: -+ sel->r = *crop; -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ return ret; -+} -+ -+static const struct v4l2_subdev_video_ops csi2_sd_video_ops = { -+ .s_stream = set_stream, -+}; -+ -+static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = { -+ .init_cfg = ipu6_isys_subdev_init_cfg, -+ .get_fmt = v4l2_subdev_get_fmt, -+ .set_fmt = ipu6_isys_subdev_set_fmt, -+ .get_selection = ipu6_isys_csi2_get_sel, -+ .set_selection = ipu6_isys_csi2_set_sel, -+ .enum_mbus_code = ipu6_isys_subdev_enum_mbus_code, -+ .set_routing = ipu6_isys_subdev_set_routing, -+}; -+ -+static const struct v4l2_subdev_ops csi2_sd_ops = { -+ .core = &csi2_sd_core_ops, -+ .video = &csi2_sd_video_ops, -+ .pad = &csi2_sd_pad_ops, -+}; -+ -+static const struct media_entity_operations csi2_entity_ops = { -+ .link_validate = v4l2_subdev_link_validate, -+ .has_pad_interdep = v4l2_subdev_has_pad_interdep, -+}; -+ -+void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2) -+{ -+ if (!csi2->isys) -+ return; -+ -+ v4l2_device_unregister_subdev(&csi2->asd.sd); -+ v4l2_subdev_cleanup(&csi2->asd.sd); -+ ipu6_isys_subdev_cleanup(&csi2->asd); -+ csi2->isys = NULL; -+} -+ -+int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, -+ struct ipu6_isys *isys, -+ void __iomem *base, unsigned int index) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ int ret; -+ -+ csi2->isys = isys; -+ csi2->base = base; -+ csi2->port = index; -+ -+ csi2->asd.sd.entity.ops = &csi2_entity_ops; -+ csi2->asd.isys = isys; -+ ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0, -+ NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS); -+ if (ret) -+ goto fail; -+ -+ csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index; -+ csi2->asd.supported_codes = csi2_supported_codes; -+ snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name), -+ IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index); -+ v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd); -+ ret = v4l2_subdev_init_finalize(&csi2->asd.sd); -+ if (ret) { -+ dev_err(dev, "failed to init v4l2 subdev\n"); -+ goto fail; -+ } -+ -+ ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd); -+ if (ret) { -+ dev_err(dev, "failed to register v4l2 subdev\n"); -+ goto fail; -+ } -+ -+ return 0; -+ -+fail: -+ ipu6_isys_csi2_cleanup(csi2); -+ -+ return ret; -+} -+ -+void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream) -+{ -+ struct video_device *vdev = stream->asd->sd.devnode; -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); -+ struct v4l2_event ev = { -+ .type = V4L2_EVENT_FRAME_SYNC, -+ }; -+ -+ ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence); -+ v4l2_event_queue(vdev, &ev); -+ -+ dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n", -+ csi2->port, ev.u.frame_sync.frame_sequence, stream->vc); -+} -+ -+void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream) -+{ -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); -+ u32 frame_sequence = atomic_read(&stream->sequence); -+ -+ dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n", -+ csi2->port, frame_sequence); -+} -+ -+int ipu6_isys_csi2_get_remote_desc(u32 source_stream, -+ struct ipu6_isys_csi2 *csi2, -+ struct media_entity *source_entity, -+ struct v4l2_mbus_frame_desc_entry *entry, -+ int *nr_queues) -+{ -+ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; -+ struct device *dev = &csi2->isys->adev->auxdev.dev; -+ struct v4l2_mbus_frame_desc desc; -+ struct v4l2_subdev *source; -+ struct media_pad *pad; -+ unsigned int i; -+ int count = 0; -+ int ret; -+ -+ source = media_entity_to_v4l2_subdev(source_entity); -+ if (!source) -+ return -EPIPE; -+ -+ pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]); -+ if (!pad) -+ return -EPIPE; -+ -+ ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc); -+ if (ret) -+ return ret; -+ -+ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { -+ dev_err(dev, "Unsupported frame descriptor type\n"); -+ return -EINVAL; -+ } -+ -+ for (i = 0; i < desc.num_entries; i++) { -+ if (source_stream == desc.entry[i].stream) { -+ desc_entry = &desc.entry[i]; -+ break; -+ } -+ } -+ -+ if (!desc_entry) { -+ dev_err(dev, "Failed to find stream %u from remote subdev\n", -+ source_stream); -+ return -EINVAL; -+ } -+ -+ if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) { -+ dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc); -+ return -EINVAL; -+ } -+ -+ *entry = *desc_entry; -+ for (i = 0; i < desc.num_entries; i++) { -+ if (entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc) -+ count++; -+ } -+ -+ *nr_queues = count; -+ return 0; -+} -+ -+void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status) -+{ -+ struct ipu6_isys_stream *stream = av->stream; -+ struct v4l2_subdev *sd = &stream->asd->sd; -+ struct v4l2_subdev_state *state; -+ struct media_pad *r_pad; -+ unsigned int i; -+ u32 r_stream; -+ -+ r_pad = media_pad_remote_pad_first(&av->pad); -+ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); -+ -+ state = v4l2_subdev_lock_and_get_active_state(sd); -+ -+ for (i = 0; i < state->stream_configs.num_configs; i++) { -+ struct v4l2_subdev_stream_config *cfg = -+ &state->stream_configs.configs[i]; -+ -+ if (cfg->pad == r_pad->index && r_stream == cfg->stream) { -+ dev_dbg(&av->isys->adev->auxdev.dev, -+ "%s: pad:%u, stream:%u, status:%u\n", -+ sd->entity.name, r_pad->index, r_stream, -+ status); -+ cfg->enabled = status; -+ } -+ } -+ -+ v4l2_subdev_unlock_state(state); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h -new file mode 100644 -index 000000000000..d4765bae6112 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h -@@ -0,0 +1,81 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_ISYS_CSI2_H -+#define IPU6_ISYS_CSI2_H -+ -+#include <linux/container_of.h> -+ -+#include "ipu6-isys-subdev.h" -+ -+struct media_entity; -+struct v4l2_mbus_frame_desc_entry; -+ -+struct ipu6_isys_video; -+struct ipu6_isys; -+struct ipu6_isys_csi2_pdata; -+struct ipu6_isys_stream; -+ -+#define NR_OF_CSI2_VC 16 -+#define INVALID_VC_ID -1 -+#define NR_OF_CSI2_SINK_PADS 1 -+#define CSI2_PAD_SINK 0 -+#define NR_OF_CSI2_SRC_PADS 8 -+#define CSI2_PAD_SRC 1 -+#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS) -+ -+#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0 -+#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0 -+#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95 -+#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8 -+ -+#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0 -+#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0 -+#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85 -+#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2 -+ -+struct ipu6_isys_csi2 { -+ struct ipu6_isys_subdev asd; -+ struct ipu6_isys_csi2_pdata *pdata; -+ struct ipu6_isys *isys; -+ -+ void __iomem *base; -+ u32 receiver_errors; -+ unsigned int nlanes; -+ unsigned int port; -+ unsigned int stream_count; -+}; -+ -+struct ipu6_isys_csi2_timing { -+ u32 ctermen; -+ u32 csettle; -+ u32 dtermen; -+ u32 dsettle; -+}; -+ -+struct ipu6_csi2_error { -+ const char *error_string; -+ bool is_info_only; -+}; -+ -+#define ipu6_isys_subdev_to_csi2(__sd) \ -+ container_of(__sd, struct ipu6_isys_csi2, asd) -+ -+#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd) -+ -+s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2); -+int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys, -+ void __iomem *base, unsigned int index); -+void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2); -+void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream); -+void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream); -+void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2); -+void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2); -+int ipu6_isys_csi2_get_remote_desc(u32 source_stream, -+ struct ipu6_isys_csi2 *csi2, -+ struct media_entity *source_entity, -+ struct v4l2_mbus_frame_desc_entry *entry, -+ int *nr_queues); -+void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status); -+ -+#endif /* IPU6_ISYS_CSI2_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -new file mode 100644 -index 000000000000..510c5ca34f9f ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -@@ -0,0 +1,381 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bug.h> -+#include <linux/device.h> -+#include <linux/minmax.h> -+ -+#include <media/media-entity.h> -+#include <media/mipi-csi2.h> -+#include <media/v4l2-ctrls.h> -+#include <media/v4l2-subdev.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-subdev.h" -+ -+unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) -+{ -+ switch (code) { -+ case MEDIA_BUS_FMT_RGB888_1X24: -+ return 24; -+ case MEDIA_BUS_FMT_RGB565_1X16: -+ case MEDIA_BUS_FMT_UYVY8_1X16: -+ case MEDIA_BUS_FMT_YUYV8_1X16: -+ return 16; -+ case MEDIA_BUS_FMT_SBGGR12_1X12: -+ case MEDIA_BUS_FMT_SGBRG12_1X12: -+ case MEDIA_BUS_FMT_SGRBG12_1X12: -+ case MEDIA_BUS_FMT_SRGGB12_1X12: -+ return 12; -+ case MEDIA_BUS_FMT_SBGGR10_1X10: -+ case MEDIA_BUS_FMT_SGBRG10_1X10: -+ case MEDIA_BUS_FMT_SGRBG10_1X10: -+ case MEDIA_BUS_FMT_SRGGB10_1X10: -+ return 10; -+ case MEDIA_BUS_FMT_SBGGR8_1X8: -+ case MEDIA_BUS_FMT_SGBRG8_1X8: -+ case MEDIA_BUS_FMT_SGRBG8_1X8: -+ case MEDIA_BUS_FMT_SRGGB8_1X8: -+ return 8; -+ default: -+ WARN_ON(1); -+ return 8; -+ } -+} -+ -+unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) -+{ -+ switch (code) { -+ case MEDIA_BUS_FMT_RGB565_1X16: -+ return MIPI_CSI2_DT_RGB565; -+ case MEDIA_BUS_FMT_RGB888_1X24: -+ return MIPI_CSI2_DT_RGB888; -+ case MEDIA_BUS_FMT_UYVY8_1X16: -+ case MEDIA_BUS_FMT_YUYV8_1X16: -+ return MIPI_CSI2_DT_YUV422_8B; -+ case MEDIA_BUS_FMT_SBGGR12_1X12: -+ case MEDIA_BUS_FMT_SGBRG12_1X12: -+ case MEDIA_BUS_FMT_SGRBG12_1X12: -+ case MEDIA_BUS_FMT_SRGGB12_1X12: -+ return MIPI_CSI2_DT_RAW12; -+ case MEDIA_BUS_FMT_SBGGR10_1X10: -+ case MEDIA_BUS_FMT_SGBRG10_1X10: -+ case MEDIA_BUS_FMT_SGRBG10_1X10: -+ case MEDIA_BUS_FMT_SRGGB10_1X10: -+ return MIPI_CSI2_DT_RAW10; -+ case MEDIA_BUS_FMT_SBGGR8_1X8: -+ case MEDIA_BUS_FMT_SGBRG8_1X8: -+ case MEDIA_BUS_FMT_SGRBG8_1X8: -+ case MEDIA_BUS_FMT_SRGGB8_1X8: -+ return MIPI_CSI2_DT_RAW8; -+ default: -+ /* return unavailable MIPI data type - 0x3f */ -+ WARN_ON(1); -+ return 0x3f; -+ } -+} -+ -+bool ipu6_isys_is_bayer_format(u32 code) -+{ -+ switch (ipu6_isys_mbus_code_to_mipi(code)) { -+ case MIPI_CSI2_DT_RAW8: -+ case MIPI_CSI2_DT_RAW10: -+ case MIPI_CSI2_DT_RAW12: -+ return true; -+ default: -+ return false; -+ } -+} -+ -+u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) -+{ -+ static const u32 code_map[] = { -+ MEDIA_BUS_FMT_SRGGB8_1X8, -+ MEDIA_BUS_FMT_SGRBG8_1X8, -+ MEDIA_BUS_FMT_SGBRG8_1X8, -+ MEDIA_BUS_FMT_SBGGR8_1X8, -+ MEDIA_BUS_FMT_SRGGB10_1X10, -+ MEDIA_BUS_FMT_SGRBG10_1X10, -+ MEDIA_BUS_FMT_SGBRG10_1X10, -+ MEDIA_BUS_FMT_SBGGR10_1X10, -+ MEDIA_BUS_FMT_SRGGB12_1X12, -+ MEDIA_BUS_FMT_SGRBG12_1X12, -+ MEDIA_BUS_FMT_SGBRG12_1X12, -+ MEDIA_BUS_FMT_SBGGR12_1X12 -+ }; -+ u32 i; -+ -+ for (i = 0; i < ARRAY_SIZE(code_map); i++) -+ if (code_map[i] == code) -+ break; -+ -+ if (WARN_ON(i == ARRAY_SIZE(code_map))) -+ return code; -+ -+ return code_map[i ^ (((y & 1) << 1) | (x & 1))]; -+} -+ -+int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ struct v4l2_mbus_framefmt *fmt; -+ struct v4l2_rect *crop; -+ u32 code = asd->supported_codes[0]; -+ u32 other_pad, other_stream; -+ unsigned int i; -+ int ret; -+ -+ /* No transcoding, source and sink formats must match. */ -+ if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) && -+ sd->entity.num_pads > 1) -+ return v4l2_subdev_get_fmt(sd, state, format); -+ -+ format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH, -+ IPU6_ISYS_MAX_WIDTH); -+ format->format.height = clamp(format->format.height, -+ IPU6_ISYS_MIN_HEIGHT, -+ IPU6_ISYS_MAX_HEIGHT); -+ -+ for (i = 0; asd->supported_codes[i]; i++) { -+ if (asd->supported_codes[i] == format->format.code) { -+ code = asd->supported_codes[i]; -+ break; -+ } -+ } -+ format->format.code = code; -+ format->format.field = V4L2_FIELD_NONE; -+ -+ /* Store the format and propagate it to the source pad. */ -+ fmt = v4l2_subdev_state_get_stream_format(state, format->pad, -+ format->stream); -+ if (!fmt) -+ return -EINVAL; -+ -+ *fmt = format->format; -+ -+ if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK)) -+ return 0; -+ -+ /* propagate format to following source pad */ -+ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, -+ format->stream); -+ if (!fmt) -+ return -EINVAL; -+ -+ *fmt = format->format; -+ -+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, -+ format->pad, -+ format->stream, -+ &other_pad, -+ &other_stream); -+ if (ret) -+ return -EINVAL; -+ -+ crop = v4l2_subdev_state_get_stream_crop(state, other_pad, -+ other_stream); -+ /* reset crop */ -+ crop->left = 0; -+ crop->top = 0; -+ crop->width = fmt->width; -+ crop->height = fmt->height; -+ -+ return 0; -+} -+ -+int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); -+ const u32 *supported_codes = asd->supported_codes; -+ u32 index; -+ -+ for (index = 0; supported_codes[index]; index++) { -+ if (index == code->index) { -+ code->code = supported_codes[index]; -+ return 0; -+ } -+ } -+ -+ return -EINVAL; -+} -+ -+static int subdev_set_routing(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_krouting *routing) -+{ -+ static const struct v4l2_mbus_framefmt format = { -+ .width = 4096, -+ .height = 3072, -+ .code = MEDIA_BUS_FMT_SGRBG10_1X10, -+ .field = V4L2_FIELD_NONE, -+ }; -+ int ret; -+ -+ ret = v4l2_subdev_routing_validate(sd, routing, -+ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); -+ if (ret) -+ return ret; -+ -+ return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); -+} -+ -+int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, -+ struct v4l2_mbus_framefmt *format) -+{ -+ struct v4l2_mbus_framefmt *fmt; -+ struct v4l2_subdev_state *state; -+ -+ if (!sd || !format) -+ return -EINVAL; -+ -+ state = v4l2_subdev_lock_and_get_active_state(sd); -+ fmt = v4l2_subdev_state_get_stream_format(state, pad, stream); -+ if (fmt) -+ *format = *fmt; -+ v4l2_subdev_unlock_state(state); -+ -+ return fmt ? 0 : -EINVAL; -+} -+ -+int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, -+ struct v4l2_rect *crop) -+{ -+ struct v4l2_subdev_state *state; -+ struct v4l2_rect *rect; -+ -+ if (!sd || !crop) -+ return -EINVAL; -+ -+ state = v4l2_subdev_lock_and_get_active_state(sd); -+ rect = v4l2_subdev_state_get_stream_crop(state, pad, stream); -+ if (rect) -+ *crop = *rect; -+ v4l2_subdev_unlock_state(state); -+ -+ return rect ? 0 : -EINVAL; -+} -+ -+u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) -+{ -+ struct v4l2_subdev_state *state; -+ struct v4l2_subdev_route *routes; -+ unsigned int i; -+ u32 source_stream = 0; -+ -+ state = v4l2_subdev_lock_and_get_active_state(sd); -+ if (!state) -+ return 0; -+ -+ routes = state->routing.routes; -+ for (i = 0; i < state->routing.num_routes; i++) { -+ if (routes[i].source_pad == pad) { -+ source_stream = routes[i].source_stream; -+ break; -+ } -+ } -+ -+ v4l2_subdev_unlock_state(state); -+ -+ return source_stream; -+} -+ -+int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state) -+{ -+ struct v4l2_subdev_route route = { -+ .sink_pad = 0, -+ .sink_stream = 0, -+ .source_pad = 1, -+ .source_stream = 0, -+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, -+ }; -+ struct v4l2_subdev_krouting routing = { -+ .num_routes = 1, -+ .routes = &route, -+ }; -+ -+ return subdev_set_routing(sd, state, &routing); -+} -+ -+int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ enum v4l2_subdev_format_whence which, -+ struct v4l2_subdev_krouting *routing) -+{ -+ return subdev_set_routing(sd, state, routing); -+} -+ -+int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, -+ const struct v4l2_subdev_ops *ops, -+ unsigned int nr_ctrls, -+ unsigned int num_sink_pads, -+ unsigned int num_source_pads) -+{ -+ unsigned int num_pads = num_sink_pads + num_source_pads; -+ unsigned int i; -+ int ret; -+ -+ v4l2_subdev_init(&asd->sd, ops); -+ -+ asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | -+ V4L2_SUBDEV_FL_HAS_EVENTS | -+ V4L2_SUBDEV_FL_STREAMS; -+ asd->sd.owner = THIS_MODULE; -+ asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; -+ -+ asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, -+ sizeof(*asd->pad), GFP_KERNEL); -+ -+ if (!asd->pad) -+ return -ENOMEM; -+ -+ for (i = 0; i < num_sink_pads; i++) -+ asd->pad[i].flags = MEDIA_PAD_FL_SINK | -+ MEDIA_PAD_FL_MUST_CONNECT; -+ -+ for (i = num_sink_pads; i < num_pads; i++) -+ asd->pad[i].flags = MEDIA_PAD_FL_SOURCE; -+ -+ ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad); -+ if (ret) -+ return ret; -+ -+ if (asd->ctrl_init) { -+ ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls); -+ if (ret) -+ goto out_media_entity_cleanup; -+ -+ asd->ctrl_init(&asd->sd); -+ if (asd->ctrl_handler.error) { -+ ret = asd->ctrl_handler.error; -+ goto out_v4l2_ctrl_handler_free; -+ } -+ -+ asd->sd.ctrl_handler = &asd->ctrl_handler; -+ } -+ -+ asd->source = -1; -+ -+ return 0; -+ -+out_v4l2_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&asd->ctrl_handler); -+ -+out_media_entity_cleanup: -+ media_entity_cleanup(&asd->sd.entity); -+ -+ return ret; -+} -+ -+void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd) -+{ -+ media_entity_cleanup(&asd->sd.entity); -+ v4l2_ctrl_handler_free(&asd->ctrl_handler); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h -new file mode 100644 -index 000000000000..adea2a55761d ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h -@@ -0,0 +1,61 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_ISYS_SUBDEV_H -+#define IPU6_ISYS_SUBDEV_H -+ -+#include <linux/container_of.h> -+ -+#include <media/media-entity.h> -+#include <media/v4l2-ctrls.h> -+#include <media/v4l2-subdev.h> -+ -+struct ipu6_isys; -+ -+struct ipu6_isys_subdev { -+ struct v4l2_subdev sd; -+ struct ipu6_isys *isys; -+ u32 const *supported_codes; -+ struct media_pad *pad; -+ struct v4l2_ctrl_handler ctrl_handler; -+ void (*ctrl_init)(struct v4l2_subdev *sd); -+ int source; /* SSI stream source; -1 if unset */ -+}; -+ -+#define to_ipu6_isys_subdev(__sd) \ -+ container_of(__sd, struct ipu6_isys_subdev, sd) -+ -+unsigned int ipu6_isys_mbus_code_to_bpp(u32 code); -+unsigned int ipu6_isys_mbus_code_to_mipi(u32 code); -+bool ipu6_isys_is_bayer_format(u32 code); -+u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y); -+ -+int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *fmt); -+int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_mbus_code_enum -+ *code); -+int ipu6_isys_subdev_link_validate(struct v4l2_subdev *sd, -+ struct media_link *link, -+ struct v4l2_subdev_format *source_fmt, -+ struct v4l2_subdev_format *sink_fmt); -+u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); -+int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, -+ struct v4l2_mbus_framefmt *format); -+int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, -+ struct v4l2_rect *crop); -+int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state); -+int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ enum v4l2_subdev_format_whence which, -+ struct v4l2_subdev_krouting *routing); -+int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, -+ const struct v4l2_subdev_ops *ops, -+ unsigned int nr_ctrls, -+ unsigned int num_sink_pads, -+ unsigned int num_source_pads); -+void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd); -+#endif /* IPU6_ISYS_SUBDEV_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h -new file mode 100644 -index 000000000000..2034e1109d98 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h -@@ -0,0 +1,189 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2023 Intel Corporation */ -+ -+#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H -+#define IPU6_PLATFORM_ISYS_CSI2_REG_H -+ -+#include <linux/bits.h> -+ -+#define CSI_REG_BASE 0x220000 -+#define CSI_REG_BASE_PORT(id) ((id) * 0x1000) -+ -+#define IPU6_CSI_PORT_A_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(0)) -+#define IPU6_CSI_PORT_B_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(1)) -+#define IPU6_CSI_PORT_C_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(2)) -+#define IPU6_CSI_PORT_D_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(3)) -+#define IPU6_CSI_PORT_E_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(4)) -+#define IPU6_CSI_PORT_F_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(5)) -+#define IPU6_CSI_PORT_G_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(6)) -+#define IPU6_CSI_PORT_H_ADDR_OFFSET \ -+ (CSI_REG_BASE + CSI_REG_BASE_PORT(7)) -+ -+/* CSI Port Genral Purpose Registers */ -+#define CSI_REG_PORT_GPREG_SRST 0x0 -+#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 -+#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 -+ -+/* -+ * Port IRQs mapping events: -+ * IRQ0 - CSI_FE event -+ * IRQ1 - CSI_SYNC -+ * IRQ2 - S2M_SIDS0TO7 -+ * IRQ3 - S2M_SIDS8TO15 -+ */ -+#define CSI_PORT_REG_BASE_IRQ_CSI 0x80 -+#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0 -+#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0 -+#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0 -+ -+#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0 -+#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4 -+#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8 -+#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc -+#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10 -+#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14 -+ -+#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0) -+#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0) -+ -+#define CSI_RX_NUM_ERRORS_IN_IRQ 20 -+#define CSI_RX_NUM_IRQ 32 -+ -+#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2)) -+#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2)) -+ -+/* PPI2CSI */ -+#define CSI_REG_PPI2CSI_ENABLE 0x200 -+#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204 -+#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3) -+#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208 -+ -+enum CSI_PPI2CSI_CTRL { -+ CSI_PPI2CSI_DISABLE = 0, -+ CSI_PPI2CSI_ENABLE = 1, -+}; -+ -+/* CSI_FE */ -+#define CSI_REG_CSI_FE_ENABLE 0x280 -+#define CSI_REG_CSI_FE_MODE 0x284 -+#define CSI_REG_CSI_FE_MUX_CTRL 0x288 -+#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290 -+ -+enum CSI_FE_ENABLE_TYPE { -+ CSI_FE_DISABLE = 0, -+ CSI_FE_ENABLE = 1, -+}; -+ -+enum CSI_FE_MODE_TYPE { -+ CSI_FE_DPHY_MODE = 0, -+ CSI_FE_CPHY_MODE = 1, -+}; -+ -+enum CSI_FE_INPUT_SELECTOR { -+ CSI_SENSOR_INPUT = 0, -+ CSI_MIPIGEN_INPUT = 1, -+}; -+ -+enum CSI_FE_SYNC_CNTR_SEL_TYPE { -+ CSI_CNTR_SENSOR_LINE_ID = BIT(0), -+ CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID, -+ CSI_CNTR_SENSOR_FRAME_ID = BIT(1), -+ CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID, -+}; -+ -+/* CSI HUB General Purpose Registers */ -+#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000) -+#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004) -+ -+#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4) -+#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000 -+#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000 -+#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) \ -+ (CSI_REG_BASE + (ofs) + (id) * 4) -+ -+enum CSI_PORT_CLK_GATING_SWITCH { -+ CSI_PORT_CLK_GATING_OFF = 0, -+ CSI_PORT_CLK_GATING_ON = 1, -+}; -+ -+#define CSI_REG_BASE_HUB_IRQ 0x18200 -+ -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214 -+ -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230 -+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234 -+ -+/* MTL IPU6V6 irq ctrl0 & ctrl1 */ -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714 -+ -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730 -+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734 -+ -+/* -+ * 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits) -+ * [0] CSI_PORT.IRQ_CTRL0_csi -+ * [1] CSI_PORT.IRQ_CTRL1_csi_sync -+ * [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7 -+ * [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15 -+ */ -+#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \ -+ (0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE)) -+ -+/* -+ * ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3 -+ * sip0 - 0, 1 -+ * sip1 - 2, 3 -+ * 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes -+ * all offset are base from isys base address -+ */ -+ -+#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4) -+#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4) -+ -+#define CSI2_HUB_GPREG_DPHY_TIMER_INCR 0x238040 -+#define CSI2_HUB_GPREG_HPLL_FREQ 0x238044 -+#define CSI2_HUB_GPREG_IS_CLK_RATIO 0x238048 -+#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE 0x23804c -+#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE 0x238058 -+#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL 0x23805c -+#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL 0x238088 -+#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL 0x2380a4 -+#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL 0x2380d0 -+ -+#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48) -+#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48) -+#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48) -+ -+/* offset from port base */ -+#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL 0x0 -+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE 0x4 -+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE 0x8 -+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8) -+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8) -+ -+#endif /* IPU6_ISYS_CSI2_REG_H */ --- -2.43.2 - - -From 71d3da5e8f1ea094e26030a12ceed4553cbe182a Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:23 +0800 -Subject: [PATCH 16/33] media: intel/ipu6: add the CSI2 DPHY implementation - -IPU6 CSI2 DPHY hardware varies on different platforms, current -IPU6 has three DPHY hardware instance which maybe used on tigerlake, -alderlake, metorlake and jasperlake. MCD DPHY is shipped on tigerlake -and alderlake, DWC DPHY is shipped on metorlake. - -Each PHY has its own register space, input system driver call the -DPHY callback which was set at isys_probe(). - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - .../media/pci/intel/ipu6/ipu6-isys-dwc-phy.c | 536 +++++++++++++ - .../media/pci/intel/ipu6/ipu6-isys-jsl-phy.c | 242 ++++++ - .../media/pci/intel/ipu6/ipu6-isys-mcd-phy.c | 720 ++++++++++++++++++ - 3 files changed, 1498 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c -new file mode 100644 -index 000000000000..a4bb5ff51d4e ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c -@@ -0,0 +1,536 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/delay.h> -+#include <linux/device.h> -+#include <linux/iopoll.h> -+#include <linux/math64.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-isys.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+ -+#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i)) -+#define IPU6_DWC_DPHY_RSTZ 0x00 -+#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04 -+#define IPU6_DWC_DPHY_HSFREQRANGE 0x08 -+#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c -+#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10 -+#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14 -+#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18 -+#define IPU6_DWC_DPHY_DFT_CTRL0 0x28 -+#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c -+#define IPU6_DWC_DPHY_DFT_CTRL2 0x30 -+ -+/* -+ * test IFC request definition: -+ * - req: 0 for read, 1 for write -+ * - 12 bits address -+ * - 8bits data (will ignore for read) -+ * --24----16------4-----0 -+ * --|-data-|-addr-|-req-| -+ */ -+#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \ -+ FIELD_PREP(GENMASK(15, 4), addr) | \ -+ FIELD_PREP(GENMASK(1, 0), req)) -+ -+#define TEST_IFC_REQ_READ 0 -+#define TEST_IFC_REQ_WRITE 1 -+#define TEST_IFC_REQ_RESET 2 -+ -+#define TEST_IFC_ACCESS_MODE_FSM 0 -+#define TEST_IFC_ACCESS_MODE_IFC_CTL 1 -+ -+enum phy_fsm_state { -+ PHY_FSM_STATE_POWERON = 0, -+ PHY_FSM_STATE_BGPON = 1, -+ PHY_FSM_STATE_CAL_TYPE = 2, -+ PHY_FSM_STATE_BURNIN_CAL = 3, -+ PHY_FSM_STATE_TERMCAL = 4, -+ PHY_FSM_STATE_OFFSETCAL = 5, -+ PHY_FSM_STATE_OFFSET_LANE = 6, -+ PHY_FSM_STATE_IDLE = 7, -+ PHY_FSM_STATE_ULP = 8, -+ PHY_FSM_STATE_DDLTUNNING = 9, -+ PHY_FSM_STATE_SKEW_BACKWARD = 10, -+ PHY_FSM_STATE_INVALID, -+}; -+ -+static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, -+ u32 data) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); -+ -+ dev_dbg(dev, "write: reg 0x%lx = data 0x%x", base + addr - isys_base, -+ data); -+ writel(data, base + addr); -+} -+ -+static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); -+ u32 data; -+ -+ data = readl(base + addr); -+ dev_dbg(dev, "read: reg 0x%lx = data 0x%x", base + addr - isys_base, -+ data); -+ -+ return data; -+} -+ -+static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, -+ u32 data, u8 shift, u8 width) -+{ -+ u32 temp; -+ u32 mask; -+ -+ mask = (1 << width) - 1; -+ temp = dwc_dphy_read(isys, phy_id, addr); -+ temp &= ~(mask << shift); -+ temp |= (data & mask) << shift; -+ dwc_dphy_write(isys, phy_id, addr, temp); -+} -+ -+static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id, -+ u32 addr, u8 shift, u8 width) -+{ -+ u32 val; -+ -+ val = dwc_dphy_read(isys, phy_id, addr) >> shift; -+ return val & ((1 << width) - 1); -+} -+ -+#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC) -+static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr, -+ u32 *val) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); -+ void __iomem *reg; -+ u32 completion; -+ int ret; -+ -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, -+ IFC_REQ(TEST_IFC_REQ_READ, addr, 0)); -+ reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; -+ ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), -+ 10, DWC_DPHY_TIMEOUT); -+ if (ret) { -+ dev_err(dev, "DWC ifc request read timeout\n"); -+ return ret; -+ } -+ -+ *val = completion >> 8 & 0xff; -+ *val = FIELD_GET(GENMASK(15, 8), completion); -+ dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val); -+ -+ return 0; -+} -+ -+static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, -+ u32 data) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); -+ void __iomem *reg; -+ u32 completion; -+ int ret; -+ -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, -+ IFC_REQ(TEST_IFC_REQ_WRITE, addr, data)); -+ completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION); -+ reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; -+ ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), -+ 10, DWC_DPHY_TIMEOUT); -+ if (ret) -+ dev_err(dev, "DWC ifc request write timeout\n"); -+ -+ return ret; -+} -+ -+static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id, -+ u32 addr, u32 data, u8 shift, u8 width) -+{ -+ u32 temp, mask; -+ int ret; -+ -+ ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp); -+ if (ret) -+ return; -+ -+ mask = (1 << width) - 1; -+ temp &= ~(mask << shift); -+ temp |= (data & mask) << shift; -+ dwc_dphy_ifc_write(isys, phy_id, addr, temp); -+} -+ -+static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, -+ u8 shift, u8 width) -+{ -+ int ret; -+ u32 val; -+ -+ ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val); -+ if (ret) -+ return 0; -+ -+ return ((val >> shift) & ((1 << width) - 1)); -+} -+ -+static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ u32 fsm_state; -+ int ret; -+ -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1); -+ usleep_range(10, 20); -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1); -+ -+ ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state, -+ (fsm_state == PHY_FSM_STATE_IDLE || -+ fsm_state == PHY_FSM_STATE_ULP), -+ 100, DWC_DPHY_TIMEOUT, false, isys, -+ phy_id, 0x1e, 0, 4); -+ -+ if (ret) -+ dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id, -+ fsm_state); -+ -+ return ret; -+} -+ -+struct dwc_dphy_freq_range { -+ u8 hsfreq; -+ u16 min; -+ u16 max; -+ u16 default_mbps; -+ u16 osc_freq_target; -+}; -+ -+#define DPHY_FREQ_RANGE_NUM (63) -+#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff) -+static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = { -+ {0x00, 80, 97, 80, 335}, -+ {0x10, 80, 107, 90, 335}, -+ {0x20, 84, 118, 100, 335}, -+ {0x30, 93, 128, 110, 335}, -+ {0x01, 103, 139, 120, 335}, -+ {0x11, 112, 149, 130, 335}, -+ {0x21, 122, 160, 140, 335}, -+ {0x31, 131, 170, 150, 335}, -+ {0x02, 141, 181, 160, 335}, -+ {0x12, 150, 191, 170, 335}, -+ {0x22, 160, 202, 180, 335}, -+ {0x32, 169, 212, 190, 335}, -+ {0x03, 183, 228, 205, 335}, -+ {0x13, 198, 244, 220, 335}, -+ {0x23, 212, 259, 235, 335}, -+ {0x33, 226, 275, 250, 335}, -+ {0x04, 250, 301, 275, 335}, -+ {0x14, 274, 328, 300, 335}, -+ {0x25, 297, 354, 325, 335}, -+ {0x35, 321, 380, 350, 335}, -+ {0x05, 369, 433, 400, 335}, -+ {0x16, 416, 485, 450, 335}, -+ {0x26, 464, 538, 500, 335}, -+ {0x37, 511, 590, 550, 335}, -+ {0x07, 559, 643, 600, 335}, -+ {0x18, 606, 695, 650, 335}, -+ {0x28, 654, 748, 700, 335}, -+ {0x39, 701, 800, 750, 335}, -+ {0x09, 749, 853, 800, 335}, -+ {0x19, 796, 905, 850, 335}, -+ {0x29, 844, 958, 900, 335}, -+ {0x3a, 891, 1010, 950, 335}, -+ {0x0a, 939, 1063, 1000, 335}, -+ {0x1a, 986, 1115, 1050, 335}, -+ {0x2a, 1034, 1168, 1100, 335}, -+ {0x3b, 1081, 1220, 1150, 335}, -+ {0x0b, 1129, 1273, 1200, 335}, -+ {0x1b, 1176, 1325, 1250, 335}, -+ {0x2b, 1224, 1378, 1300, 335}, -+ {0x3c, 1271, 1430, 1350, 335}, -+ {0x0c, 1319, 1483, 1400, 335}, -+ {0x1c, 1366, 1535, 1450, 335}, -+ {0x2c, 1414, 1588, 1500, 335}, -+ {0x3d, 1461, 1640, 1550, 208}, -+ {0x0d, 1509, 1693, 1600, 214}, -+ {0x1d, 1556, 1745, 1650, 221}, -+ {0x2e, 1604, 1798, 1700, 228}, -+ {0x3e, 1651, 1850, 1750, 234}, -+ {0x0e, 1699, 1903, 1800, 241}, -+ {0x1e, 1746, 1955, 1850, 248}, -+ {0x2f, 1794, 2008, 1900, 255}, -+ {0x3f, 1841, 2060, 1950, 261}, -+ {0x0f, 1889, 2113, 2000, 268}, -+ {0x40, 1936, 2165, 2050, 275}, -+ {0x41, 1984, 2218, 2100, 281}, -+ {0x42, 2031, 2270, 2150, 288}, -+ {0x43, 2079, 2323, 2200, 294}, -+ {0x44, 2126, 2375, 2250, 302}, -+ {0x45, 2174, 2428, 2300, 308}, -+ {0x46, 2221, 2480, 2350, 315}, -+ {0x47, 2269, 2500, 2400, 321}, -+ {0x48, 2316, 2500, 2450, 328}, -+ {0x49, 2364, 2500, 2500, 335} -+}; -+ -+static u16 get_hsfreq_by_mbps(u32 mbps) -+{ -+ unsigned int i = DPHY_FREQ_RANGE_NUM; -+ -+ while (i--) { -+ if (freqranges[i].default_mbps == mbps || -+ (mbps >= freqranges[i].min && mbps <= freqranges[i].max)) -+ return i; -+ } -+ -+ return DPHY_FREQ_RANGE_INVALID_INDEX; -+} -+ -+static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys, -+ u32 phy_id, u32 mbps) -+{ -+ struct ipu6_bus_device *adev = isys->adev; -+ struct device *dev = &adev->auxdev.dev; -+ struct ipu6_device *isp = adev->isp; -+ u32 cfg_clk_freqrange; -+ u32 osc_freq_target; -+ u32 index; -+ -+ dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps); -+ -+ index = get_hsfreq_by_mbps(mbps); -+ if (index == DPHY_FREQ_RANGE_INVALID_INDEX) { -+ dev_err(dev, "link freq not found for mbps %u", mbps); -+ return -EINVAL; -+ } -+ -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE, -+ freqranges[index].hsfreq, 0, 7); -+ -+ /* Force termination Calibration */ -+ if (isys->phy_termcal_val) { -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1); -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2); -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, -+ isys->phy_termcal_val, 4, 4); -+ } -+ -+ /* -+ * Enable override to configure the DDL target oscillation -+ * frequency on bit 0 of register 0xe4 -+ */ -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1); -+ /* -+ * configure registers 0xe2, 0xe3 with the -+ * appropriate DDL target oscillation frequency -+ * 0x1cc(460) -+ */ -+ osc_freq_target = freqranges[index].osc_freq_target; -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2, -+ osc_freq_target & 0xff, 0, 8); -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3, -+ (osc_freq_target >> 8) & 0xf, 0, 4); -+ -+ if (mbps < 1500) { -+ /* deskew_polarity_rw, for < 1.5Gbps */ -+ dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1); -+ } -+ -+ /* -+ * Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4] -+ * (38.4 - 17) * 4 = ~85 (0x55) -+ */ -+ cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10; -+ dev_dbg(dev, "ref_clk = %u clk_freqrange = %u", -+ isp->buttress.ref_clk, cfg_clk_freqrange); -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE, -+ cfg_clk_freqrange, 0, 8); -+ -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1); -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1); -+ -+ return 0; -+} -+ -+static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master, -+ u32 slave, u32 mbps) -+{ -+ /* Config mastermacro */ -+ dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1); -+ -+ /* Config master PHY clk lane to drive long channel clk */ -+ dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1); -+ -+ /* Config both PHYs data lanes to get clk from long channel */ -+ dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1); -+ dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1); -+ -+ /* Config slave PHY clk lane to bypass long channel clk to DDR clk */ -+ dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1); -+ -+ /* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */ -+ dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2); -+ -+ /* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */ -+ dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1); -+ -+ /* Turn off slave PHY LP-RX clk lane */ -+ dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1); -+ dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5); -+} -+ -+#define PHY_E 4 -+static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ u32 rescal_done; -+ int ret; -+ -+ ret = dwc_dphy_pwr_up(isys, phy_id); -+ if (ret != 0) { -+ dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret); -+ return ret; -+ } -+ -+ /* reset forcerxmode */ -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1); -+ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1); -+ -+ dev_dbg(dev, "Dphy %u is ready!", phy_id); -+ -+ if (phy_id != PHY_E || isys->phy_termcal_val) -+ return 0; -+ -+ usleep_range(100, 200); -+ rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1); -+ if (rescal_done) { -+ isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id, -+ 0x220, 2, 4); -+ dev_dbg(dev, "termcal done with value = %u", -+ isys->phy_termcal_val); -+ } -+ -+ return 0; -+} -+ -+static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id) -+{ -+ dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id); -+ -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0); -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0); -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE, -+ TEST_IFC_ACCESS_MODE_FSM); -+ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, -+ TEST_IFC_REQ_RESET); -+} -+ -+int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ u32 phy_id, primary, secondary; -+ u32 nlanes, port, mbps; -+ s64 link_freq; -+ int ret; -+ -+ port = cfg->port; -+ -+ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { -+ dev_warn(dev, "invalid port ID %d\n", port); -+ return -EINVAL; -+ } -+ -+ nlanes = cfg->nlanes; -+ /* only port 0, 2 and 4 support 4 lanes */ -+ if (nlanes == 4 && port % 2) { -+ dev_err(dev, "invalid csi-port %u with %u lanes\n", port, -+ nlanes); -+ return -EINVAL; -+ } -+ -+ link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]); -+ if (link_freq < 0) { -+ dev_err(dev, "get link freq failed(%lld).\n", link_freq); -+ return link_freq; -+ } -+ -+ mbps = div_u64(link_freq, 500000); -+ -+ phy_id = port; -+ primary = port & ~1; -+ secondary = primary + 1; -+ if (on) { -+ if (nlanes == 4) { -+ dev_dbg(dev, "config phy %u and %u in aggr mode\n", -+ primary, secondary); -+ -+ ipu6_isys_dwc_phy_reset(isys, primary); -+ ipu6_isys_dwc_phy_reset(isys, secondary); -+ ipu6_isys_dwc_phy_aggr_setup(isys, primary, -+ secondary, mbps); -+ -+ ret = ipu6_isys_dwc_phy_config(isys, primary, mbps); -+ if (ret) -+ return ret; -+ ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary); -+ return ret; -+ } -+ -+ dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n", -+ phy_id, nlanes); -+ -+ ipu6_isys_dwc_phy_reset(isys, phy_id); -+ ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps); -+ if (ret) -+ return ret; -+ -+ ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id); -+ return ret; -+ } -+ -+ if (nlanes == 4) { -+ dev_dbg(dev, "Power down phy %u and phy %u for port %u\n", -+ primary, secondary, port); -+ ipu6_isys_dwc_phy_reset(isys, secondary); -+ ipu6_isys_dwc_phy_reset(isys, primary); -+ -+ return 0; -+ } -+ -+ dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes); -+ -+ ipu6_isys_dwc_phy_reset(isys, phy_id); -+ -+ return 0; -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c -new file mode 100644 -index 000000000000..dcc7743e0cee ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c -@@ -0,0 +1,242 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/device.h> -+#include <linux/io.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+ -+/* only use BB0, BB2, BB4, and BB6 on PHY0 */ -+#define IPU6SE_ISYS_PHY_BB_NUM 4 -+#define IPU6SE_ISYS_PHY_0_BASE 0x10000 -+ -+#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) -+#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) -+#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) -+#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) -+ -+/* -+ * use port_cfg to configure that which data lanes used -+ * +---------+ +------+ +-----+ -+ * | port0 x4<-----| | | | -+ * | | | port | | | -+ * | port1 x2<-----| | | | -+ * | | | <-| PHY | -+ * | port2 x4<-----| | | | -+ * | | |config| | | -+ * | port3 x2<-----| | | | -+ * +---------+ +------+ +-----+ -+ */ -+static const unsigned int csi2_port_cfg[][3] = { -+ {0, 0, 0x1f}, /* no link */ -+ {4, 0, 0x10}, /* x4 + x4 config */ -+ {2, 0, 0x12}, /* x2 + x2 config */ -+ {1, 0, 0x13}, /* x1 + x1 config */ -+ {2, 1, 0x15}, /* x2x1 + x2x1 config */ -+ {1, 1, 0x16}, /* x1x1 + x1x1 config */ -+ {2, 2, 0x18}, /* x2x2 + x2x2 config */ -+ {1, 2, 0x19} /* x1x2 + x1x2 config */ -+}; -+ -+/* port, nlanes, bbindex, portcfg */ -+static const unsigned int phy_port_cfg[][4] = { -+ /* sip0 */ -+ {0, 1, 0, 0x15}, -+ {0, 2, 0, 0x15}, -+ {0, 4, 0, 0x15}, -+ {0, 4, 2, 0x22}, -+ /* sip1 */ -+ {2, 1, 4, 0x15}, -+ {2, 2, 4, 0x15}, -+ {2, 4, 4, 0x15}, -+ {2, 4, 6, 0x22} -+}; -+ -+static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys, -+ unsigned int port, -+ unsigned int nlanes) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *base = isys->adev->isp->base; -+ unsigned int bbnum; -+ u32 val, reg, i; -+ -+ dev_dbg(dev, "port %u with %u lanes", port, nlanes); -+ -+ /* only support <1.5Gbps */ -+ for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) { -+ /* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */ -+ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); -+ val = readl(base + reg); -+ val |= FIELD_PREP(GENMASK(6, 1), 13); -+ writel(val, base + reg); -+ -+ /* cphy_rx_control1.en_crc1 = 1 */ -+ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i); -+ val = readl(base + reg); -+ val |= BIT(31); -+ writel(val, base + reg); -+ -+ /* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */ -+ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i); -+ val = readl(base + reg); -+ val |= BIT(25) | BIT(26); -+ writel(val, base + reg); -+ -+ /* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */ -+ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); -+ val = readl(base + reg); -+ val |= BIT(0); -+ writel(val, base + reg); -+ } -+ -+ /* Front end config, use minimal channel loss */ -+ for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) { -+ if (phy_port_cfg[i][0] == port && -+ phy_port_cfg[i][1] == nlanes) { -+ bbnum = phy_port_cfg[i][2] / 2; -+ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum); -+ val = readl(base + reg); -+ val |= phy_port_cfg[i][3]; -+ writel(val, base + reg); -+ } -+ } -+} -+ -+static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys) -+{ -+ void __iomem *base = isys->adev->isp->base; -+ u32 val, reg; -+ -+ reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL; -+ val = readl(base + reg); -+ val |= BIT(0); -+ writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL); -+ -+ reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL; -+ val = readl(base + reg); -+ val |= BIT(0); -+ writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL); -+ -+ reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL; -+ val = readl(base + reg); -+ val |= BIT(0); -+ writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL); -+ -+ reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL; -+ val = readl(base + reg); -+ val |= BIT(0); -+ writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL); -+} -+ -+static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys, -+ unsigned int port, unsigned int nlanes) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ unsigned int sip = port / 2; -+ unsigned int index; -+ -+ switch (nlanes) { -+ case 1: -+ index = 5; -+ break; -+ case 2: -+ index = 6; -+ break; -+ case 4: -+ index = 1; -+ break; -+ default: -+ dev_err(dev, "lanes nr %u is unsupported\n", nlanes); -+ return -EINVAL; -+ } -+ -+ dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes); -+ -+ writel(csi2_port_cfg[index][2], -+ isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)); -+ -+ return 0; -+} -+ -+static void -+ipu6_isys_csi2_set_timing(struct ipu6_isys *isys, -+ const struct ipu6_isys_csi2_timing *timing, -+ unsigned int port, unsigned int nlanes) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *reg; -+ u32 port_base; -+ u32 i; -+ -+ port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) : -+ CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port); -+ -+ dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes); -+ -+ reg = isys->pdata->base + port_base; -+ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE; -+ -+ writel(timing->ctermen, reg); -+ -+ reg = isys->pdata->base + port_base; -+ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE; -+ writel(timing->csettle, reg); -+ -+ for (i = 0; i < nlanes; i++) { -+ reg = isys->pdata->base + port_base; -+ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i); -+ writel(timing->dtermen, reg); -+ -+ reg = isys->pdata->base + port_base; -+ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i); -+ writel(timing->dsettle, reg); -+ } -+} -+ -+#define DPHY_TIMER_INCR 0x28 -+int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ int ret = 0; -+ u32 nlanes; -+ u32 port; -+ -+ if (!on) -+ return 0; -+ -+ port = cfg->port; -+ nlanes = cfg->nlanes; -+ -+ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { -+ dev_warn(dev, "invalid port ID %d\n", port); -+ return -EINVAL; -+ } -+ -+ ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes); -+ -+ writel(DPHY_TIMER_INCR, -+ isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR); -+ -+ /* set port cfg and rx timing */ -+ ipu6_isys_csi2_set_timing(isys, timing, port, nlanes); -+ -+ ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes); -+ if (ret) -+ return ret; -+ -+ ipu6_isys_csi2_rx_control(isys); -+ -+ return 0; -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c -new file mode 100644 -index 000000000000..9abf389a05f1 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c -@@ -0,0 +1,720 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/bits.h> -+#include <linux/container_of.h> -+#include <linux/device.h> -+#include <linux/iopoll.h> -+#include <linux/list.h> -+#include <linux/refcount.h> -+#include <linux/time64.h> -+ -+#include <media/v4l2-async.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+ -+#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8) -+#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4) -+#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0) -+#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8) -+#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0) -+#define CSI_REG_HUB_GPREG_PHY_READY BIT(4) -+ -+#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC) -+ -+/* -+ * bridge to phy in buttress reg map, each phy has 16 kbytes -+ * only 2 phys for TGL U and Y -+ */ -+#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000) -+ -+/* -+ * There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL. -+ * Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes. -+ * CSI port 1, 3 (5, 7) can support max 2 data lanes. -+ * CSI port 0, 2 (4, 6) can support max 4 data lanes. -+ * PHY configurations are PPI based instead of port. -+ * Left: -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x4 | unused | D3 | D2 | C0 | D0 | D1 | -+ * |---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 | -+ * ----------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x2x1 | C1 | D0 | unused | C0 | D0 | D1 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x1x1 | C1 | D0 | unused | C0 | D0 | unused | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x1x2 | C1 | D0 | D1 | C0 | D0 | unused | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * -+ * Right: -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x4 | D1 | D0 | C2 | D2 | D3 | unused | -+ * |---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 | -+ * ----------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x2x1 | D1 | D0 | C2 | unused | D0 | C3 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x1x1 | unused | D0 | C2 | unused | D0 | C3 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * | | | | | | | | -+ * | x1x2 | unused | D0 | C2 | D1 | D0 | C3 | -+ * +---------+---------+---------+---------+--------+---------+----------+ -+ * -+ * ppi mapping per phy : -+ * -+ * x4 + x4: -+ * Left : port0 - PPI range {0, 1, 2, 3, 4} -+ * Right: port2 - PPI range {6, 7, 8, 9, 10} -+ * -+ * x4 + x2x2: -+ * Left: port0 - PPI range {0, 1, 2, 3, 4} -+ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} -+ * -+ * x2x2 + x4: -+ * Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} -+ * Right: port2 - PPI range {6, 7, 8, 9, 10} -+ * -+ * x2x2 + x2x2: -+ * Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} -+ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} -+ */ -+ -+struct phy_reg { -+ u32 reg; -+ u32 val; -+}; -+ -+static const struct phy_reg common_init_regs[] = { -+ /* for TGL-U, use 0x80000000 */ -+ {0x00000040, 0x80000000}, -+ {0x00000044, 0x00a80880}, -+ {0x00000044, 0x00b80880}, -+ {0x00000010, 0x0000078c}, -+ {0x00000344, 0x2f4401e2}, -+ {0x00000544, 0x924401e2}, -+ {0x00000744, 0x594401e2}, -+ {0x00000944, 0x624401e2}, -+ {0x00000b44, 0xfc4401e2}, -+ {0x00000d44, 0xc54401e2}, -+ {0x00000f44, 0x034401e2}, -+ {0x00001144, 0x8f4401e2}, -+ {0x00001344, 0x754401e2}, -+ {0x00001544, 0xe94401e2}, -+ {0x00001744, 0xcb4401e2}, -+ {0x00001944, 0xfa4401e2} -+}; -+ -+static const struct phy_reg x1_port0_config_regs[] = { -+ {0x00000694, 0xc80060fa}, -+ {0x00000680, 0x3d4f78ea}, -+ {0x00000690, 0x10a0140b}, -+ {0x000006a8, 0xdf04010a}, -+ {0x00000700, 0x57050060}, -+ {0x00000710, 0x0030001c}, -+ {0x00000738, 0x5f004444}, -+ {0x0000073c, 0x78464204}, -+ {0x00000748, 0x7821f940}, -+ {0x0000074c, 0xb2000433}, -+ {0x00000494, 0xfe6030fa}, -+ {0x00000480, 0x29ef5ed0}, -+ {0x00000490, 0x10a0540b}, -+ {0x000004a8, 0x7a01010a}, -+ {0x00000500, 0xef053460}, -+ {0x00000510, 0xe030101c}, -+ {0x00000538, 0xdf808444}, -+ {0x0000053c, 0xc8422204}, -+ {0x00000540, 0x0180088c}, -+ {0x00000574, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x1_port1_config_regs[] = { -+ {0x00000c94, 0xc80060fa}, -+ {0x00000c80, 0xcf47abea}, -+ {0x00000c90, 0x10a0840b}, -+ {0x00000ca8, 0xdf04010a}, -+ {0x00000d00, 0x57050060}, -+ {0x00000d10, 0x0030001c}, -+ {0x00000d38, 0x5f004444}, -+ {0x00000d3c, 0x78464204}, -+ {0x00000d48, 0x7821f940}, -+ {0x00000d4c, 0xb2000433}, -+ {0x00000a94, 0xc91030fa}, -+ {0x00000a80, 0x5a166ed0}, -+ {0x00000a90, 0x10a0540b}, -+ {0x00000aa8, 0x5d060100}, -+ {0x00000b00, 0xef053460}, -+ {0x00000b10, 0xa030101c}, -+ {0x00000b38, 0xdf808444}, -+ {0x00000b3c, 0xc8422204}, -+ {0x00000b40, 0x0180088c}, -+ {0x00000b74, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x1_port2_config_regs[] = { -+ {0x00001294, 0x28f000fa}, -+ {0x00001280, 0x08130cea}, -+ {0x00001290, 0x10a0140b}, -+ {0x000012a8, 0xd704010a}, -+ {0x00001300, 0x8d050060}, -+ {0x00001310, 0x0030001c}, -+ {0x00001338, 0xdf008444}, -+ {0x0000133c, 0x78422204}, -+ {0x00001348, 0x7821f940}, -+ {0x0000134c, 0x5a000433}, -+ {0x00001094, 0x2d20b0fa}, -+ {0x00001080, 0xade75dd0}, -+ {0x00001090, 0x10a0540b}, -+ {0x000010a8, 0xb101010a}, -+ {0x00001100, 0x33053460}, -+ {0x00001110, 0x0030101c}, -+ {0x00001138, 0xdf808444}, -+ {0x0000113c, 0xc8422204}, -+ {0x00001140, 0x8180088c}, -+ {0x00001174, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x1_port3_config_regs[] = { -+ {0x00001894, 0xc80060fa}, -+ {0x00001880, 0x0f90fd6a}, -+ {0x00001890, 0x10a0840b}, -+ {0x000018a8, 0xdf04010a}, -+ {0x00001900, 0x57050060}, -+ {0x00001910, 0x0030001c}, -+ {0x00001938, 0x5f004444}, -+ {0x0000193c, 0x78464204}, -+ {0x00001948, 0x7821f940}, -+ {0x0000194c, 0xb2000433}, -+ {0x00001694, 0x3050d0fa}, -+ {0x00001680, 0x0ef6d050}, -+ {0x00001690, 0x10a0540b}, -+ {0x000016a8, 0xe301010a}, -+ {0x00001700, 0x69053460}, -+ {0x00001710, 0xa030101c}, -+ {0x00001738, 0xdf808444}, -+ {0x0000173c, 0xc8422204}, -+ {0x00001740, 0x0180088c}, -+ {0x00001774, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x2_port0_config_regs[] = { -+ {0x00000694, 0xc80060fa}, -+ {0x00000680, 0x3d4f78ea}, -+ {0x00000690, 0x10a0140b}, -+ {0x000006a8, 0xdf04010a}, -+ {0x00000700, 0x57050060}, -+ {0x00000710, 0x0030001c}, -+ {0x00000738, 0x5f004444}, -+ {0x0000073c, 0x78464204}, -+ {0x00000748, 0x7821f940}, -+ {0x0000074c, 0xb2000433}, -+ {0x00000494, 0xc80060fa}, -+ {0x00000480, 0x29ef5ed8}, -+ {0x00000490, 0x10a0540b}, -+ {0x000004a8, 0x7a01010a}, -+ {0x00000500, 0xef053460}, -+ {0x00000510, 0xe030101c}, -+ {0x00000538, 0xdf808444}, -+ {0x0000053c, 0xc8422204}, -+ {0x00000540, 0x0180088c}, -+ {0x00000574, 0x00000000}, -+ {0x00000294, 0xc80060fa}, -+ {0x00000280, 0xcb45b950}, -+ {0x00000290, 0x10a0540b}, -+ {0x000002a8, 0x8c01010a}, -+ {0x00000300, 0xef053460}, -+ {0x00000310, 0x8030101c}, -+ {0x00000338, 0x41808444}, -+ {0x0000033c, 0x32422204}, -+ {0x00000340, 0x0180088c}, -+ {0x00000374, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x2_port1_config_regs[] = { -+ {0x00000c94, 0xc80060fa}, -+ {0x00000c80, 0xcf47abea}, -+ {0x00000c90, 0x10a0840b}, -+ {0x00000ca8, 0xdf04010a}, -+ {0x00000d00, 0x57050060}, -+ {0x00000d10, 0x0030001c}, -+ {0x00000d38, 0x5f004444}, -+ {0x00000d3c, 0x78464204}, -+ {0x00000d48, 0x7821f940}, -+ {0x00000d4c, 0xb2000433}, -+ {0x00000a94, 0xc80060fa}, -+ {0x00000a80, 0x5a166ed8}, -+ {0x00000a90, 0x10a0540b}, -+ {0x00000aa8, 0x7a01010a}, -+ {0x00000b00, 0xef053460}, -+ {0x00000b10, 0xa030101c}, -+ {0x00000b38, 0xdf808444}, -+ {0x00000b3c, 0xc8422204}, -+ {0x00000b40, 0x0180088c}, -+ {0x00000b74, 0x00000000}, -+ {0x00000894, 0xc80060fa}, -+ {0x00000880, 0x4d4f21d0}, -+ {0x00000890, 0x10a0540b}, -+ {0x000008a8, 0x5601010a}, -+ {0x00000900, 0xef053460}, -+ {0x00000910, 0x8030101c}, -+ {0x00000938, 0xdf808444}, -+ {0x0000093c, 0xc8422204}, -+ {0x00000940, 0x0180088c}, -+ {0x00000974, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x2_port2_config_regs[] = { -+ {0x00001294, 0xc80060fa}, -+ {0x00001280, 0x08130cea}, -+ {0x00001290, 0x10a0140b}, -+ {0x000012a8, 0xd704010a}, -+ {0x00001300, 0x8d050060}, -+ {0x00001310, 0x0030001c}, -+ {0x00001338, 0xdf008444}, -+ {0x0000133c, 0x78422204}, -+ {0x00001348, 0x7821f940}, -+ {0x0000134c, 0x5a000433}, -+ {0x00001094, 0xc80060fa}, -+ {0x00001080, 0xade75dd8}, -+ {0x00001090, 0x10a0540b}, -+ {0x000010a8, 0xb101010a}, -+ {0x00001100, 0x33053460}, -+ {0x00001110, 0x0030101c}, -+ {0x00001138, 0xdf808444}, -+ {0x0000113c, 0xc8422204}, -+ {0x00001140, 0x8180088c}, -+ {0x00001174, 0x00000000}, -+ {0x00000e94, 0xc80060fa}, -+ {0x00000e80, 0x0fbf16d0}, -+ {0x00000e90, 0x10a0540b}, -+ {0x00000ea8, 0x7a01010a}, -+ {0x00000f00, 0xf5053460}, -+ {0x00000f10, 0xc030101c}, -+ {0x00000f38, 0xdf808444}, -+ {0x00000f3c, 0xc8422204}, -+ {0x00000f40, 0x8180088c}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x2_port3_config_regs[] = { -+ {0x00001894, 0xc80060fa}, -+ {0x00001880, 0x0f90fd6a}, -+ {0x00001890, 0x10a0840b}, -+ {0x000018a8, 0xdf04010a}, -+ {0x00001900, 0x57050060}, -+ {0x00001910, 0x0030001c}, -+ {0x00001938, 0x5f004444}, -+ {0x0000193c, 0x78464204}, -+ {0x00001948, 0x7821f940}, -+ {0x0000194c, 0xb2000433}, -+ {0x00001694, 0xc80060fa}, -+ {0x00001680, 0x0ef6d058}, -+ {0x00001690, 0x10a0540b}, -+ {0x000016a8, 0x7a01010a}, -+ {0x00001700, 0x69053460}, -+ {0x00001710, 0xa030101c}, -+ {0x00001738, 0xdf808444}, -+ {0x0000173c, 0xc8422204}, -+ {0x00001740, 0x0180088c}, -+ {0x00001774, 0x00000000}, -+ {0x00001494, 0xc80060fa}, -+ {0x00001480, 0xf9d34bd0}, -+ {0x00001490, 0x10a0540b}, -+ {0x000014a8, 0x7a01010a}, -+ {0x00001500, 0x1b053460}, -+ {0x00001510, 0x0030101c}, -+ {0x00001538, 0xdf808444}, -+ {0x0000153c, 0xc8422204}, -+ {0x00001540, 0x8180088c}, -+ {0x00001574, 0x00000000}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x4_port0_config_regs[] = { -+ {0x00000694, 0xc80060fa}, -+ {0x00000680, 0x3d4f78fa}, -+ {0x00000690, 0x10a0140b}, -+ {0x000006a8, 0xdf04010a}, -+ {0x00000700, 0x57050060}, -+ {0x00000710, 0x0030001c}, -+ {0x00000738, 0x5f004444}, -+ {0x0000073c, 0x78464204}, -+ {0x00000748, 0x7821f940}, -+ {0x0000074c, 0xb2000433}, -+ {0x00000494, 0xfe6030fa}, -+ {0x00000480, 0x29ef5ed8}, -+ {0x00000490, 0x10a0540b}, -+ {0x000004a8, 0x7a01010a}, -+ {0x00000500, 0xef053460}, -+ {0x00000510, 0xe030101c}, -+ {0x00000538, 0xdf808444}, -+ {0x0000053c, 0xc8422204}, -+ {0x00000540, 0x0180088c}, -+ {0x00000574, 0x00000004}, -+ {0x00000294, 0x23e030fa}, -+ {0x00000280, 0xcb45b950}, -+ {0x00000290, 0x10a0540b}, -+ {0x000002a8, 0x8c01010a}, -+ {0x00000300, 0xef053460}, -+ {0x00000310, 0x8030101c}, -+ {0x00000338, 0x41808444}, -+ {0x0000033c, 0x32422204}, -+ {0x00000340, 0x0180088c}, -+ {0x00000374, 0x00000004}, -+ {0x00000894, 0x5620b0fa}, -+ {0x00000880, 0x4d4f21dc}, -+ {0x00000890, 0x10a0540b}, -+ {0x000008a8, 0x5601010a}, -+ {0x00000900, 0xef053460}, -+ {0x00000910, 0x8030101c}, -+ {0x00000938, 0xdf808444}, -+ {0x0000093c, 0xc8422204}, -+ {0x00000940, 0x0180088c}, -+ {0x00000974, 0x00000004}, -+ {0x00000a94, 0xc91030fa}, -+ {0x00000a80, 0x5a166ecc}, -+ {0x00000a90, 0x10a0540b}, -+ {0x00000aa8, 0x5d01010a}, -+ {0x00000b00, 0xef053460}, -+ {0x00000b10, 0xa030101c}, -+ {0x00000b38, 0xdf808444}, -+ {0x00000b3c, 0xc8422204}, -+ {0x00000b40, 0x0180088c}, -+ {0x00000b74, 0x00000004}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x4_port1_config_regs[] = { -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x4_port2_config_regs[] = { -+ {0x00001294, 0x28f000fa}, -+ {0x00001280, 0x08130cfa}, -+ {0x00001290, 0x10c0140b}, -+ {0x000012a8, 0xd704010a}, -+ {0x00001300, 0x8d050060}, -+ {0x00001310, 0x0030001c}, -+ {0x00001338, 0xdf008444}, -+ {0x0000133c, 0x78422204}, -+ {0x00001348, 0x7821f940}, -+ {0x0000134c, 0x5a000433}, -+ {0x00001094, 0x2d20b0fa}, -+ {0x00001080, 0xade75dd8}, -+ {0x00001090, 0x10a0540b}, -+ {0x000010a8, 0xb101010a}, -+ {0x00001100, 0x33053460}, -+ {0x00001110, 0x0030101c}, -+ {0x00001138, 0xdf808444}, -+ {0x0000113c, 0xc8422204}, -+ {0x00001140, 0x8180088c}, -+ {0x00001174, 0x00000004}, -+ {0x00000e94, 0xd308d0fa}, -+ {0x00000e80, 0x0fbf16d0}, -+ {0x00000e90, 0x10a0540b}, -+ {0x00000ea8, 0x2c01010a}, -+ {0x00000f00, 0xf5053460}, -+ {0x00000f10, 0xc030101c}, -+ {0x00000f38, 0xdf808444}, -+ {0x00000f3c, 0xc8422204}, -+ {0x00000f40, 0x8180088c}, -+ {0x00000f74, 0x00000004}, -+ {0x00001494, 0x136850fa}, -+ {0x00001480, 0xf9d34bdc}, -+ {0x00001490, 0x10a0540b}, -+ {0x000014a8, 0x5a01010a}, -+ {0x00001500, 0x1b053460}, -+ {0x00001510, 0x0030101c}, -+ {0x00001538, 0xdf808444}, -+ {0x0000153c, 0xc8422204}, -+ {0x00001540, 0x8180088c}, -+ {0x00001574, 0x00000004}, -+ {0x00001694, 0x3050d0fa}, -+ {0x00001680, 0x0ef6d04c}, -+ {0x00001690, 0x10a0540b}, -+ {0x000016a8, 0xe301010a}, -+ {0x00001700, 0x69053460}, -+ {0x00001710, 0xa030101c}, -+ {0x00001738, 0xdf808444}, -+ {0x0000173c, 0xc8422204}, -+ {0x00001740, 0x0180088c}, -+ {0x00001774, 0x00000004}, -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg x4_port3_config_regs[] = { -+ {0x00000000, 0x00000000} -+}; -+ -+static const struct phy_reg *x1_config_regs[4] = { -+ x1_port0_config_regs, -+ x1_port1_config_regs, -+ x1_port2_config_regs, -+ x1_port3_config_regs -+}; -+ -+static const struct phy_reg *x2_config_regs[4] = { -+ x2_port0_config_regs, -+ x2_port1_config_regs, -+ x2_port2_config_regs, -+ x2_port3_config_regs -+}; -+ -+static const struct phy_reg *x4_config_regs[4] = { -+ x4_port0_config_regs, -+ x4_port1_config_regs, -+ x4_port2_config_regs, -+ x4_port3_config_regs -+}; -+ -+static const struct phy_reg **config_regs[3] = { -+ x1_config_regs, -+ x2_config_regs, -+ x4_config_regs -+}; -+ -+static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ u32 val; -+ int ret; -+ -+ val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); -+ val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN; -+ writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); -+ -+ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), -+ val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK, -+ 200, MCD_PHY_POWER_STATUS_TIMEOUT); -+ if (ret) -+ dev_err(dev, "PHY%d powerup ack timeout", id); -+ -+ return ret; -+} -+ -+static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ u32 val; -+ int ret; -+ -+ writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); -+ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), -+ val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK), -+ 200, MCD_PHY_POWER_STATUS_TIMEOUT); -+ if (ret) -+ dev_err(dev, "PHY%d powerdown ack timeout", id); -+ -+ return ret; -+} -+ -+static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert) -+{ -+ void __iomem *isys_base = isys->pdata->base; -+ u32 val; -+ -+ val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); -+ if (assert) -+ val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET; -+ else -+ val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET); -+ -+ writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); -+} -+ -+static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ u32 val; -+ int ret; -+ -+ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), -+ val, val & CSI_REG_HUB_GPREG_PHY_READY, -+ 200, MCD_PHY_POWER_STATUS_TIMEOUT); -+ if (ret) -+ dev_err(dev, "PHY%d ready ack timeout", id); -+ -+ return ret; -+} -+ -+static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys) -+{ -+ struct ipu6_bus_device *adev = isys->adev; -+ struct ipu6_device *isp = adev->isp; -+ void __iomem *isp_base = isp->base; -+ struct sensor_async_sd *s_asd; -+ struct v4l2_async_connection *asc; -+ void __iomem *phy_base; -+ unsigned int i; -+ u8 phy_id; -+ -+ list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { -+ s_asd = container_of(asc, struct sensor_async_sd, asc); -+ phy_id = s_asd->csi2.port / 4; -+ phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); -+ -+ for (i = 0; i < ARRAY_SIZE(common_init_regs); i++) -+ writel(common_init_regs[i].val, -+ phy_base + common_init_regs[i].reg); -+ } -+} -+ -+static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg) -+{ -+ int phy_port; -+ int ret; -+ -+ if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1)) -+ return -EINVAL; -+ -+ /* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */ -+ /* normalize driver port number */ -+ phy_port = cfg->port % 4; -+ -+ /* swap port number only for A and B */ -+ if (phy_port == 0) -+ phy_port = 1; -+ else if (phy_port == 1) -+ phy_port = 0; -+ -+ ret = phy_port; -+ -+ /* check validity per lane configuration */ -+ if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2)) -+ ret = -EINVAL; -+ else if ((cfg->nlanes == 2 || cfg->nlanes == 1) && -+ !(phy_port >= 0 && phy_port <= 3)) -+ ret = -EINVAL; -+ -+ return ret; -+} -+ -+static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_bus_device *adev = isys->adev; -+ const struct phy_reg **phy_config_regs; -+ struct ipu6_device *isp = adev->isp; -+ void __iomem *isp_base = isp->base; -+ struct sensor_async_sd *s_asd; -+ struct ipu6_isys_csi2_config cfg; -+ struct v4l2_async_connection *asc; -+ u8 phy_port, phy_id; -+ unsigned int i; -+ void __iomem *phy_base; -+ -+ list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { -+ s_asd = container_of(asc, struct sensor_async_sd, asc); -+ cfg.port = s_asd->csi2.port; -+ cfg.nlanes = s_asd->csi2.nlanes; -+ phy_port = ipu6_isys_driver_port_to_phy_port(&cfg); -+ if (phy_port < 0) { -+ dev_err(dev, "invalid port %d for lane %d", cfg.port, -+ cfg.nlanes); -+ return -ENXIO; -+ } -+ -+ phy_id = cfg.port / 4; -+ phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); -+ dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id, -+ cfg.nlanes); -+ -+ phy_config_regs = config_regs[cfg.nlanes / 2]; -+ cfg.port = phy_port; -+ for (i = 0; phy_config_regs[cfg.port][i].reg; i++) -+ writel(phy_config_regs[cfg.port][i].val, -+ phy_base + phy_config_regs[cfg.port][i].reg); -+ } -+ -+ return 0; -+} -+ -+#define CSI_MCD_PHY_NUM 2 -+static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM]; -+ -+int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ void __iomem *isys_base = isys->pdata->base; -+ u8 port, phy_id; -+ refcount_t *ref; -+ int ret; -+ -+ port = cfg->port; -+ phy_id = port / 4; -+ -+ ref = &phy_power_ref_count[phy_id]; -+ -+ dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port, -+ cfg->nlanes); -+ -+ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { -+ dev_warn(dev, "invalid port ID %d\n", port); -+ return -EINVAL; -+ } -+ -+ if (on) { -+ if (refcount_read(ref)) { -+ dev_dbg(dev, "for phy %d is already UP", phy_id); -+ refcount_inc(ref); -+ return 0; -+ } -+ -+ ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id); -+ if (ret) -+ return ret; -+ -+ ipu6_isys_mcd_phy_reset(isys, phy_id, 0); -+ ipu6_isys_mcd_phy_common_init(isys); -+ -+ ret = ipu6_isys_mcd_phy_config(isys); -+ if (ret) -+ return ret; -+ -+ ipu6_isys_mcd_phy_reset(isys, phy_id, 1); -+ ret = ipu6_isys_mcd_phy_ready(isys, phy_id); -+ if (ret) -+ return ret; -+ -+ refcount_set(ref, 1); -+ return 0; -+ } -+ -+ if (!refcount_dec_and_test(ref)) -+ return 0; -+ -+ return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id); -+} --- -2.43.2 - - -From 5d8544bd1d6dc7fce10a3bf01d10d926b8990b54 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:24 +0800 -Subject: [PATCH 17/33] media: intel/ipu6: add input system driver - -Input system driver do basic isys hardware setup and irq handling -and work with fwnode and v4l2 to register the ISYS v4l2 devices. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - drivers/media/pci/intel/ipu6/ipu6-isys.c | 1353 ++++++++++++++++++++++ - drivers/media/pci/intel/ipu6/ipu6-isys.h | 207 ++++ - 2 files changed, 1560 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c -new file mode 100644 -index 000000000000..e8983363a0da ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c -@@ -0,0 +1,1353 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/auxiliary_bus.h> -+#include <linux/bitfield.h> -+#include <linux/bits.h> -+#include <linux/completion.h> -+#include <linux/container_of.h> -+#include <linux/delay.h> -+#include <linux/device.h> -+#include <linux/dma-mapping.h> -+#include <linux/err.h> -+#include <linux/firmware.h> -+#include <linux/io.h> -+#include <linux/irqreturn.h> -+#include <linux/list.h> -+#include <linux/module.h> -+#include <linux/mutex.h> -+#include <linux/pci.h> -+#include <linux/pm_runtime.h> -+#include <linux/pm_qos.h> -+#include <linux/slab.h> -+#include <linux/spinlock.h> -+#include <linux/string.h> -+ -+#include <media/ipu-bridge.h> -+#include <media/media-device.h> -+#include <media/media-entity.h> -+#include <media/v4l2-async.h> -+#include <media/v4l2-device.h> -+#include <media/v4l2-fwnode.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-cpd.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-mmu.h" -+#include "ipu6-platform-buttress-regs.h" -+#include "ipu6-platform-isys-csi2-reg.h" -+#include "ipu6-platform-regs.h" -+ -+#define IPU6_BUTTRESS_FABIC_CONTROL 0x68 -+#define GDA_ENABLE_IWAKE_INDEX 2 -+#define GDA_IWAKE_THRESHOLD_INDEX 1 -+#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX 0 -+#define GDA_MEMOPEN_THRESHOLD_INDEX 3 -+#define DEFAULT_DID_RATIO 90 -+#define DEFAULT_IWAKE_THRESHOLD 0x42 -+#define DEFAULT_MEM_OPEN_TIME 10 -+#define ONE_THOUSAND_MICROSECOND 1000 -+/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */ -+#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE 0x800 -+ -+/* LTR & DID value are 10 bit at most */ -+#define LTR_DID_VAL_MAX 1023 -+#define LTR_DEFAULT_VALUE 0x70503c19 -+#define FILL_TIME_DEFAULT_VALUE 0xfff0783c -+#define LTR_DID_PKGC_2R 20 -+#define LTR_SCALE_DEFAULT 5 -+#define LTR_SCALE_1024NS 2 -+#define DID_SCALE_1US 2 -+#define DID_SCALE_32US 3 -+#define REG_PKGC_PMON_CFG 0xb00 -+ -+#define VAL_PKGC_PMON_CFG_RESET 0x38 -+#define VAL_PKGC_PMON_CFG_START 0x7 -+ -+#define IS_PIXEL_BUFFER_PAGES 0x80 -+/* -+ * when iwake mode is disabled, the critical threshold is statically set -+ * to 75% of the IS pixel buffer, criticalThreshold = (128 * 3) / 4 -+ */ -+#define CRITICAL_THRESHOLD_IWAKE_DISABLE (IS_PIXEL_BUFFER_PAGES * 3 / 4) -+ -+union fabric_ctrl { -+ struct { -+ u16 ltr_val : 10; -+ u16 ltr_scale : 3; -+ u16 reserved : 3; -+ u16 did_val : 10; -+ u16 did_scale : 3; -+ u16 reserved2 : 1; -+ u16 keep_power_in_D0 : 1; -+ u16 keep_power_override : 1; -+ } bits; -+ u32 value; -+}; -+ -+enum ltr_did_type { -+ LTR_IWAKE_ON, -+ LTR_IWAKE_OFF, -+ LTR_ISYS_ON, -+ LTR_ISYS_OFF, -+ LTR_ENHANNCE_IWAKE, -+ LTR_TYPE_MAX -+}; -+ -+#define ISYS_PM_QOS_VALUE 300 -+ -+static int isys_isr_one(struct ipu6_bus_device *adev); -+ -+static int -+isys_complete_ext_device_registration(struct ipu6_isys *isys, -+ struct v4l2_subdev *sd, -+ struct ipu6_isys_csi2_config *csi2) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ unsigned int i; -+ int ret; -+ -+ for (i = 0; i < sd->entity.num_pads; i++) { -+ if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) -+ break; -+ } -+ -+ if (i == sd->entity.num_pads) { -+ dev_warn(dev, "no src pad in external entity\n"); -+ ret = -ENOENT; -+ goto unregister_subdev; -+ } -+ -+ ret = media_create_pad_link(&sd->entity, i, -+ &isys->csi2[csi2->port].asd.sd.entity, -+ 0, 0); -+ if (ret) { -+ dev_warn(dev, "can't create link\n"); -+ goto unregister_subdev; -+ } -+ -+ isys->csi2[csi2->port].nlanes = csi2->nlanes; -+ -+ return 0; -+ -+unregister_subdev: -+ v4l2_device_unregister_subdev(sd); -+ -+ return ret; -+} -+ -+static void isys_stream_init(struct ipu6_isys *isys) -+{ -+ u32 i; -+ -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { -+ mutex_init(&isys->streams[i].mutex); -+ init_completion(&isys->streams[i].stream_open_completion); -+ init_completion(&isys->streams[i].stream_close_completion); -+ init_completion(&isys->streams[i].stream_start_completion); -+ init_completion(&isys->streams[i].stream_stop_completion); -+ INIT_LIST_HEAD(&isys->streams[i].queues); -+ isys->streams[i].isys = isys; -+ isys->streams[i].stream_handle = i; -+ isys->streams[i].vc = INVALID_VC_ID; -+ } -+} -+ -+static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys) -+{ -+ const struct ipu6_isys_internal_csi2_pdata *csi2 = -+ &isys->pdata->ipdata->csi2; -+ unsigned int i; -+ -+ for (i = 0; i < csi2->nports; i++) -+ ipu6_isys_csi2_cleanup(&isys->csi2[i]); -+} -+ -+static int isys_csi2_register_subdevices(struct ipu6_isys *isys) -+{ -+ const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = -+ &isys->pdata->ipdata->csi2; -+ struct device *dev = &isys->adev->auxdev.dev; -+ unsigned int i; -+ int ret; -+ -+ isys->csi2 = devm_kcalloc(dev, csi2_pdata->nports, -+ sizeof(*isys->csi2), GFP_KERNEL); -+ if (!isys->csi2) -+ return -ENOMEM; -+ -+ for (i = 0; i < csi2_pdata->nports; i++) { -+ ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, -+ isys->pdata->base + -+ csi2_pdata->offsets[i], i); -+ if (ret) -+ goto fail; -+ -+ isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); -+ } -+ -+ return 0; -+ -+fail: -+ while (i--) -+ ipu6_isys_csi2_cleanup(&isys->csi2[i]); -+ -+ return ret; -+} -+ -+static int isys_csi2_create_media_links(struct ipu6_isys *isys) -+{ -+ const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = -+ &isys->pdata->ipdata->csi2; -+ struct device *dev = &isys->adev->auxdev.dev; -+ unsigned int i, j, k; -+ int ret; -+ -+ for (i = 0; i < csi2_pdata->nports; i++) { -+ struct media_entity *sd = &isys->csi2[i].asd.sd.entity; -+ -+ for (j = 0; j < NR_OF_VIDEO_DEVICE; j++) { -+ struct media_entity *v = &isys->av[j].vdev.entity; -+ u32 flag = MEDIA_LNK_FL_DYNAMIC; -+ -+ for (k = CSI2_PAD_SRC; k < NR_OF_CSI2_PADS; k++) { -+ ret = media_create_pad_link(sd, k, v, 0, flag); -+ if (ret) { -+ dev_err(dev, "CSI2 can't create link\n"); -+ return ret; -+ } -+ } -+ } -+ } -+ -+ return 0; -+} -+ -+static void isys_unregister_video_devices(struct ipu6_isys *isys) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) -+ ipu6_isys_video_cleanup(&isys->av[i]); -+} -+ -+static int isys_register_video_devices(struct ipu6_isys *isys) -+{ -+ unsigned int i; -+ int ret; -+ -+ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) { -+ snprintf(isys->av[i].vdev.name, sizeof(isys->av[i].vdev.name), -+ IPU6_ISYS_ENTITY_PREFIX " ISYS Capture %u", i); -+ isys->av[i].isys = isys; -+ isys->av[i].aq.vbq.buf_struct_size = -+ sizeof(struct ipu6_isys_video_buffer); -+ isys->av[i].pfmt = &ipu6_isys_pfmts[0]; -+ -+ ret = ipu6_isys_video_init(&isys->av[i]); -+ if (ret) -+ goto fail; -+ } -+ -+ return 0; -+ -+fail: -+ while (i--) -+ ipu6_isys_video_cleanup(&isys->av[i]); -+ -+ return ret; -+} -+ -+void isys_setup_hw(struct ipu6_isys *isys) -+{ -+ void __iomem *base = isys->pdata->base; -+ const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; -+ u32 irqs = 0; -+ unsigned int i, nports; -+ -+ nports = isys->pdata->ipdata->csi2.nports; -+ -+ /* Enable irqs for all MIPI ports */ -+ for (i = 0; i < nports; i++) -+ irqs |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); -+ -+ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_edge); -+ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_lnp); -+ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_mask); -+ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_enable); -+ writel(GENMASK(19, 0), -+ base + isys->pdata->ipdata->csi2.ctrl0_irq_clear); -+ -+ irqs = ISYS_UNISPART_IRQS; -+ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_EDGE); -+ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE); -+ writel(GENMASK(28, 0), base + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); -+ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); -+ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_ENABLE); -+ -+ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); -+ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG); -+ -+ /* Write CDC FIFO threshold values for isys */ -+ for (i = 0; i < isys->pdata->ipdata->hw_variant.cdc_fifos; i++) -+ writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i)); -+} -+ -+static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) -+{ -+ struct ipu6_isys_stream *stream; -+ unsigned int i; -+ u32 status; -+ int source; -+ -+ ipu6_isys_register_errors(csi2); -+ -+ status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); -+ -+ writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + -+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); -+ -+ source = csi2->asd.source; -+ for (i = 0; i < NR_OF_CSI2_VC; i++) { -+ if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { -+ stream = ipu6_isys_query_stream_by_source(csi2->isys, -+ source, i); -+ if (stream) { -+ ipu6_isys_csi2_sof_event_by_stream(stream); -+ ipu6_isys_put_stream(stream); -+ } -+ } -+ -+ if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { -+ stream = ipu6_isys_query_stream_by_source(csi2->isys, -+ source, i); -+ if (stream) { -+ ipu6_isys_csi2_eof_event_by_stream(stream); -+ ipu6_isys_put_stream(stream); -+ } -+ } -+ } -+} -+ -+irqreturn_t isys_isr(struct ipu6_bus_device *adev) -+{ -+ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); -+ void __iomem *base = isys->pdata->base; -+ u32 status_sw, status_csi; -+ u32 ctrl0_status, ctrl0_clear; -+ -+ spin_lock(&isys->power_lock); -+ if (!isys->power) { -+ spin_unlock(&isys->power_lock); -+ return IRQ_NONE; -+ } -+ -+ ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; -+ ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; -+ -+ status_csi = readl(isys->pdata->base + ctrl0_status); -+ status_sw = readl(isys->pdata->base + -+ IPU6_REG_ISYS_UNISPART_IRQ_STATUS); -+ -+ writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, -+ base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); -+ -+ do { -+ writel(status_csi, isys->pdata->base + ctrl0_clear); -+ -+ writel(status_sw, isys->pdata->base + -+ IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); -+ -+ if (isys->isr_csi2_bits & status_csi) { -+ unsigned int i; -+ -+ for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { -+ /* irq from not enabled port */ -+ if (!isys->csi2[i].base) -+ continue; -+ if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) -+ ipu6_isys_csi2_isr(&isys->csi2[i]); -+ } -+ } -+ -+ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); -+ -+ if (!isys_isr_one(adev)) -+ status_sw = IPU6_ISYS_UNISPART_IRQ_SW; -+ else -+ status_sw = 0; -+ -+ status_csi = readl(isys->pdata->base + ctrl0_status); -+ status_sw |= readl(isys->pdata->base + -+ IPU6_REG_ISYS_UNISPART_IRQ_STATUS); -+ } while ((status_csi & isys->isr_csi2_bits) || -+ (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); -+ -+ writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); -+ -+ spin_unlock(&isys->power_lock); -+ -+ return IRQ_HANDLED; -+} -+ -+static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did) -+{ -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ struct ltr_did ltrdid_default; -+ -+ ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE; -+ ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; -+ -+ if (iwake_watermark->ltrdid.lut_ltr.value) -+ *pltr_did = iwake_watermark->ltrdid; -+ else -+ *pltr_did = ltrdid_default; -+} -+ -+static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ u32 req_id = index; -+ u32 offset = 0; -+ int ret; -+ -+ ret = ipu6_fw_isys_send_proxy_token(isys, req_id, index, offset, value); -+ if (ret) -+ dev_err(dev, "write %d failed %d", index, ret); -+ -+ return ret; -+} -+ -+/* -+ * When input system is powered up and before enabling any new sensor capture, -+ * or after disabling any sensor capture the following values need to be set: -+ * LTR_value = LTR(usec) from calculation; -+ * LTR_scale = 2; -+ * DID_value = DID(usec) from calculation; -+ * DID_scale = 2; -+ * -+ * When input system is powered down, the LTR and DID values -+ * must be returned to the default values: -+ * LTR_value = 1023; -+ * LTR_scale = 5; -+ * DID_value = 1023; -+ * DID_scale = 2; -+ */ -+static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did, -+ enum ltr_did_type use) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ u16 ltr_val, ltr_scale = LTR_SCALE_1024NS; -+ u16 did_val, did_scale = DID_SCALE_1US; -+ struct ipu6_device *isp = isys->adev->isp; -+ union fabric_ctrl fc; -+ -+ switch (use) { -+ case LTR_IWAKE_ON: -+ ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX); -+ did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX); -+ ltr_scale = (ltr == LTR_DID_VAL_MAX && -+ did == LTR_DID_VAL_MAX) ? -+ LTR_SCALE_DEFAULT : LTR_SCALE_1024NS; -+ break; -+ case LTR_ISYS_ON: -+ case LTR_IWAKE_OFF: -+ ltr_val = LTR_DID_PKGC_2R; -+ did_val = LTR_DID_PKGC_2R; -+ break; -+ case LTR_ISYS_OFF: -+ ltr_val = LTR_DID_VAL_MAX; -+ did_val = LTR_DID_VAL_MAX; -+ ltr_scale = LTR_SCALE_DEFAULT; -+ break; -+ case LTR_ENHANNCE_IWAKE: -+ if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) { -+ ltr_val = LTR_DID_VAL_MAX; -+ did_val = LTR_DID_VAL_MAX; -+ ltr_scale = LTR_SCALE_DEFAULT; -+ } else if (did < ONE_THOUSAND_MICROSECOND) { -+ ltr_val = ltr; -+ did_val = did; -+ } else { -+ ltr_val = ltr; -+ /* div 90% value by 32 to account for scale change */ -+ did_val = did / 32; -+ did_scale = DID_SCALE_32US; -+ } -+ break; -+ default: -+ ltr_val = LTR_DID_VAL_MAX; -+ did_val = LTR_DID_VAL_MAX; -+ ltr_scale = LTR_SCALE_DEFAULT; -+ break; -+ } -+ -+ fc.value = readl(isp->base + IPU6_BUTTRESS_FABIC_CONTROL); -+ fc.bits.ltr_val = ltr_val; -+ fc.bits.ltr_scale = ltr_scale; -+ fc.bits.did_val = did_val; -+ fc.bits.did_scale = did_scale; -+ -+ dev_dbg(dev, "ltr: value %u scale %u, did: value %u scale %u\n", -+ ltr_val, ltr_scale, did_val, did_scale); -+ writel(fc.value, isp->base + IPU6_BUTTRESS_FABIC_CONTROL); -+} -+ -+/* -+ * Driver may clear register GDA_ENABLE_IWAKE before FW configures the -+ * stream for debug purpose. Otherwise driver should not access this register. -+ */ -+static void enable_iwake(struct ipu6_isys *isys, bool enable) -+{ -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ int ret; -+ -+ mutex_lock(&iwake_watermark->mutex); -+ -+ if (iwake_watermark->iwake_enabled == enable) { -+ mutex_unlock(&iwake_watermark->mutex); -+ return; -+ } -+ -+ ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable); -+ if (!ret) -+ iwake_watermark->iwake_enabled = enable; -+ -+ mutex_unlock(&iwake_watermark->mutex); -+} -+ -+void update_watermark_setting(struct ipu6_isys *isys) -+{ -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ u32 iwake_threshold, iwake_critical_threshold, page_num; -+ struct device *dev = &isys->adev->auxdev.dev; -+ u32 calc_fill_time_us = 0, ltr = 0, did = 0; -+ struct video_stream_watermark *p_watermark; -+ enum ltr_did_type ltr_did_type; -+ struct list_head *stream_node; -+ u64 isys_pb_datarate_mbs = 0; -+ u32 mem_open_threshold = 0; -+ struct ltr_did ltrdid; -+ u64 threshold_bytes; -+ u32 max_sram_size; -+ u32 shift; -+ -+ shift = isys->pdata->ipdata->sram_gran_shift; -+ max_sram_size = isys->pdata->ipdata->max_sram_size; -+ -+ mutex_lock(&iwake_watermark->mutex); -+ if (iwake_watermark->force_iwake_disable) { -+ set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); -+ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, -+ CRITICAL_THRESHOLD_IWAKE_DISABLE); -+ goto unlock_exit; -+ } -+ -+ if (list_empty(&iwake_watermark->video_list)) { -+ isys_pb_datarate_mbs = 0; -+ } else { -+ list_for_each(stream_node, &iwake_watermark->video_list) { -+ p_watermark = list_entry(stream_node, -+ struct video_stream_watermark, -+ stream_node); -+ isys_pb_datarate_mbs += p_watermark->stream_data_rate; -+ } -+ } -+ mutex_unlock(&iwake_watermark->mutex); -+ -+ if (!isys_pb_datarate_mbs) { -+ enable_iwake(isys, false); -+ set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); -+ mutex_lock(&iwake_watermark->mutex); -+ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, -+ CRITICAL_THRESHOLD_IWAKE_DISABLE); -+ goto unlock_exit; -+ } -+ -+ enable_iwake(isys, true); -+ calc_fill_time_us = max_sram_size / isys_pb_datarate_mbs; -+ -+ if (isys->pdata->ipdata->enhanced_iwake) { -+ ltr = isys->pdata->ipdata->ltr; -+ did = calc_fill_time_us * DEFAULT_DID_RATIO / 100; -+ ltr_did_type = LTR_ENHANNCE_IWAKE; -+ } else { -+ get_lut_ltrdid(isys, <rdid); -+ -+ if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0) -+ ltr = 0; -+ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1) -+ ltr = ltrdid.lut_ltr.bits.val0; -+ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2) -+ ltr = ltrdid.lut_ltr.bits.val1; -+ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3) -+ ltr = ltrdid.lut_ltr.bits.val2; -+ else -+ ltr = ltrdid.lut_ltr.bits.val3; -+ -+ did = calc_fill_time_us - ltr; -+ ltr_did_type = LTR_IWAKE_ON; -+ } -+ -+ set_iwake_ltrdid(isys, ltr, did, ltr_did_type); -+ -+ /* calculate iwake threshold with 2KB granularity pages */ -+ threshold_bytes = did * isys_pb_datarate_mbs; -+ iwake_threshold = max_t(u32, 1, threshold_bytes >> shift); -+ iwake_threshold = min_t(u32, iwake_threshold, max_sram_size); -+ -+ mutex_lock(&iwake_watermark->mutex); -+ if (isys->pdata->ipdata->enhanced_iwake) { -+ set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, -+ DEFAULT_IWAKE_THRESHOLD); -+ /* calculate number of pages that will be filled in 10 usec */ -+ page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) / -+ ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE; -+ page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) % -+ ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0; -+ mem_open_threshold = isys->pdata->ipdata->memopen_threshold; -+ mem_open_threshold = max_t(u32, mem_open_threshold, page_num); -+ dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold); -+ set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX, -+ mem_open_threshold); -+ } else { -+ set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, -+ iwake_threshold); -+ } -+ -+ iwake_critical_threshold = iwake_threshold + -+ (IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2; -+ -+ dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold, -+ iwake_critical_threshold); -+ -+ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, -+ iwake_critical_threshold); -+ -+ writel(VAL_PKGC_PMON_CFG_RESET, -+ isys->adev->isp->base + REG_PKGC_PMON_CFG); -+ writel(VAL_PKGC_PMON_CFG_START, -+ isys->adev->isp->base + REG_PKGC_PMON_CFG); -+unlock_exit: -+ mutex_unlock(&iwake_watermark->mutex); -+} -+ -+static void isys_iwake_watermark_init(struct ipu6_isys *isys) -+{ -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ -+ INIT_LIST_HEAD(&iwake_watermark->video_list); -+ mutex_init(&iwake_watermark->mutex); -+ -+ iwake_watermark->ltrdid.lut_ltr.value = 0; -+ iwake_watermark->isys = isys; -+ iwake_watermark->iwake_enabled = false; -+ iwake_watermark->force_iwake_disable = false; -+} -+ -+static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys) -+{ -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ -+ mutex_lock(&iwake_watermark->mutex); -+ list_del(&iwake_watermark->video_list); -+ mutex_unlock(&iwake_watermark->mutex); -+ -+ mutex_destroy(&iwake_watermark->mutex); -+} -+ -+/* The .bound() notifier callback when a match is found */ -+static int isys_notifier_bound(struct v4l2_async_notifier *notifier, -+ struct v4l2_subdev *sd, -+ struct v4l2_async_connection *asc) -+{ -+ struct ipu6_isys *isys = -+ container_of(notifier, struct ipu6_isys, notifier); -+ struct sensor_async_sd *s_asd = -+ container_of(asc, struct sensor_async_sd, asc); -+ int ret; -+ -+ ret = ipu_bridge_instantiate_vcm(sd->dev); -+ if (ret) { -+ dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n"); -+ return ret; -+ } -+ -+ dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n", -+ sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); -+ ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); -+ if (ret) -+ return ret; -+ -+ return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); -+} -+ -+static int isys_notifier_complete(struct v4l2_async_notifier *notifier) -+{ -+ struct ipu6_isys *isys = -+ container_of(notifier, struct ipu6_isys, notifier); -+ -+ return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); -+} -+ -+static const struct v4l2_async_notifier_operations isys_async_ops = { -+ .bound = isys_notifier_bound, -+ .complete = isys_notifier_complete, -+}; -+ -+#define ISYS_MAX_PORTS 8 -+static int isys_notifier_init(struct ipu6_isys *isys) -+{ -+ struct ipu6_device *isp = isys->adev->isp; -+ struct device *dev = &isp->pdev->dev; -+ unsigned int i; -+ int ret; -+ -+ v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev); -+ -+ for (i = 0; i < ISYS_MAX_PORTS; i++) { -+ struct v4l2_fwnode_endpoint vep = { -+ .bus_type = V4L2_MBUS_CSI2_DPHY -+ }; -+ struct sensor_async_sd *s_asd; -+ struct fwnode_handle *ep; -+ -+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, -+ FWNODE_GRAPH_ENDPOINT_NEXT); -+ if (!ep) -+ continue; -+ -+ ret = v4l2_fwnode_endpoint_parse(ep, &vep); -+ if (ret) { -+ dev_err(dev, "fwnode endpoint parse failed: %d\n", ret); -+ goto err_parse; -+ } -+ -+ s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep, -+ struct sensor_async_sd); -+ if (IS_ERR(s_asd)) { -+ ret = PTR_ERR(s_asd); -+ dev_err(dev, "add remove fwnode failed: %d\n", ret); -+ goto err_parse; -+ } -+ -+ s_asd->csi2.port = vep.base.port; -+ s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; -+ -+ dev_dbg(dev, "remote endpoint port %d with %d lanes added\n", -+ s_asd->csi2.port, s_asd->csi2.nlanes); -+ -+ fwnode_handle_put(ep); -+ -+ continue; -+ -+err_parse: -+ fwnode_handle_put(ep); -+ return ret; -+ } -+ -+ isys->notifier.ops = &isys_async_ops; -+ ret = v4l2_async_nf_register(&isys->notifier); -+ if (ret) { -+ dev_err(dev, "failed to register async notifier : %d\n", ret); -+ v4l2_async_nf_cleanup(&isys->notifier); -+ } -+ -+ return ret; -+} -+ -+static void isys_notifier_cleanup(struct ipu6_isys *isys) -+{ -+ v4l2_async_nf_unregister(&isys->notifier); -+ v4l2_async_nf_cleanup(&isys->notifier); -+} -+ -+static int isys_register_devices(struct ipu6_isys *isys) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct pci_dev *pdev = isys->adev->isp->pdev; -+ int ret; -+ -+ isys->media_dev.dev = dev; -+ media_device_pci_init(&isys->media_dev, -+ pdev, IPU6_MEDIA_DEV_MODEL_NAME); -+ -+ strscpy(isys->v4l2_dev.name, isys->media_dev.model, -+ sizeof(isys->v4l2_dev.name)); -+ -+ ret = media_device_register(&isys->media_dev); -+ if (ret < 0) -+ goto out_media_device_unregister; -+ -+ isys->v4l2_dev.mdev = &isys->media_dev; -+ isys->v4l2_dev.ctrl_handler = NULL; -+ -+ ret = v4l2_device_register(dev->parent, &isys->v4l2_dev); -+ if (ret < 0) -+ goto out_media_device_unregister; -+ -+ ret = isys_register_video_devices(isys); -+ if (ret) -+ goto out_v4l2_device_unregister; -+ -+ ret = isys_csi2_register_subdevices(isys); -+ if (ret) -+ goto out_isys_unregister_video_device; -+ -+ ret = isys_csi2_create_media_links(isys); -+ if (ret) -+ goto out_isys_unregister_subdevices; -+ -+ ret = isys_notifier_init(isys); -+ if (ret) -+ goto out_isys_unregister_subdevices; -+ -+ return 0; -+ -+out_isys_unregister_subdevices: -+ isys_csi2_unregister_subdevices(isys); -+ -+out_isys_unregister_video_device: -+ isys_unregister_video_devices(isys); -+ -+out_v4l2_device_unregister: -+ v4l2_device_unregister(&isys->v4l2_dev); -+ -+out_media_device_unregister: -+ media_device_unregister(&isys->media_dev); -+ media_device_cleanup(&isys->media_dev); -+ -+ dev_err(dev, "failed to register isys devices\n"); -+ -+ return ret; -+} -+ -+static void isys_unregister_devices(struct ipu6_isys *isys) -+{ -+ isys_unregister_video_devices(isys); -+ isys_csi2_unregister_subdevices(isys); -+ v4l2_device_unregister(&isys->v4l2_dev); -+ media_device_unregister(&isys->media_dev); -+ media_device_cleanup(&isys->media_dev); -+} -+ -+static int isys_runtime_pm_resume(struct device *dev) -+{ -+ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); -+ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); -+ struct ipu6_device *isp = adev->isp; -+ unsigned long flags; -+ int ret; -+ -+ if (!isys) -+ return 0; -+ -+ ret = ipu6_mmu_hw_init(adev->mmu); -+ if (ret) -+ return ret; -+ -+ cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); -+ -+ ret = ipu6_buttress_start_tsc_sync(isp); -+ if (ret) -+ return ret; -+ -+ spin_lock_irqsave(&isys->power_lock, flags); -+ isys->power = 1; -+ spin_unlock_irqrestore(&isys->power_lock, flags); -+ -+ isys_setup_hw(isys); -+ -+ set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); -+ -+ return 0; -+} -+ -+static int isys_runtime_pm_suspend(struct device *dev) -+{ -+ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); -+ struct ipu6_isys *isys; -+ unsigned long flags; -+ -+ isys = dev_get_drvdata(dev); -+ if (!isys) -+ return 0; -+ -+ spin_lock_irqsave(&isys->power_lock, flags); -+ isys->power = 0; -+ spin_unlock_irqrestore(&isys->power_lock, flags); -+ -+ mutex_lock(&isys->mutex); -+ isys->need_reset = false; -+ mutex_unlock(&isys->mutex); -+ -+ isys->phy_termcal_val = 0; -+ cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); -+ -+ set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); -+ -+ ipu6_mmu_hw_cleanup(adev->mmu); -+ -+ return 0; -+} -+ -+static int isys_suspend(struct device *dev) -+{ -+ struct ipu6_isys *isys = dev_get_drvdata(dev); -+ -+ /* If stream is open, refuse to suspend */ -+ if (isys->stream_opened) -+ return -EBUSY; -+ -+ return 0; -+} -+ -+static int isys_resume(struct device *dev) -+{ -+ return 0; -+} -+ -+static const struct dev_pm_ops isys_pm_ops = { -+ .runtime_suspend = isys_runtime_pm_suspend, -+ .runtime_resume = isys_runtime_pm_resume, -+ .suspend = isys_suspend, -+ .resume = isys_resume, -+}; -+ -+static void isys_remove(struct auxiliary_device *auxdev) -+{ -+ struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); -+ struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev); -+ struct ipu6_device *isp = adev->isp; -+ struct isys_fw_msgs *fwmsg, *safe; -+ unsigned int i; -+ -+ list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head) -+ dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs), -+ fwmsg, fwmsg->dma_addr, 0); -+ -+ list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head) -+ dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs), -+ fwmsg, fwmsg->dma_addr, 0); -+ -+ isys_unregister_devices(isys); -+ isys_notifier_cleanup(isys); -+ -+ cpu_latency_qos_remove_request(&isys->pm_qos); -+ -+ if (!isp->secure_mode) { -+ ipu6_cpd_free_pkg_dir(adev); -+ ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); -+ release_firmware(adev->fw); -+ } -+ -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) -+ mutex_destroy(&isys->streams[i].mutex); -+ -+ isys_iwake_watermark_cleanup(isys); -+ mutex_destroy(&isys->stream_mutex); -+ mutex_destroy(&isys->mutex); -+} -+ -+static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount) -+{ -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct isys_fw_msgs *addr; -+ dma_addr_t dma_addr; -+ unsigned long flags; -+ unsigned int i; -+ -+ for (i = 0; i < amount; i++) { -+ addr = dma_alloc_attrs(dev, sizeof(struct isys_fw_msgs), -+ &dma_addr, GFP_KERNEL, 0); -+ if (!addr) -+ break; -+ addr->dma_addr = dma_addr; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ list_add(&addr->head, &isys->framebuflist); -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ } -+ -+ if (i == amount) -+ return 0; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ while (!list_empty(&isys->framebuflist)) { -+ addr = list_first_entry(&isys->framebuflist, -+ struct isys_fw_msgs, head); -+ list_del(&addr->head); -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ dma_free_attrs(dev, sizeof(struct isys_fw_msgs), addr, -+ addr->dma_addr, 0); -+ spin_lock_irqsave(&isys->listlock, flags); -+ } -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ -+ return -ENOMEM; -+} -+ -+struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream) -+{ -+ struct ipu6_isys *isys = stream->isys; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct isys_fw_msgs *msg; -+ unsigned long flags; -+ int ret; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ if (list_empty(&isys->framebuflist)) { -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ dev_dbg(dev, "Frame list empty\n"); -+ -+ ret = alloc_fw_msg_bufs(isys, 5); -+ if (ret < 0) -+ return NULL; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ if (list_empty(&isys->framebuflist)) { -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ dev_err(dev, "Frame list empty\n"); -+ return NULL; -+ } -+ } -+ msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); -+ list_move(&msg->head, &isys->framebuflist_fw); -+ spin_unlock_irqrestore(&isys->listlock, flags); -+ memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); -+ -+ return msg; -+} -+ -+void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys) -+{ -+ struct isys_fw_msgs *fwmsg, *fwmsg0; -+ unsigned long flags; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head) -+ list_move(&fwmsg->head, &isys->framebuflist); -+ spin_unlock_irqrestore(&isys->listlock, flags); -+} -+ -+void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data) -+{ -+ struct isys_fw_msgs *msg; -+ unsigned long flags; -+ u64 *ptr = (u64 *)data; -+ -+ if (!ptr) -+ return; -+ -+ spin_lock_irqsave(&isys->listlock, flags); -+ msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); -+ list_move(&msg->head, &isys->framebuflist); -+ spin_unlock_irqrestore(&isys->listlock, flags); -+} -+ -+static int isys_probe(struct auxiliary_device *auxdev, -+ const struct auxiliary_device_id *auxdev_id) -+{ -+ struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); -+ struct ipu6_device *isp = adev->isp; -+ const struct firmware *fw; -+ struct ipu6_isys *isys; -+ unsigned int i; -+ int ret; -+ -+ if (!isp->bus_ready_to_probe) -+ return -EPROBE_DEFER; -+ -+ isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL); -+ if (!isys) -+ return -ENOMEM; -+ -+ ret = ipu6_mmu_hw_init(adev->mmu); -+ if (ret) -+ return ret; -+ -+ adev->auxdrv_data = -+ (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; -+ adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); -+ isys->adev = adev; -+ isys->pdata = adev->pdata; -+ -+ /* initial sensor type */ -+ isys->sensor_type = isys->pdata->ipdata->sensor_type_start; -+ -+ spin_lock_init(&isys->streams_lock); -+ spin_lock_init(&isys->power_lock); -+ isys->power = 0; -+ isys->phy_termcal_val = 0; -+ -+ mutex_init(&isys->mutex); -+ mutex_init(&isys->stream_mutex); -+ -+ spin_lock_init(&isys->listlock); -+ INIT_LIST_HEAD(&isys->framebuflist); -+ INIT_LIST_HEAD(&isys->framebuflist_fw); -+ -+ isys->line_align = IPU6_ISYS_2600_MEM_LINE_ALIGN; -+ isys->icache_prefetch = 0; -+ -+ dev_set_drvdata(&auxdev->dev, isys); -+ -+ isys_stream_init(isys); -+ -+ if (!isp->secure_mode) { -+ fw = isp->cpd_fw; -+ ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt); -+ if (ret) -+ goto release_firmware; -+ -+ ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); -+ if (ret) -+ goto remove_shared_buffer; -+ } -+ -+ cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); -+ -+ ret = alloc_fw_msg_bufs(isys, 20); -+ if (ret < 0) -+ goto out_remove_pkg_dir_shared_buffer; -+ -+ isys_iwake_watermark_init(isys); -+ -+ if (is_ipu6se(adev->isp->hw_ver)) -+ isys->phy_set_power = ipu6_isys_jsl_phy_set_power; -+ else if (is_ipu6ep_mtl(adev->isp->hw_ver)) -+ isys->phy_set_power = ipu6_isys_dwc_phy_set_power; -+ else -+ isys->phy_set_power = ipu6_isys_mcd_phy_set_power; -+ -+ ret = isys_register_devices(isys); -+ if (ret) -+ goto out_remove_pkg_dir_shared_buffer; -+ -+ ipu6_mmu_hw_cleanup(adev->mmu); -+ -+ return 0; -+ -+out_remove_pkg_dir_shared_buffer: -+ if (!isp->secure_mode) -+ ipu6_cpd_free_pkg_dir(adev); -+remove_shared_buffer: -+ if (!isp->secure_mode) -+ ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); -+release_firmware: -+ if (!isp->secure_mode) -+ release_firmware(adev->fw); -+ -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) -+ mutex_destroy(&isys->streams[i].mutex); -+ -+ mutex_destroy(&isys->mutex); -+ mutex_destroy(&isys->stream_mutex); -+ -+ ipu6_mmu_hw_cleanup(adev->mmu); -+ -+ return ret; -+} -+ -+struct fwmsg { -+ int type; -+ char *msg; -+ bool valid_ts; -+}; -+ -+static const struct fwmsg fw_msg[] = { -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, -+ "STREAM_START_AND_CAPTURE_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, -+ "STREAM_START_AND_CAPTURE_DONE", 1}, -+ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, -+ {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, -+ {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, -+ {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, -+ {-1, "UNKNOWN MESSAGE", 0} -+}; -+ -+static u32 resp_type_to_index(int type) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < ARRAY_SIZE(fw_msg); i++) -+ if (fw_msg[i].type == type) -+ return i; -+ -+ return ARRAY_SIZE(fw_msg) - 1; -+} -+ -+static int isys_isr_one(struct ipu6_bus_device *adev) -+{ -+ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); -+ struct ipu6_fw_isys_resp_info_abi *resp; -+ struct ipu6_isys_stream *stream; -+ struct ipu6_isys_csi2 *csi2 = NULL; -+ u32 index; -+ u64 ts; -+ -+ if (!isys->fwcom) -+ return 1; -+ -+ resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); -+ if (!resp) -+ return 1; -+ -+ ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; -+ -+ index = resp_type_to_index(resp->type); -+ dev_dbg(&adev->auxdev.dev, -+ "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", -+ resp->type, fw_msg[index].msg, resp->stream_handle, -+ fw_msg[index].valid_ts ? ts : 0, resp->pin_id); -+ -+ if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) -+ /* Suspension is kind of special case: not enough buffers */ -+ dev_dbg(&adev->auxdev.dev, -+ "FW error resp SUSPENSION, details %d\n", -+ resp->error_info.error_details); -+ else if (resp->error_info.error) -+ dev_dbg(&adev->auxdev.dev, -+ "FW error resp error %d, details %d\n", -+ resp->error_info.error, resp->error_info.error_details); -+ -+ if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { -+ dev_err(&adev->auxdev.dev, "bad stream handle %u\n", -+ resp->stream_handle); -+ goto leave; -+ } -+ -+ stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); -+ if (!stream) { -+ dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", -+ resp->stream_handle); -+ goto leave; -+ } -+ stream->error = resp->error_info.error; -+ -+ csi2 = ipu6_isys_subdev_to_csi2(stream->asd); -+ -+ switch (resp->type) { -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: -+ complete(&stream->stream_open_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: -+ complete(&stream->stream_close_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: -+ complete(&stream->stream_start_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: -+ complete(&stream->stream_start_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: -+ complete(&stream->stream_stop_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: -+ complete(&stream->stream_stop_completion); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: -+ /* -+ * firmware only release the capture msg until software -+ * get pin_data_ready event -+ */ -+ ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); -+ if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && -+ stream->output_pins[resp->pin_id].pin_ready) -+ stream->output_pins[resp->pin_id].pin_ready(stream, -+ resp); -+ else -+ dev_warn(&adev->auxdev.dev, -+ "%d:No data pin ready handler for pin id %d\n", -+ resp->stream_handle, resp->pin_id); -+ if (csi2) -+ ipu6_isys_csi2_error(csi2); -+ -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: -+ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: -+ -+ ipu6_isys_csi2_sof_event_by_stream(stream); -+ stream->seq[stream->seq_index].sequence = -+ atomic_read(&stream->sequence) - 1; -+ stream->seq[stream->seq_index].timestamp = ts; -+ dev_dbg(&adev->auxdev.dev, -+ "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", -+ resp->stream_handle, -+ stream->seq[stream->seq_index].sequence, ts); -+ stream->seq_index = (stream->seq_index + 1) -+ % IPU6_ISYS_MAX_PARALLEL_SOF; -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: -+ ipu6_isys_csi2_eof_event_by_stream(stream); -+ dev_dbg(&adev->auxdev.dev, -+ "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", -+ resp->stream_handle, -+ stream->seq[stream->seq_index].sequence, ts); -+ break; -+ case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: -+ break; -+ default: -+ dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", -+ resp->stream_handle, resp->type); -+ break; -+ } -+ -+ ipu6_isys_put_stream(stream); -+leave: -+ ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); -+ return 0; -+} -+ -+static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { -+ .isr = isys_isr, -+ .isr_threaded = NULL, -+ .wake_isr_thread = false, -+}; -+ -+static const struct auxiliary_device_id ipu6_isys_id_table[] = { -+ { -+ .name = "intel_ipu6.isys", -+ .driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data, -+ }, -+ { } -+}; -+MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table); -+ -+static struct auxiliary_driver isys_driver = { -+ .name = IPU6_ISYS_NAME, -+ .probe = isys_probe, -+ .remove = isys_remove, -+ .id_table = ipu6_isys_id_table, -+ .driver = { -+ .pm = &isys_pm_ops, -+ }, -+}; -+ -+module_auxiliary_driver(isys_driver); -+ -+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); -+MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); -+MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); -+MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>"); -+MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>"); -+MODULE_LICENSE("GPL"); -+MODULE_DESCRIPTION("Intel IPU6 input system driver"); -+MODULE_IMPORT_NS(INTEL_IPU6); -+MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h -new file mode 100644 -index 000000000000..cf7a90bfedc9 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h -@@ -0,0 +1,207 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_ISYS_H -+#define IPU6_ISYS_H -+ -+#include <linux/irqreturn.h> -+#include <linux/list.h> -+#include <linux/mutex.h> -+#include <linux/pm_qos.h> -+#include <linux/spinlock_types.h> -+#include <linux/types.h> -+ -+#include <media/media-device.h> -+#include <media/v4l2-async.h> -+#include <media/v4l2-device.h> -+ -+#include "ipu6.h" -+#include "ipu6-fw-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-isys-video.h" -+ -+struct ipu6_bus_device; -+ -+#define IPU6_ISYS_ENTITY_PREFIX "Intel IPU6" -+/* FW support max 16 streams */ -+#define IPU6_ISYS_MAX_STREAMS 16 -+#define ISYS_UNISPART_IRQS (IPU6_ISYS_UNISPART_IRQ_SW | \ -+ IPU6_ISYS_UNISPART_IRQ_CSI0 | \ -+ IPU6_ISYS_UNISPART_IRQ_CSI1) -+ -+#define IPU6_ISYS_2600_MEM_LINE_ALIGN 64 -+ -+/* -+ * Current message queue configuration. These must be big enough -+ * so that they never gets full. Queues are located in system memory -+ */ -+#define IPU6_ISYS_SIZE_RECV_QUEUE 40 -+#define IPU6_ISYS_SIZE_SEND_QUEUE 40 -+#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5 -+#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5 -+#define IPU6_ISYS_NUM_RECV_QUEUE 1 -+ -+#define IPU6_ISYS_MIN_WIDTH 1U -+#define IPU6_ISYS_MIN_HEIGHT 1U -+#define IPU6_ISYS_MAX_WIDTH 4672U -+#define IPU6_ISYS_MAX_HEIGHT 3416U -+ -+/* the threshold granularity is 2KB on IPU6 */ -+#define IPU6_SRAM_GRANULARITY_SHIFT 11 -+#define IPU6_SRAM_GRANULARITY_SIZE 2048 -+/* the threshold granularity is 1KB on IPU6SE */ -+#define IPU6SE_SRAM_GRANULARITY_SHIFT 10 -+#define IPU6SE_SRAM_GRANULARITY_SIZE 1024 -+/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */ -+#define IPU6_MAX_SRAM_SIZE (200 << 10) -+/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */ -+#define IPU6SE_MAX_SRAM_SIZE (96 << 10) -+ -+#define IPU6EP_LTR_VALUE 200 -+#define IPU6EP_MIN_MEMOPEN_TH 0x4 -+#define IPU6EP_MTL_LTR_VALUE 1023 -+#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc -+ -+struct ltr_did { -+ union { -+ u32 value; -+ struct { -+ u8 val0; -+ u8 val1; -+ u8 val2; -+ u8 val3; -+ } bits; -+ } lut_ltr; -+ union { -+ u32 value; -+ struct { -+ u8 th0; -+ u8 th1; -+ u8 th2; -+ u8 th3; -+ } bits; -+ } lut_fill_time; -+}; -+ -+struct isys_iwake_watermark { -+ bool iwake_enabled; -+ bool force_iwake_disable; -+ u32 iwake_threshold; -+ u64 isys_pixelbuffer_datarate; -+ struct ltr_did ltrdid; -+ struct mutex mutex; /* protect whole struct */ -+ struct ipu6_isys *isys; -+ struct list_head video_list; -+}; -+ -+struct ipu6_isys_csi2_config { -+ u32 nlanes; -+ u32 port; -+}; -+ -+struct sensor_async_sd { -+ struct v4l2_async_connection asc; -+ struct ipu6_isys_csi2_config csi2; -+}; -+ -+/* -+ * struct ipu6_isys -+ * -+ * @media_dev: Media device -+ * @v4l2_dev: V4L2 device -+ * @adev: ISYS bus device -+ * @power: Is ISYS powered on or not? -+ * @isr_bits: Which bits does the ISR handle? -+ * @power_lock: Serialise access to power (power state in general) -+ * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers -+ * @streams_lock: serialise access to streams -+ * @streams: streams per firmware stream ID -+ * @fwcom: fw communication layer private pointer -+ * or optional external library private pointer -+ * @line_align: line alignment in memory -+ * @phy_termcal_val: the termination calibration value, only used for DWC PHY -+ * @need_reset: Isys requires d0i0->i3 transition -+ * @ref_count: total number of callers fw open -+ * @mutex: serialise access isys video open/release related operations -+ * @stream_mutex: serialise stream start and stop, queueing requests -+ * @pdata: platform data pointer -+ * @csi2: CSI-2 receivers -+ */ -+struct ipu6_isys { -+ struct media_device media_dev; -+ struct v4l2_device v4l2_dev; -+ struct ipu6_bus_device *adev; -+ -+ int power; -+ spinlock_t power_lock; -+ u32 isr_csi2_bits; -+ u32 csi2_rx_ctrl_cached; -+ spinlock_t streams_lock; -+ struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS]; -+ int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; -+ void *fwcom; -+ unsigned int line_align; -+ u32 phy_termcal_val; -+ bool need_reset; -+ bool icache_prefetch; -+ bool csi2_cse_ipc_not_supported; -+ unsigned int ref_count; -+ unsigned int stream_opened; -+ unsigned int sensor_type; -+ -+ struct mutex mutex; -+ struct mutex stream_mutex; -+ -+ struct ipu6_isys_pdata *pdata; -+ -+ int (*phy_set_power)(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on); -+ -+ struct ipu6_isys_csi2 *csi2; -+ struct ipu6_isys_video av[NR_OF_VIDEO_DEVICE]; -+ -+ struct pm_qos_request pm_qos; -+ spinlock_t listlock; /* Protect framebuflist */ -+ struct list_head framebuflist; -+ struct list_head framebuflist_fw; -+ struct v4l2_async_notifier notifier; -+ struct isys_iwake_watermark iwake_watermark; -+}; -+ -+struct isys_fw_msgs { -+ union { -+ u64 dummy; -+ struct ipu6_fw_isys_frame_buff_set_abi frame; -+ struct ipu6_fw_isys_stream_cfg_data_abi stream; -+ } fw_msg; -+ struct list_head head; -+ dma_addr_t dma_addr; -+}; -+ -+struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); -+void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data); -+void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); -+ -+extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; -+ -+void isys_setup_hw(struct ipu6_isys *isys); -+irqreturn_t isys_isr(struct ipu6_bus_device *adev); -+void update_watermark_setting(struct ipu6_isys *isys); -+ -+int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on); -+ -+int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on); -+ -+int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, -+ struct ipu6_isys_csi2_config *cfg, -+ const struct ipu6_isys_csi2_timing *timing, -+ bool on); -+#endif /* IPU6_ISYS_H */ --- -2.43.2 - - -From 7cdb944c1cc8beb6f61268e2fe177d585fa5f415 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:25 +0800 -Subject: [PATCH 18/33] media: intel/ipu6: input system video capture nodes - -Register v4l2 video device and setup the vb2 queue to -support basic video capture. Video streaming callback -will trigger the input system driver to construct a -input system stream configuration for firmware based on -data type and stream ID and then queue buffers to firmware -to do capture. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - .../media/pci/intel/ipu6/ipu6-isys-queue.c | 825 +++++++++++ - .../media/pci/intel/ipu6/ipu6-isys-queue.h | 76 + - .../media/pci/intel/ipu6/ipu6-isys-video.c | 1253 +++++++++++++++++ - .../media/pci/intel/ipu6/ipu6-isys-video.h | 136 ++ - 4 files changed, 2290 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.h - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.c - create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.h - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c -new file mode 100644 -index 000000000000..735d2d642d87 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c -@@ -0,0 +1,825 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+#include <linux/atomic.h> -+#include <linux/bug.h> -+#include <linux/device.h> -+#include <linux/list.h> -+#include <linux/lockdep.h> -+#include <linux/mutex.h> -+#include <linux/spinlock.h> -+#include <linux/types.h> -+ -+#include <media/media-entity.h> -+#include <media/v4l2-subdev.h> -+#include <media/videobuf2-dma-contig.h> -+#include <media/videobuf2-v4l2.h> -+ -+#include "ipu6-bus.h" -+#include "ipu6-fw-isys.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-video.h" -+ -+static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, -+ unsigned int *num_planes, unsigned int sizes[], -+ struct device *alloc_devs[]) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ bool use_fmt = false; -+ unsigned int i; -+ u32 size; -+ -+ /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ -+ if (!*num_planes) { -+ use_fmt = true; -+ *num_planes = av->mpix.num_planes; -+ } -+ -+ for (i = 0; i < *num_planes; i++) { -+ size = av->mpix.plane_fmt[i].sizeimage; -+ if (use_fmt) { -+ sizes[i] = size; -+ } else if (sizes[i] < size) { -+ dev_err(dev, "%s: queue setup: plane %d size %u < %u\n", -+ av->vdev.name, i, sizes[i], size); -+ return -EINVAL; -+ } -+ -+ alloc_devs[i] = aq->dev; -+ } -+ -+ return 0; -+} -+ -+static int ipu6_isys_buf_prepare(struct vb2_buffer *vb) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ -+ dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", -+ av->vdev.name, av->mpix.plane_fmt[0].sizeimage, -+ vb2_plane_size(vb, 0)); -+ -+ if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0)) -+ return -EINVAL; -+ -+ vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline * -+ av->mpix.height); -+ vb->planes[0].data_offset = 0; -+ -+ return 0; -+} -+ -+/* -+ * Queue a buffer list back to incoming or active queues. The buffers -+ * are removed from the buffer list. -+ */ -+void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, -+ unsigned long op_flags, -+ enum vb2_buffer_state state) -+{ -+ struct ipu6_isys_buffer *ib, *ib_safe; -+ unsigned long flags; -+ bool first = true; -+ -+ if (!bl) -+ return; -+ -+ WARN_ON_ONCE(!bl->nbufs); -+ WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE && -+ op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING); -+ -+ list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { -+ struct ipu6_isys_video *av; -+ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ struct ipu6_isys_queue *aq = -+ vb2_queue_to_isys_queue(vb->vb2_queue); -+ struct device *dev; -+ -+ av = ipu6_isys_queue_to_video(aq); -+ dev = &av->isys->adev->auxdev.dev; -+ spin_lock_irqsave(&aq->lock, flags); -+ list_del(&ib->head); -+ if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE) -+ list_add(&ib->head, &aq->active); -+ else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING) -+ list_add_tail(&ib->head, &aq->incoming); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE) -+ vb2_buffer_done(vb, state); -+ -+ if (first) { -+ dev_dbg(dev, -+ "queue buf list %p flags %lx, s %d, %d bufs\n", -+ bl, op_flags, state, bl->nbufs); -+ first = false; -+ } -+ -+ bl->nbufs--; -+ } -+ -+ WARN_ON(bl->nbufs); -+} -+ -+/* -+ * flush_firmware_streamon_fail() - Flush in cases where requests may -+ * have been queued to firmware and the *firmware streamon fails for a -+ * reason or another. -+ */ -+static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) -+{ -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ struct ipu6_isys_queue *aq; -+ unsigned long flags; -+ -+ lockdep_assert_held(&stream->mutex); -+ -+ list_for_each_entry(aq, &stream->queues, node) { -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct ipu6_isys_buffer *ib, *ib_safe; -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { -+ struct vb2_buffer *vb = -+ ipu6_isys_buffer_to_vb2_buffer(ib); -+ -+ list_del(&ib->head); -+ if (av->streaming) { -+ dev_dbg(dev, -+ "%s: queue buffer %u back to incoming\n", -+ av->vdev.name, vb->index); -+ /* Queue already streaming, return to driver. */ -+ list_add(&ib->head, &aq->incoming); -+ continue; -+ } -+ /* Queue not yet streaming, return to user. */ -+ dev_dbg(dev, "%s: return %u back to videobuf2\n", -+ av->vdev.name, vb->index); -+ vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib), -+ VB2_BUF_STATE_QUEUED); -+ } -+ spin_unlock_irqrestore(&aq->lock, flags); -+ } -+} -+ -+/* -+ * Attempt obtaining a buffer list from the incoming queues, a list of buffers -+ * that contains one entry from each video buffer queue. If a buffer can't be -+ * obtained from every queue, the buffers are returned back to the queue. -+ */ -+static int buffer_list_get(struct ipu6_isys_stream *stream, -+ struct ipu6_isys_buffer_list *bl) -+{ -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ struct ipu6_isys_queue *aq; -+ unsigned long flags; -+ unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; -+ -+ bl->nbufs = 0; -+ INIT_LIST_HEAD(&bl->head); -+ -+ list_for_each_entry(aq, &stream->queues, node) { -+ struct ipu6_isys_buffer *ib; -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ if (list_empty(&aq->incoming)) { -+ spin_unlock_irqrestore(&aq->lock, flags); -+ if (!list_empty(&bl->head)) -+ ipu6_isys_buffer_list_queue(bl, buf_flag, 0); -+ return -ENODATA; -+ } -+ -+ ib = list_last_entry(&aq->incoming, -+ struct ipu6_isys_buffer, head); -+ -+ dev_dbg(dev, "buffer: %s: buffer %u\n", -+ ipu6_isys_queue_to_video(aq)->vdev.name, -+ ipu6_isys_buffer_to_vb2_buffer(ib)->index); -+ list_del(&ib->head); -+ list_add(&ib->head, &bl->head); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ bl->nbufs++; -+ } -+ -+ dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); -+ -+ return 0; -+} -+ -+static void -+ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, -+ struct ipu6_fw_isys_frame_buff_set_abi *set) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); -+ -+ set->output_pins[aq->fw_output].addr = -+ vb2_dma_contig_plane_dma_addr(vb, 0); -+ set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; -+} -+ -+/* -+ * Convert a buffer list to a isys fw ABI framebuffer set. The -+ * buffer list is not modified. -+ */ -+#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) -+void -+ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, -+ struct ipu6_isys_stream *stream, -+ struct ipu6_isys_buffer_list *bl) -+{ -+ struct ipu6_isys_buffer *ib; -+ -+ WARN_ON(!bl->nbufs); -+ -+ set->send_irq_sof = 1; -+ set->send_resp_sof = 1; -+ set->send_irq_eof = 0; -+ set->send_resp_eof = 0; -+ -+ if (stream->streaming) -+ set->send_irq_capture_ack = 0; -+ else -+ set->send_irq_capture_ack = 1; -+ set->send_irq_capture_done = 0; -+ -+ set->send_resp_capture_ack = 1; -+ set->send_resp_capture_done = 1; -+ if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { -+ set->send_resp_capture_ack = 0; -+ set->send_resp_capture_done = 0; -+ } -+ -+ list_for_each_entry(ib, &bl->head, head) { -+ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ -+ ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); -+ } -+} -+ -+/* Start streaming for real. The buffer list must be available. */ -+static int ipu6_isys_stream_start(struct ipu6_isys_video *av, -+ struct ipu6_isys_buffer_list *bl, bool error) -+{ -+ struct ipu6_isys_stream *stream = av->stream; -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ struct ipu6_isys_buffer_list __bl; -+ int ret; -+ -+ mutex_lock(&stream->isys->stream_mutex); -+ ret = ipu6_isys_video_set_streaming(av, 1, bl); -+ mutex_unlock(&stream->isys->stream_mutex); -+ if (ret) -+ goto out_requeue; -+ -+ stream->streaming = 1; -+ -+ bl = &__bl; -+ -+ do { -+ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; -+ struct isys_fw_msgs *msg; -+ u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; -+ -+ ret = buffer_list_get(stream, bl); -+ if (ret < 0) -+ break; -+ -+ msg = ipu6_get_fw_msg_buf(stream); -+ if (!msg) -+ return -ENOMEM; -+ -+ buf = &msg->fw_msg.frame; -+ ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); -+ ipu6_fw_isys_dump_frame_buff_set(dev, buf, -+ stream->nr_output_pins); -+ ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, -+ 0); -+ ret = ipu6_fw_isys_complex_cmd(stream->isys, -+ stream->stream_handle, buf, -+ msg->dma_addr, sizeof(*buf), -+ send_type); -+ } while (!WARN_ON(ret)); -+ -+ return 0; -+ -+out_requeue: -+ if (bl && bl->nbufs) -+ ipu6_isys_buffer_list_queue(bl, -+ (IPU6_ISYS_BUFFER_LIST_FL_INCOMING | -+ error) ? -+ IPU6_ISYS_BUFFER_LIST_FL_SET_STATE : -+ 0, error ? VB2_BUF_STATE_ERROR : -+ VB2_BUF_STATE_QUEUED); -+ flush_firmware_streamon_fail(stream); -+ -+ return ret; -+} -+ -+static void buf_queue(struct vb2_buffer *vb) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); -+ struct ipu6_isys_video_buffer *ivb = -+ vb2_buffer_to_ipu6_isys_video_buffer(vvb); -+ struct ipu6_isys_buffer *ib = &ivb->ib; -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct media_pipeline *media_pipe = -+ media_entity_pipeline(&av->vdev.entity); -+ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; -+ struct ipu6_isys_stream *stream = av->stream; -+ struct ipu6_isys_buffer_list bl; -+ struct isys_fw_msgs *msg; -+ unsigned long flags; -+ dma_addr_t dma; -+ unsigned int i; -+ int ret; -+ -+ dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); -+ -+ for (i = 0; i < vb->num_planes; i++) { -+ dma = vb2_dma_contig_plane_dma_addr(vb, i); -+ dev_dbg(dev, "iova: plane %u iova %pad\n", i, &dma); -+ } -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ list_add(&ib->head, &aq->incoming); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ if (!media_pipe || !vb->vb2_queue->start_streaming_called) { -+ dev_dbg(dev, "media pipeline is not ready for %s\n", -+ av->vdev.name); -+ return; -+ } -+ -+ mutex_lock(&stream->mutex); -+ -+ if (stream->nr_streaming != stream->nr_queues) { -+ dev_dbg(dev, "not streaming yet, adding to incoming\n"); -+ goto out; -+ } -+ -+ /* -+ * We just put one buffer to the incoming list of this queue -+ * (above). Let's see whether all queues in the pipeline would -+ * have a buffer. -+ */ -+ ret = buffer_list_get(stream, &bl); -+ if (ret < 0) { -+ dev_warn(dev, "No buffers available\n"); -+ goto out; -+ } -+ -+ msg = ipu6_get_fw_msg_buf(stream); -+ if (!msg) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ buf = &msg->fw_msg.frame; -+ ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); -+ ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); -+ -+ if (!stream->streaming) { -+ ret = ipu6_isys_stream_start(av, &bl, true); -+ if (ret) -+ dev_err(dev, "stream start failed.\n"); -+ goto out; -+ } -+ -+ /* -+ * We must queue the buffers in the buffer list to the -+ * appropriate video buffer queues BEFORE passing them to the -+ * firmware since we could get a buffer event back before we -+ * have queued them ourselves to the active queue. -+ */ -+ ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); -+ -+ ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, -+ buf, msg->dma_addr, sizeof(*buf), -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); -+ if (ret < 0) -+ dev_err(dev, "send stream capture failed\n"); -+ -+out: -+ mutex_unlock(&stream->mutex); -+} -+ -+static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) -+{ -+ struct v4l2_mbus_framefmt format; -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct media_pad *remote_pad = -+ media_pad_remote_pad_first(av->vdev.entity.pads); -+ struct v4l2_subdev *sd; -+ u32 r_stream; -+ int ret; -+ -+ if (!remote_pad) -+ return -ENOTCONN; -+ -+ sd = media_entity_to_v4l2_subdev(remote_pad->entity); -+ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); -+ -+ ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, -+ &format); -+ -+ if (ret) { -+ dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", -+ sd->entity.name, remote_pad->index, r_stream); -+ return ret; -+ } -+ -+ if (format.width != av->mpix.width || -+ format.height != av->mpix.height) { -+ dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", -+ av->mpix.width, av->mpix.height, -+ format.width, format.height); -+ return -EINVAL; -+ } -+ -+ if (format.field != av->mpix.field) { -+ dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n", -+ av->mpix.field, format.field); -+ return -EINVAL; -+ } -+ -+ if (format.code != av->pfmt->code) { -+ dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", -+ av->pfmt->code, format.code); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static void return_buffers(struct ipu6_isys_queue *aq, -+ enum vb2_buffer_state state) -+{ -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct ipu6_isys_buffer *ib; -+ bool need_reset = false; -+ unsigned long flags; -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ while (!list_empty(&aq->incoming)) { -+ struct vb2_buffer *vb; -+ -+ ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer, -+ head); -+ vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ list_del(&ib->head); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ vb2_buffer_done(vb, state); -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ } -+ -+ /* -+ * Something went wrong (FW crash / HW hang / not all buffers -+ * returned from isys) if there are still buffers queued in active -+ * queue. We have to clean up places a bit. -+ */ -+ while (!list_empty(&aq->active)) { -+ struct vb2_buffer *vb; -+ -+ ib = list_first_entry(&aq->active, struct ipu6_isys_buffer, -+ head); -+ vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ -+ list_del(&ib->head); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ vb2_buffer_done(vb, state); -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ need_reset = true; -+ } -+ -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ if (need_reset) { -+ mutex_lock(&av->isys->mutex); -+ av->isys->need_reset = true; -+ mutex_unlock(&av->isys->mutex); -+ } -+} -+ -+static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) -+{ -+ video_device_pipeline_stop(&av->vdev); -+ ipu6_isys_put_stream(av->stream); -+ av->stream = NULL; -+} -+ -+static int start_streaming(struct vb2_queue *q, unsigned int count) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct ipu6_isys_buffer_list __bl, *bl = NULL; -+ struct ipu6_isys_stream *stream; -+ struct media_entity *source_entity = NULL; -+ int nr_queues, ret; -+ -+ dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", -+ av->vdev.name, av->mpix.width, av->mpix.height, -+ av->pfmt->css_pixelformat); -+ -+ ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); -+ if (ret < 0) { -+ dev_err(dev, "failed to setup video\n"); -+ goto out_return_buffers; -+ } -+ -+ ret = ipu6_isys_link_fmt_validate(aq); -+ if (ret) { -+ dev_err(dev, -+ "%s: link format validation failed (%d)\n", -+ av->vdev.name, ret); -+ goto out_pipeline_stop; -+ } -+ -+ ret = ipu6_isys_fw_open(av->isys); -+ if (ret) -+ goto out_pipeline_stop; -+ -+ stream = av->stream; -+ mutex_lock(&stream->mutex); -+ if (!stream->nr_streaming) { -+ ret = ipu6_isys_video_prepare_stream(av, source_entity, -+ nr_queues); -+ if (ret) -+ goto out_fw_close; -+ } -+ -+ stream->nr_streaming++; -+ dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, -+ stream->nr_queues); -+ -+ list_add(&aq->node, &stream->queues); -+ ipu6_isys_set_csi2_streams_status(av, true); -+ ipu6_isys_configure_stream_watermark(av, true); -+ ipu6_isys_update_stream_watermark(av, true); -+ -+ if (stream->nr_streaming != stream->nr_queues) -+ goto out; -+ -+ bl = &__bl; -+ ret = buffer_list_get(stream, bl); -+ if (ret < 0) { -+ dev_dbg(dev, -+ "no buffer available, postponing streamon\n"); -+ goto out; -+ } -+ -+ ret = ipu6_isys_stream_start(av, bl, false); -+ if (ret) -+ goto out_stream_start; -+ -+out: -+ mutex_unlock(&stream->mutex); -+ -+ return 0; -+ -+out_stream_start: -+ list_del(&aq->node); -+ stream->nr_streaming--; -+ -+out_fw_close: -+ mutex_unlock(&stream->mutex); -+ ipu6_isys_fw_close(av->isys); -+ -+out_pipeline_stop: -+ ipu6_isys_stream_cleanup(av); -+ -+out_return_buffers: -+ return_buffers(aq, VB2_BUF_STATE_QUEUED); -+ -+ return ret; -+} -+ -+static void stop_streaming(struct vb2_queue *q) -+{ -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct ipu6_isys_stream *stream = av->stream; -+ -+ ipu6_isys_set_csi2_streams_status(av, false); -+ -+ mutex_lock(&stream->mutex); -+ -+ ipu6_isys_update_stream_watermark(av, false); -+ -+ mutex_lock(&av->isys->stream_mutex); -+ if (stream->nr_streaming == stream->nr_queues && stream->streaming) -+ ipu6_isys_video_set_streaming(av, 0, NULL); -+ mutex_unlock(&av->isys->stream_mutex); -+ -+ stream->nr_streaming--; -+ list_del(&aq->node); -+ stream->streaming = 0; -+ mutex_unlock(&stream->mutex); -+ -+ ipu6_isys_stream_cleanup(av); -+ -+ return_buffers(aq, VB2_BUF_STATE_ERROR); -+ -+ ipu6_isys_fw_close(av->isys); -+} -+ -+static unsigned int -+get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, -+ struct ipu6_fw_isys_resp_info_abi *info) -+{ -+ u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; -+ struct ipu6_isys *isys = stream->isys; -+ struct device *dev = &isys->adev->auxdev.dev; -+ unsigned int i; -+ -+ /* -+ * The timestamp is invalid as no TSC in some FPGA platform, -+ * so get the sequence from pipeline directly in this case. -+ */ -+ if (time == 0) -+ return atomic_read(&stream->sequence) - 1; -+ -+ for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) -+ if (time == stream->seq[i].timestamp) { -+ dev_dbg(dev, "sof: using seq nr %u for ts %llu\n", -+ stream->seq[i].sequence, time); -+ return stream->seq[i].sequence; -+ } -+ -+ for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) -+ dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n", -+ stream->seq[i].sequence, stream->seq[i].timestamp); -+ -+ return 0; -+} -+ -+static u64 get_sof_ns_delta(struct ipu6_isys_video *av, -+ struct ipu6_fw_isys_resp_info_abi *info) -+{ -+ struct ipu6_bus_device *adev = av->isys->adev; -+ struct ipu6_device *isp = adev->isp; -+ u64 delta, tsc_now; -+ -+ ipu6_buttress_tsc_read(isp, &tsc_now); -+ if (!tsc_now) -+ return 0; -+ -+ delta = tsc_now - ((u64)info->timestamp[1] << 32 | info->timestamp[0]); -+ -+ return ipu6_buttress_tsc_ticks_to_ns(delta, isp); -+} -+ -+void ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, -+ struct ipu6_fw_isys_resp_info_abi *info) -+{ -+ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); -+ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct ipu6_isys_stream *stream = av->stream; -+ u64 ns; -+ u32 sequence; -+ -+ ns = ktime_get_ns() - get_sof_ns_delta(av, info); -+ sequence = get_sof_sequence_by_timestamp(stream, info); -+ -+ vbuf->vb2_buf.timestamp = ns; -+ vbuf->sequence = sequence; -+ -+ dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", -+ av->vdev.name, ktime_get_ns(), sequence); -+ dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, -+ vbuf->vb2_buf.timestamp); -+} -+ -+void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) -+{ -+ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ -+ if (atomic_read(&ib->str2mmio_flag)) { -+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); -+ /* -+ * Operation on buffer is ended with error and will be reported -+ * to the userspace when it is de-queued -+ */ -+ atomic_set(&ib->str2mmio_flag, 0); -+ } else { -+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE); -+ } -+} -+ -+void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, -+ struct ipu6_fw_isys_resp_info_abi *info) -+{ -+ struct ipu6_isys_queue *aq = stream->output_pins[info->pin_id].aq; -+ struct ipu6_isys *isys = stream->isys; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct ipu6_isys_buffer *ib; -+ struct vb2_buffer *vb; -+ unsigned long flags; -+ bool first = true; -+ struct vb2_v4l2_buffer *buf; -+ -+ spin_lock_irqsave(&aq->lock, flags); -+ if (list_empty(&aq->active)) { -+ spin_unlock_irqrestore(&aq->lock, flags); -+ dev_err(dev, "active queue empty\n"); -+ return; -+ } -+ -+ list_for_each_entry_reverse(ib, &aq->active, head) { -+ dma_addr_t addr; -+ -+ vb = ipu6_isys_buffer_to_vb2_buffer(ib); -+ addr = vb2_dma_contig_plane_dma_addr(vb, 0); -+ -+ if (info->pin.addr != addr) { -+ if (first) -+ dev_err(dev, "Unexpected buffer address %pad\n", -+ &addr); -+ first = false; -+ continue; -+ } -+ -+ if (info->error_info.error == -+ IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { -+ /* -+ * Check for error message: -+ * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' -+ */ -+ atomic_set(&ib->str2mmio_flag, 1); -+ } -+ dev_dbg(dev, "buffer: found buffer %pad\n", &addr); -+ -+ buf = to_vb2_v4l2_buffer(vb); -+ buf->field = V4L2_FIELD_NONE; -+ -+ list_del(&ib->head); -+ spin_unlock_irqrestore(&aq->lock, flags); -+ -+ ipu6_isys_buf_calc_sequence_time(ib, info); -+ -+ ipu6_isys_queue_buf_done(ib); -+ -+ return; -+ } -+ -+ dev_err(dev, "Failed to find a matching video buffer"); -+ -+ spin_unlock_irqrestore(&aq->lock, flags); -+} -+ -+static const struct vb2_ops ipu6_isys_queue_ops = { -+ .queue_setup = queue_setup, -+ .wait_prepare = vb2_ops_wait_prepare, -+ .wait_finish = vb2_ops_wait_finish, -+ .buf_prepare = ipu6_isys_buf_prepare, -+ .start_streaming = start_streaming, -+ .stop_streaming = stop_streaming, -+ .buf_queue = buf_queue, -+}; -+ -+int ipu6_isys_queue_init(struct ipu6_isys_queue *aq) -+{ -+ struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys; -+ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); -+ int ret; -+ -+ /* no support for userptr */ -+ if (!aq->vbq.io_modes) -+ aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; -+ -+ aq->vbq.drv_priv = aq; -+ aq->vbq.ops = &ipu6_isys_queue_ops; -+ aq->vbq.lock = &av->mutex; -+ aq->vbq.mem_ops = &vb2_dma_contig_memops; -+ aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; -+ aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; -+ -+ ret = vb2_queue_init(&aq->vbq); -+ if (ret) -+ return ret; -+ -+ aq->dev = &isys->adev->auxdev.dev; -+ aq->vbq.dev = &isys->adev->auxdev.dev; -+ spin_lock_init(&aq->lock); -+ INIT_LIST_HEAD(&aq->active); -+ INIT_LIST_HEAD(&aq->incoming); -+ -+ return 0; -+} -+ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h -new file mode 100644 -index 000000000000..9fb454577bb5 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h -@@ -0,0 +1,76 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_ISYS_QUEUE_H -+#define IPU6_ISYS_QUEUE_H -+ -+#include <linux/container_of.h> -+#include <linux/atomic.h> -+#include <linux/device.h> -+#include <linux/list.h> -+#include <linux/spinlock_types.h> -+ -+#include <media/videobuf2-v4l2.h> -+ -+#include "ipu6-fw-isys.h" -+#include "ipu6-isys-video.h" -+ -+struct ipu6_isys_queue { -+ struct vb2_queue vbq; -+ struct list_head node; -+ struct device *dev; -+ /* -+ * @lock: serialise access to queued and pre_streamon_queued -+ */ -+ spinlock_t lock; -+ struct list_head active; -+ struct list_head incoming; -+ unsigned int fw_output; -+}; -+ -+struct ipu6_isys_buffer { -+ struct list_head head; -+ atomic_t str2mmio_flag; -+}; -+ -+struct ipu6_isys_video_buffer { -+ struct vb2_v4l2_buffer vb_v4l2; -+ struct ipu6_isys_buffer ib; -+}; -+ -+#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) -+#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) -+#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2) -+ -+struct ipu6_isys_buffer_list { -+ struct list_head head; -+ unsigned int nbufs; -+}; -+ -+#define vb2_queue_to_isys_queue(__vb2) \ -+ container_of(__vb2, struct ipu6_isys_queue, vbq) -+ -+#define ipu6_isys_to_isys_video_buffer(__ib) \ -+ container_of(__ib, struct ipu6_isys_video_buffer, ib) -+ -+#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \ -+ container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2) -+ -+#define ipu6_isys_buffer_to_vb2_buffer(__ib) \ -+ (&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) -+ -+void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, -+ unsigned long op_flags, -+ enum vb2_buffer_state state); -+void -+ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, -+ struct ipu6_isys_stream *stream, -+ struct ipu6_isys_buffer_list *bl); -+void -+ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, -+ struct ipu6_fw_isys_resp_info_abi *info); -+void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib); -+void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, -+ struct ipu6_fw_isys_resp_info_abi *info); -+int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); -+#endif /* IPU6_ISYS_QUEUE_H */ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -new file mode 100644 -index 000000000000..847eac26bcd6 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -@@ -0,0 +1,1253 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * Copyright (C) 2013 - 2023 Intel Corporation -+ */ -+ -+#include <linux/align.h> -+#include <linux/bits.h> -+#include <linux/bug.h> -+#include <linux/completion.h> -+#include <linux/container_of.h> -+#include <linux/device.h> -+#include <linux/list.h> -+#include <linux/math64.h> -+#include <linux/minmax.h> -+#include <linux/module.h> -+#include <linux/mutex.h> -+#include <linux/pm_runtime.h> -+#include <linux/spinlock.h> -+#include <linux/string.h> -+ -+#include <media/media-entity.h> -+#include <media/v4l2-ctrls.h> -+#include <media/v4l2-dev.h> -+#include <media/v4l2-fh.h> -+#include <media/v4l2-ioctl.h> -+#include <media/v4l2-subdev.h> -+#include <media/videobuf2-v4l2.h> -+ -+#include "ipu6.h" -+#include "ipu6-bus.h" -+#include "ipu6-cpd.h" -+#include "ipu6-fw-isys.h" -+#include "ipu6-isys.h" -+#include "ipu6-isys-csi2.h" -+#include "ipu6-isys-queue.h" -+#include "ipu6-isys-video.h" -+#include "ipu6-platform-regs.h" -+ -+const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { -+ {V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, -+ {V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, -+ {V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, -+ {V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, -+ {V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, -+ {V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, -+ {V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, -+ {V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, -+ {V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, -+ {V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, -+ {V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, -+ {V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, -+ {V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, -+ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, -+ {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, -+ {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, -+ {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, -+ {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, -+ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, -+}; -+ -+static int video_open(struct file *file) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ struct ipu6_isys *isys = av->isys; -+ struct ipu6_bus_device *adev = isys->adev; -+ -+ mutex_lock(&isys->mutex); -+ if (isys->need_reset) { -+ mutex_unlock(&isys->mutex); -+ dev_warn(&adev->auxdev.dev, "isys power cycle required\n"); -+ return -EIO; -+ } -+ mutex_unlock(&isys->mutex); -+ -+ return v4l2_fh_open(file); -+} -+ -+static int video_release(struct file *file) -+{ -+ return vb2_fop_release(file); -+} -+ -+static const struct ipu6_isys_pixelformat * -+ipu6_isys_get_pixelformat(u32 pixelformat) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { -+ const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i]; -+ -+ if (pfmt->pixelformat == pixelformat) -+ return pfmt; -+ } -+ -+ return &ipu6_isys_pfmts[0]; -+} -+ -+int ipu6_isys_vidioc_querycap(struct file *file, void *fh, -+ struct v4l2_capability *cap) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ -+ strscpy(cap->driver, IPU6_ISYS_NAME, sizeof(cap->driver)); -+ strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card)); -+ -+ return 0; -+} -+ -+int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, -+ struct v4l2_fmtdesc *f) -+{ -+ unsigned int i, found = 0; -+ -+ if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts)) -+ return -EINVAL; -+ -+ if (!f->mbus_code) { -+ f->flags = 0; -+ f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat; -+ return 0; -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { -+ if (f->mbus_code != ipu6_isys_pfmts[i].code) -+ continue; -+ -+ if (f->index == found) { -+ f->flags = 0; -+ f->pixelformat = ipu6_isys_pfmts[i].pixelformat; -+ return 0; -+ } -+ found++; -+ } -+ -+ return -EINVAL; -+} -+ -+static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, -+ struct v4l2_frmsizeenum *fsize) -+{ -+ if (fsize->index > 0) -+ return -EINVAL; -+ -+ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; -+ fsize->stepwise.min_width = IPU6_ISYS_MIN_WIDTH; -+ fsize->stepwise.max_width = IPU6_ISYS_MAX_WIDTH; -+ fsize->stepwise.min_height = IPU6_ISYS_MIN_HEIGHT; -+ fsize->stepwise.max_height = IPU6_ISYS_MAX_HEIGHT; -+ fsize->stepwise.step_width = 2; -+ fsize->stepwise.step_height = 2; -+ -+ return 0; -+} -+ -+static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh, -+ struct v4l2_format *fmt) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ -+ fmt->fmt.pix_mp = av->mpix; -+ -+ return 0; -+} -+ -+static const struct ipu6_isys_pixelformat * -+ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av, -+ struct v4l2_pix_format_mplane *mpix) -+{ -+ const struct ipu6_isys_pixelformat *pfmt = -+ ipu6_isys_get_pixelformat(mpix->pixelformat); -+ -+ mpix->pixelformat = pfmt->pixelformat; -+ mpix->num_planes = 1; -+ -+ mpix->width = clamp(mpix->width, IPU6_ISYS_MIN_WIDTH, -+ IPU6_ISYS_MAX_WIDTH); -+ mpix->height = clamp(mpix->height, IPU6_ISYS_MIN_HEIGHT, -+ IPU6_ISYS_MAX_HEIGHT); -+ -+ if (pfmt->bpp != pfmt->bpp_packed) -+ mpix->plane_fmt[0].bytesperline = -+ mpix->width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); -+ else -+ mpix->plane_fmt[0].bytesperline = -+ DIV_ROUND_UP((unsigned int)mpix->width * pfmt->bpp, -+ BITS_PER_BYTE); -+ -+ mpix->plane_fmt[0].bytesperline = ALIGN(mpix->plane_fmt[0].bytesperline, -+ av->isys->line_align); -+ -+ /* -+ * (height + 1) * bytesperline due to a hardware issue: the DMA unit -+ * is a power of two, and a line should be transferred as few units -+ * as possible. The result is that up to line length more data than -+ * the image size may be transferred to memory after the image. -+ * Another limitation is the GDA allocation unit size. For low -+ * resolution it gives a bigger number. Use larger one to avoid -+ * memory corruption. -+ */ -+ mpix->plane_fmt[0].sizeimage = -+ max(mpix->plane_fmt[0].sizeimage, -+ mpix->plane_fmt[0].bytesperline * mpix->height + -+ max(mpix->plane_fmt[0].bytesperline, -+ av->isys->pdata->ipdata->isys_dma_overshoot)); -+ -+ memset(mpix->plane_fmt[0].reserved, 0, -+ sizeof(mpix->plane_fmt[0].reserved)); -+ -+ mpix->field = V4L2_FIELD_NONE; -+ -+ mpix->colorspace = V4L2_COLORSPACE_RAW; -+ mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; -+ mpix->quantization = V4L2_QUANTIZATION_DEFAULT; -+ mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT; -+ -+ return pfmt; -+} -+ -+static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh, -+ struct v4l2_format *f) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ -+ if (av->aq.vbq.streaming) -+ return -EBUSY; -+ -+ av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); -+ av->mpix = f->fmt.pix_mp; -+ -+ return 0; -+} -+ -+static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh, -+ struct v4l2_format *f) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ -+ ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); -+ -+ return 0; -+} -+ -+static int link_validate(struct media_link *link) -+{ -+ struct ipu6_isys_video *av = -+ container_of(link->sink, struct ipu6_isys_video, pad); -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct v4l2_subdev_state *s_state; -+ struct v4l2_subdev *s_sd; -+ struct v4l2_mbus_framefmt *s_fmt; -+ struct media_pad *s_pad; -+ u32 s_stream; -+ int ret = -EPIPE; -+ -+ if (!link->source->entity) -+ return ret; -+ -+ s_sd = media_entity_to_v4l2_subdev(link->source->entity); -+ s_state = v4l2_subdev_get_unlocked_active_state(s_sd); -+ if (!s_state) -+ return ret; -+ -+ dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n", -+ link->source->entity->name, link->source->index, -+ link->sink->entity->name); -+ -+ s_pad = media_pad_remote_pad_first(&av->pad); -+ s_stream = ipu6_isys_get_src_stream_by_src_pad(s_sd, s_pad->index); -+ -+ v4l2_subdev_lock_state(s_state); -+ -+ s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index, -+ s_stream); -+ if (!s_fmt) { -+ dev_err(dev, "failed to get source pad format\n"); -+ goto unlock; -+ } -+ -+ if (s_fmt->width != av->mpix.width || -+ s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) { -+ dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", -+ s_fmt->width, s_fmt->height, s_fmt->code, -+ av->mpix.width, av->mpix.height, av->pfmt->code); -+ goto unlock; -+ } -+ -+ v4l2_subdev_unlock_state(s_state); -+ -+ return 0; -+unlock: -+ v4l2_subdev_unlock_state(s_state); -+ -+ return ret; -+} -+ -+static void get_stream_opened(struct ipu6_isys_video *av) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&av->isys->streams_lock, flags); -+ av->isys->stream_opened++; -+ spin_unlock_irqrestore(&av->isys->streams_lock, flags); -+} -+ -+static void put_stream_opened(struct ipu6_isys_video *av) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&av->isys->streams_lock, flags); -+ av->isys->stream_opened--; -+ spin_unlock_irqrestore(&av->isys->streams_lock, flags); -+} -+ -+static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, -+ struct ipu6_fw_isys_stream_cfg_data_abi *cfg) -+{ -+ struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); -+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); -+ struct ipu6_fw_isys_input_pin_info_abi *input_pin; -+ struct ipu6_fw_isys_output_pin_info_abi *output_pin; -+ struct ipu6_isys_stream *stream = av->stream; -+ struct ipu6_isys_queue *aq = &av->aq; -+ struct v4l2_mbus_framefmt fmt; -+ struct v4l2_rect v4l2_crop; -+ struct ipu6_isys *isys = av->isys; -+ struct device *dev = &isys->adev->auxdev.dev; -+ int input_pins = cfg->nof_input_pins++; -+ int output_pins; -+ u32 src_stream; -+ int ret; -+ -+ src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); -+ ret = ipu6_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream, -+ &fmt); -+ if (ret < 0) { -+ dev_err(dev, "can't get stream format (%d)\n", ret); -+ return ret; -+ } -+ -+ ret = ipu6_isys_get_stream_pad_crop(sd, src_pad->index, src_stream, -+ &v4l2_crop); -+ if (ret < 0) { -+ dev_err(dev, "can't get stream crop (%d)\n", ret); -+ return ret; -+ } -+ -+ input_pin = &cfg->input_pins[input_pins]; -+ input_pin->input_res.width = fmt.width; -+ input_pin->input_res.height = fmt.height; -+ input_pin->dt = av->dt; -+ input_pin->bits_per_pix = av->pfmt->bpp_packed; -+ input_pin->mapped_dt = 0x40; /* invalid mipi data type */ -+ input_pin->mipi_decompression = 0; -+ input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; -+ input_pin->mipi_store_mode = av->pfmt->bpp == av->pfmt->bpp_packed ? -+ IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : -+ IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; -+ input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; -+ -+ output_pins = cfg->nof_output_pins++; -+ aq->fw_output = output_pins; -+ stream->output_pins[output_pins].pin_ready = ipu6_isys_queue_buf_ready; -+ stream->output_pins[output_pins].aq = aq; -+ -+ output_pin = &cfg->output_pins[output_pins]; -+ output_pin->input_pin_id = input_pins; -+ output_pin->output_res.width = av->mpix.width; -+ output_pin->output_res.height = av->mpix.height; -+ -+ output_pin->stride = av->mpix.plane_fmt[0].bytesperline; -+ if (av->pfmt->bpp != av->pfmt->bpp_packed) -+ output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; -+ else -+ output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; -+ output_pin->ft = av->pfmt->css_pixelformat; -+ output_pin->send_irq = 1; -+ memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); -+ output_pin->s2m_pixel_soc_pixel_remapping = -+ S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; -+ output_pin->csi_be_soc_pixel_remapping = -+ CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; -+ -+ output_pin->snoopable = true; -+ output_pin->error_handling_enable = false; -+ output_pin->sensor_type = isys->sensor_type++; -+ if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) -+ isys->sensor_type = isys->pdata->ipdata->sensor_type_start; -+ -+ return 0; -+} -+ -+static int start_stream_firmware(struct ipu6_isys_video *av, -+ struct ipu6_isys_buffer_list *bl) -+{ -+ struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; -+ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; -+ struct ipu6_isys_stream *stream = av->stream; -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct isys_fw_msgs *msg = NULL; -+ struct ipu6_isys_queue *aq; -+ int ret, retout, tout; -+ u16 send_type; -+ -+ msg = ipu6_get_fw_msg_buf(stream); -+ if (!msg) -+ return -ENOMEM; -+ -+ stream_cfg = &msg->fw_msg.stream; -+ stream_cfg->src = stream->stream_source; -+ stream_cfg->vc = stream->vc; -+ stream_cfg->isl_use = 0; -+ stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; -+ -+ list_for_each_entry(aq, &stream->queues, node) { -+ struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); -+ -+ ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); -+ if (ret < 0) { -+ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); -+ return ret; -+ } -+ } -+ -+ ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); -+ -+ stream->nr_output_pins = stream_cfg->nof_output_pins; -+ -+ reinit_completion(&stream->stream_open_completion); -+ -+ ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, -+ stream_cfg, msg->dma_addr, -+ sizeof(*stream_cfg), -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); -+ if (ret < 0) { -+ dev_err(dev, "can't open stream (%d)\n", ret); -+ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); -+ return ret; -+ } -+ -+ get_stream_opened(av); -+ -+ tout = wait_for_completion_timeout(&stream->stream_open_completion, -+ IPU6_FW_CALL_TIMEOUT_JIFFIES); -+ -+ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); -+ -+ if (!tout) { -+ dev_err(dev, "stream open time out\n"); -+ ret = -ETIMEDOUT; -+ goto out_put_stream_opened; -+ } -+ if (stream->error) { -+ dev_err(dev, "stream open error: %d\n", stream->error); -+ ret = -EIO; -+ goto out_put_stream_opened; -+ } -+ dev_dbg(dev, "start stream: open complete\n"); -+ -+ if (bl) { -+ msg = ipu6_get_fw_msg_buf(stream); -+ if (!msg) { -+ ret = -ENOMEM; -+ goto out_put_stream_opened; -+ } -+ buf = &msg->fw_msg.frame; -+ ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); -+ ipu6_isys_buffer_list_queue(bl, -+ IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); -+ } -+ -+ reinit_completion(&stream->stream_start_completion); -+ -+ if (bl) { -+ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; -+ ipu6_fw_isys_dump_frame_buff_set(dev, buf, -+ stream_cfg->nof_output_pins); -+ ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, -+ buf, msg->dma_addr, -+ sizeof(*buf), send_type); -+ } else { -+ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; -+ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, -+ send_type); -+ } -+ -+ if (ret < 0) { -+ dev_err(dev, "can't start streaming (%d)\n", ret); -+ goto out_stream_close; -+ } -+ -+ tout = wait_for_completion_timeout(&stream->stream_start_completion, -+ IPU6_FW_CALL_TIMEOUT_JIFFIES); -+ if (!tout) { -+ dev_err(dev, "stream start time out\n"); -+ ret = -ETIMEDOUT; -+ goto out_stream_close; -+ } -+ if (stream->error) { -+ dev_err(dev, "stream start error: %d\n", stream->error); -+ ret = -EIO; -+ goto out_stream_close; -+ } -+ dev_dbg(dev, "start stream: complete\n"); -+ -+ return 0; -+ -+out_stream_close: -+ reinit_completion(&stream->stream_close_completion); -+ -+ retout = ipu6_fw_isys_simple_cmd(av->isys, -+ stream->stream_handle, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); -+ if (retout < 0) { -+ dev_dbg(dev, "can't close stream (%d)\n", retout); -+ goto out_put_stream_opened; -+ } -+ -+ tout = wait_for_completion_timeout(&stream->stream_close_completion, -+ IPU6_FW_CALL_TIMEOUT_JIFFIES); -+ if (!tout) -+ dev_err(dev, "stream close time out\n"); -+ else if (stream->error) -+ dev_err(dev, "stream close error: %d\n", stream->error); -+ else -+ dev_dbg(dev, "stream close complete\n"); -+ -+out_put_stream_opened: -+ put_stream_opened(av); -+ -+ return ret; -+} -+ -+static void stop_streaming_firmware(struct ipu6_isys_video *av) -+{ -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct ipu6_isys_stream *stream = av->stream; -+ int ret, tout; -+ -+ reinit_completion(&stream->stream_stop_completion); -+ -+ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); -+ -+ if (ret < 0) { -+ dev_err(dev, "can't stop stream (%d)\n", ret); -+ return; -+ } -+ -+ tout = wait_for_completion_timeout(&stream->stream_stop_completion, -+ IPU6_FW_CALL_TIMEOUT_JIFFIES); -+ if (!tout) -+ dev_warn(dev, "stream stop time out\n"); -+ else if (stream->error) -+ dev_warn(dev, "stream stop error: %d\n", stream->error); -+ else -+ dev_dbg(dev, "stop stream: complete\n"); -+} -+ -+static void close_streaming_firmware(struct ipu6_isys_video *av) -+{ -+ struct ipu6_isys_stream *stream = av->stream; -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ int ret, tout; -+ -+ reinit_completion(&stream->stream_close_completion); -+ -+ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, -+ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); -+ if (ret < 0) { -+ dev_err(dev, "can't close stream (%d)\n", ret); -+ return; -+ } -+ -+ tout = wait_for_completion_timeout(&stream->stream_close_completion, -+ IPU6_FW_CALL_TIMEOUT_JIFFIES); -+ if (!tout) -+ dev_warn(dev, "stream close time out\n"); -+ else if (stream->error) -+ dev_warn(dev, "stream close error: %d\n", stream->error); -+ else -+ dev_dbg(dev, "close stream: complete\n"); -+ -+ put_stream_opened(av); -+} -+ -+int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, -+ struct media_entity *source_entity, -+ int nr_queues) -+{ -+ struct ipu6_isys_stream *stream = av->stream; -+ struct ipu6_isys_csi2 *csi2; -+ -+ if (WARN_ON(stream->nr_streaming)) -+ return -EINVAL; -+ -+ stream->nr_queues = nr_queues; -+ atomic_set(&stream->sequence, 0); -+ -+ stream->seq_index = 0; -+ memset(stream->seq, 0, sizeof(stream->seq)); -+ -+ if (WARN_ON(!list_empty(&stream->queues))) -+ return -EINVAL; -+ -+ stream->stream_source = stream->asd->source; -+ csi2 = ipu6_isys_subdev_to_csi2(stream->asd); -+ csi2->receiver_errors = 0; -+ stream->source_entity = source_entity; -+ -+ dev_dbg(&av->isys->adev->auxdev.dev, -+ "prepare stream: external entity %s\n", -+ stream->source_entity->name); -+ -+ return 0; -+} -+ -+void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, -+ bool state) -+{ -+ struct ipu6_isys *isys = av->isys; -+ struct ipu6_isys_csi2 *csi2 = NULL; -+ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; -+ struct device *dev = &isys->adev->auxdev.dev; -+ struct v4l2_mbus_framefmt format; -+ struct v4l2_subdev *esd; -+ struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; -+ unsigned int bpp, lanes; -+ s64 link_freq = 0; -+ u64 pixel_rate = 0; -+ int ret; -+ -+ if (!state) -+ return; -+ -+ esd = media_entity_to_v4l2_subdev(av->stream->source_entity); -+ -+ av->watermark.width = av->mpix.width; -+ av->watermark.height = av->mpix.height; -+ av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; -+ av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; -+ -+ ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); -+ if (!ret && hb.value >= 0) -+ av->watermark.hblank = hb.value; -+ else -+ av->watermark.hblank = 0; -+ -+ csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); -+ link_freq = ipu6_isys_csi2_get_link_freq(csi2); -+ if (link_freq > 0) { -+ lanes = csi2->nlanes; -+ ret = ipu6_isys_get_stream_pad_fmt(&csi2->asd.sd, 0, -+ av->source_stream, &format); -+ if (!ret) { -+ bpp = ipu6_isys_mbus_code_to_bpp(format.code); -+ pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); -+ } -+ } -+ -+ av->watermark.pixel_rate = pixel_rate; -+ -+ if (!pixel_rate) { -+ mutex_lock(&iwake_watermark->mutex); -+ iwake_watermark->force_iwake_disable = true; -+ mutex_unlock(&iwake_watermark->mutex); -+ dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", -+ av->stream->source_entity->name); -+ } -+} -+ -+static void calculate_stream_datarate(struct ipu6_isys_video *av) -+{ -+ struct video_stream_watermark *watermark = &av->watermark; -+ u32 bpp = av->pfmt->bpp; -+ u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line; -+ u64 line_time_ns, stream_data_rate; -+ u16 shift, size; -+ -+ shift = watermark->sram_gran_shift; -+ size = watermark->sram_gran_size; -+ -+ pixels_per_line = watermark->width + watermark->hblank; -+ line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, -+ watermark->pixel_rate); -+ bytes_per_line = watermark->width * bpp / 8; -+ pages_per_line = DIV_ROUND_UP(bytes_per_line, size); -+ pb_bytes_per_line = pages_per_line << shift; -+ stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns); -+ -+ watermark->stream_data_rate = stream_data_rate; -+} -+ -+void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) -+{ -+ struct isys_iwake_watermark *iwake_watermark = -+ &av->isys->iwake_watermark; -+ -+ if (!av->watermark.pixel_rate) -+ return; -+ -+ if (state) { -+ calculate_stream_datarate(av); -+ mutex_lock(&iwake_watermark->mutex); -+ list_add(&av->watermark.stream_node, -+ &iwake_watermark->video_list); -+ mutex_unlock(&iwake_watermark->mutex); -+ } else { -+ av->watermark.stream_data_rate = 0; -+ mutex_lock(&iwake_watermark->mutex); -+ list_del(&av->watermark.stream_node); -+ mutex_unlock(&iwake_watermark->mutex); -+ } -+ -+ update_watermark_setting(av->isys); -+} -+ -+void ipu6_isys_put_stream(struct ipu6_isys_stream *stream) -+{ -+ struct device *dev = &stream->isys->adev->auxdev.dev; -+ unsigned int i; -+ unsigned long flags; -+ -+ if (!stream) { -+ dev_err(dev, "no available stream\n"); -+ return; -+ } -+ -+ spin_lock_irqsave(&stream->isys->streams_lock, flags); -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { -+ if (&stream->isys->streams[i] == stream) { -+ if (stream->isys->streams_ref_count[i] > 0) -+ stream->isys->streams_ref_count[i]--; -+ else -+ dev_warn(dev, "invalid stream %d\n", i); -+ -+ break; -+ } -+ } -+ spin_unlock_irqrestore(&stream->isys->streams_lock, flags); -+} -+ -+static struct ipu6_isys_stream * -+ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd) -+{ -+ struct ipu6_isys_stream *stream = NULL; -+ struct ipu6_isys *isys = av->isys; -+ unsigned long flags; -+ unsigned int i; -+ u8 vc = av->vc; -+ -+ if (!isys) -+ return NULL; -+ -+ spin_lock_irqsave(&isys->streams_lock, flags); -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { -+ if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && -+ isys->streams[i].asd == asd) { -+ isys->streams_ref_count[i]++; -+ stream = &isys->streams[i]; -+ break; -+ } -+ } -+ -+ if (!stream) { -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { -+ if (!isys->streams_ref_count[i]) { -+ isys->streams_ref_count[i]++; -+ stream = &isys->streams[i]; -+ stream->vc = vc; -+ stream->asd = asd; -+ break; -+ } -+ } -+ } -+ spin_unlock_irqrestore(&isys->streams_lock, flags); -+ -+ return stream; -+} -+ -+struct ipu6_isys_stream * -+ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle) -+{ -+ unsigned long flags; -+ struct ipu6_isys_stream *stream = NULL; -+ -+ if (!isys) -+ return NULL; -+ -+ if (stream_handle >= IPU6_ISYS_MAX_STREAMS) { -+ dev_err(&isys->adev->auxdev.dev, -+ "stream_handle %d is invalid\n", stream_handle); -+ return NULL; -+ } -+ -+ spin_lock_irqsave(&isys->streams_lock, flags); -+ if (isys->streams_ref_count[stream_handle] > 0) { -+ isys->streams_ref_count[stream_handle]++; -+ stream = &isys->streams[stream_handle]; -+ } -+ spin_unlock_irqrestore(&isys->streams_lock, flags); -+ -+ return stream; -+} -+ -+struct ipu6_isys_stream * -+ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc) -+{ -+ struct ipu6_isys_stream *stream = NULL; -+ unsigned long flags; -+ unsigned int i; -+ -+ if (!isys) -+ return NULL; -+ -+ if (source < 0) { -+ dev_err(&stream->isys->adev->auxdev.dev, -+ "query stream with invalid port number\n"); -+ return NULL; -+ } -+ -+ spin_lock_irqsave(&isys->streams_lock, flags); -+ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { -+ if (!isys->streams_ref_count[i]) -+ continue; -+ -+ if (isys->streams[i].stream_source == source && -+ isys->streams[i].vc == vc) { -+ stream = &isys->streams[i]; -+ isys->streams_ref_count[i]++; -+ break; -+ } -+ } -+ spin_unlock_irqrestore(&isys->streams_lock, flags); -+ -+ return stream; -+} -+ -+static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *av) -+{ -+ struct media_pipeline *pipeline = -+ media_entity_pipeline(&av->vdev.entity); -+ struct media_entity *entity; -+ unsigned int i; -+ u64 stream_mask = 0; -+ -+ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) { -+ entity = &av->isys->av[i].vdev.entity; -+ if (pipeline == media_entity_pipeline(entity)) -+ stream_mask |= BIT_ULL(av->isys->av[i].source_stream); -+ } -+ -+ return stream_mask; -+} -+ -+int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, -+ struct ipu6_isys_buffer_list *bl) -+{ -+ struct v4l2_subdev_krouting *routing; -+ struct ipu6_isys_stream *stream = av->stream; -+ struct v4l2_subdev_state *subdev_state; -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct v4l2_subdev *sd = NULL; -+ struct v4l2_subdev *ssd = NULL; -+ struct media_pad *r_pad; -+ struct media_pad *s_pad = NULL; -+ u32 sink_pad, sink_stream; -+ u64 r_stream; -+ u64 stream_mask = 0; -+ int ret = 0; -+ -+ dev_dbg(dev, "set stream: %d\n", state); -+ -+ if (WARN(!stream->source_entity, "No source entity for stream\n")) -+ return -ENODEV; -+ -+ ssd = media_entity_to_v4l2_subdev(stream->source_entity); -+ sd = &stream->asd->sd; -+ r_pad = media_pad_remote_pad_first(&av->pad); -+ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); -+ -+ subdev_state = v4l2_subdev_lock_and_get_active_state(sd); -+ routing = &subdev_state->routing; -+ ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index, -+ r_stream, &sink_pad, -+ &sink_stream); -+ v4l2_subdev_unlock_state(subdev_state); -+ if (ret) -+ return ret; -+ -+ s_pad = media_pad_remote_pad_first(&stream->asd->pad[sink_pad]); -+ -+ stream_mask = get_stream_mask_by_pipeline(av); -+ if (!state) { -+ stop_streaming_firmware(av); -+ -+ /* stop external sub-device now. */ -+ dev_dbg(dev, "disable streams 0x%llx of %s\n", stream_mask, -+ ssd->name); -+ ret = v4l2_subdev_disable_streams(ssd, s_pad->index, -+ stream_mask); -+ if (ret) { -+ dev_err(dev, "disable streams of %s failed with %d\n", -+ ssd->name, ret); -+ return ret; -+ } -+ -+ /* stop sub-device which connects with video */ -+ dev_dbg(dev, "stream off entity %s pad:%d\n", sd->name, -+ r_pad->index); -+ ret = v4l2_subdev_call(sd, video, s_stream, state); -+ if (ret) { -+ dev_err(dev, "stream off %s failed with %d\n", sd->name, -+ ret); -+ return ret; -+ } -+ close_streaming_firmware(av); -+ } else { -+ ret = start_stream_firmware(av, bl); -+ if (ret) { -+ dev_err(dev, "start stream of firmware failed\n"); -+ goto out_clear_stream_watermark; -+ } -+ -+ /* start sub-device which connects with video */ -+ dev_dbg(dev, "stream on %s pad %d\n", sd->name, r_pad->index); -+ ret = v4l2_subdev_call(sd, video, s_stream, state); -+ if (ret) { -+ dev_err(dev, "stream on %s failed with %d\n", sd->name, -+ ret); -+ goto out_media_entity_stop_streaming_firmware; -+ } -+ -+ /* start external sub-device now. */ -+ dev_dbg(dev, "enable streams 0x%llx of %s\n", stream_mask, -+ ssd->name); -+ ret = v4l2_subdev_enable_streams(ssd, s_pad->index, -+ stream_mask); -+ if (ret) { -+ dev_err(dev, -+ "enable streams 0x%llx of %s failed with %d\n", -+ stream_mask, stream->source_entity->name, ret); -+ goto out_media_entity_stop_streaming; -+ } -+ } -+ -+ av->streaming = state; -+ -+ return 0; -+ -+out_media_entity_stop_streaming: -+ v4l2_subdev_disable_streams(sd, r_pad->index, BIT(r_stream)); -+ -+out_media_entity_stop_streaming_firmware: -+ stop_streaming_firmware(av); -+ -+out_clear_stream_watermark: -+ ipu6_isys_update_stream_watermark(av, 0); -+ -+ return ret; -+} -+ -+static const struct v4l2_ioctl_ops ioctl_ops_mplane = { -+ .vidioc_querycap = ipu6_isys_vidioc_querycap, -+ .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, -+ .vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes, -+ .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, -+ .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, -+ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, -+ .vidioc_reqbufs = vb2_ioctl_reqbufs, -+ .vidioc_create_bufs = vb2_ioctl_create_bufs, -+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, -+ .vidioc_querybuf = vb2_ioctl_querybuf, -+ .vidioc_qbuf = vb2_ioctl_qbuf, -+ .vidioc_dqbuf = vb2_ioctl_dqbuf, -+ .vidioc_streamon = vb2_ioctl_streamon, -+ .vidioc_streamoff = vb2_ioctl_streamoff, -+ .vidioc_expbuf = vb2_ioctl_expbuf, -+}; -+ -+static const struct media_entity_operations entity_ops = { -+ .link_validate = link_validate, -+}; -+ -+static const struct v4l2_file_operations isys_fops = { -+ .owner = THIS_MODULE, -+ .poll = vb2_fop_poll, -+ .unlocked_ioctl = video_ioctl2, -+ .mmap = vb2_fop_mmap, -+ .open = video_open, -+ .release = video_release, -+}; -+ -+int ipu6_isys_fw_open(struct ipu6_isys *isys) -+{ -+ struct ipu6_bus_device *adev = isys->adev; -+ const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; -+ int ret; -+ -+ ret = pm_runtime_resume_and_get(&adev->auxdev.dev); -+ if (ret < 0) -+ return ret; -+ -+ mutex_lock(&isys->mutex); -+ -+ if (isys->ref_count++) -+ goto unlock; -+ -+ ipu6_configure_spc(adev->isp, &ipdata->hw_variant, -+ IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, -+ adev->pkg_dir, adev->pkg_dir_dma_addr); -+ -+ /* -+ * Buffers could have been left to wrong queue at last closure. -+ * Move them now back to empty buffer queue. -+ */ -+ ipu6_cleanup_fw_msg_bufs(isys); -+ -+ if (isys->fwcom) { -+ /* -+ * Something went wrong in previous shutdown. As we are now -+ * restarting isys we can safely delete old context. -+ */ -+ dev_warn(&adev->auxdev.dev, "clearing old context\n"); -+ ipu6_fw_isys_cleanup(isys); -+ } -+ -+ ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); -+ if (ret < 0) -+ goto out; -+ -+unlock: -+ mutex_unlock(&isys->mutex); -+ -+ return 0; -+ -+out: -+ isys->ref_count--; -+ mutex_unlock(&isys->mutex); -+ pm_runtime_put(&adev->auxdev.dev); -+ -+ return ret; -+} -+ -+void ipu6_isys_fw_close(struct ipu6_isys *isys) -+{ -+ mutex_lock(&isys->mutex); -+ -+ isys->ref_count--; -+ if (!isys->ref_count) { -+ ipu6_fw_isys_close(isys); -+ if (isys->fwcom) { -+ isys->need_reset = true; -+ dev_warn(&isys->adev->auxdev.dev, -+ "failed to close fw isys\n"); -+ } -+ } -+ -+ mutex_unlock(&isys->mutex); -+ -+ if (isys->need_reset) -+ pm_runtime_put_sync(&isys->adev->auxdev.dev); -+ else -+ pm_runtime_put(&isys->adev->auxdev.dev); -+} -+ -+int ipu6_isys_setup_video(struct ipu6_isys_video *av, -+ struct media_entity **source_entity, int *nr_queues) -+{ -+ struct device *dev = &av->isys->adev->auxdev.dev; -+ struct v4l2_mbus_frame_desc_entry entry; -+ struct v4l2_subdev_route *route = NULL; -+ struct v4l2_subdev_route *r; -+ struct v4l2_subdev_state *state; -+ struct ipu6_isys_subdev *asd; -+ struct v4l2_subdev *remote_sd; -+ struct media_pipeline *pipeline; -+ struct media_pad *source_pad, *remote_pad; -+ int ret = -EINVAL; -+ -+ remote_pad = media_pad_remote_pad_first(&av->pad); -+ if (!remote_pad) { -+ dev_dbg(dev, "failed to get remote pad\n"); -+ return -ENODEV; -+ } -+ -+ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); -+ asd = to_ipu6_isys_subdev(remote_sd); -+ source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]); -+ if (!source_pad) { -+ dev_dbg(dev, "No external source entity\n"); -+ return -ENODEV; -+ } -+ -+ *source_entity = source_pad->entity; -+ -+ /* Find the root */ -+ state = v4l2_subdev_lock_and_get_active_state(remote_sd); -+ for_each_active_route(&state->routing, r) { -+ if (r->source_pad != remote_pad->index) -+ continue; -+ -+ route = r; -+ break; -+ } -+ -+ if (!route) { -+ v4l2_subdev_unlock_state(state); -+ dev_dbg(dev, "Failed to find route\n"); -+ return -ENODEV; -+ } -+ v4l2_subdev_unlock_state(state); -+ av->source_stream = route->sink_stream; -+ -+ ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, -+ to_ipu6_isys_csi2(asd), -+ *source_entity, &entry, -+ nr_queues); -+ if (ret == -ENOIOCTLCMD) { -+ av->vc = 0; -+ av->dt = ipu6_isys_mbus_code_to_mipi(av->pfmt->code); -+ *nr_queues = 1; -+ } else if (!ret) { -+ dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", -+ entry.stream, entry.length, entry.bus.csi2.vc, -+ entry.bus.csi2.dt); -+ -+ av->vc = entry.bus.csi2.vc; -+ av->dt = entry.bus.csi2.dt; -+ } else { -+ dev_err(dev, "failed to get remote frame desc\n"); -+ return ret; -+ } -+ -+ pipeline = media_entity_pipeline(&av->vdev.entity); -+ if (!pipeline) -+ ret = video_device_pipeline_alloc_start(&av->vdev); -+ else -+ ret = video_device_pipeline_start(&av->vdev, pipeline); -+ if (ret < 0) { -+ dev_dbg(dev, "media pipeline start failed\n"); -+ return ret; -+ } -+ -+ av->stream = ipu6_isys_get_stream(av, asd); -+ if (!av->stream) { -+ video_device_pipeline_stop(&av->vdev); -+ dev_err(dev, "no available stream for firmware\n"); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+/* -+ * Do everything that's needed to initialise things related to video -+ * buffer queue, video node, and the related media entity. The caller -+ * is expected to assign isys field and set the name of the video -+ * device. -+ */ -+int ipu6_isys_video_init(struct ipu6_isys_video *av) -+{ -+ struct v4l2_format format = { -+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, -+ .fmt.pix_mp = { -+ .width = 1920, -+ .height = 1080, -+ }, -+ }; -+ int ret; -+ -+ mutex_init(&av->mutex); -+ av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | -+ V4L2_CAP_VIDEO_CAPTURE_MPLANE; -+ av->vdev.vfl_dir = VFL_DIR_RX; -+ -+ ret = ipu6_isys_queue_init(&av->aq); -+ if (ret) -+ goto out_free_watermark; -+ -+ av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; -+ ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); -+ if (ret) -+ goto out_vb2_queue_release; -+ -+ av->vdev.entity.ops = &entity_ops; -+ av->vdev.release = video_device_release_empty; -+ av->vdev.fops = &isys_fops; -+ av->vdev.v4l2_dev = &av->isys->v4l2_dev; -+ if (!av->vdev.ioctl_ops) -+ av->vdev.ioctl_ops = &ioctl_ops_mplane; -+ av->vdev.queue = &av->aq.vbq; -+ av->vdev.lock = &av->mutex; -+ -+ ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp); -+ av->mpix = format.fmt.pix_mp; -+ -+ set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); -+ video_set_drvdata(&av->vdev, av); -+ -+ ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1); -+ if (ret) -+ goto out_media_entity_cleanup; -+ -+ return ret; -+ -+out_media_entity_cleanup: -+ vb2_video_unregister_device(&av->vdev); -+ media_entity_cleanup(&av->vdev.entity); -+ -+out_vb2_queue_release: -+ vb2_queue_release(&av->aq.vbq); -+ -+out_free_watermark: -+ mutex_destroy(&av->mutex); -+ -+ return ret; -+} -+ -+void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) -+{ -+ vb2_video_unregister_device(&av->vdev); -+ media_entity_cleanup(&av->vdev.entity); -+ mutex_destroy(&av->mutex); -+} -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h -new file mode 100644 -index 000000000000..21cd33c7e277 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h -@@ -0,0 +1,136 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* Copyright (C) 2013 - 2023 Intel Corporation */ -+ -+#ifndef IPU6_ISYS_VIDEO_H -+#define IPU6_ISYS_VIDEO_H -+ -+#include <linux/atomic.h> -+#include <linux/completion.h> -+#include <linux/container_of.h> -+#include <linux/list.h> -+#include <linux/mutex.h> -+ -+#include <media/media-entity.h> -+#include <media/v4l2-dev.h> -+ -+#include "ipu6-isys-queue.h" -+ -+#define IPU6_ISYS_OUTPUT_PINS 11 -+#define IPU6_ISYS_MAX_PARALLEL_SOF 2 -+#define NR_OF_VIDEO_DEVICE 31 -+ -+struct file; -+struct ipu6_isys; -+struct ipu6_isys_subdev; -+ -+struct ipu6_isys_pixelformat { -+ u32 pixelformat; -+ u32 bpp; -+ u32 bpp_packed; -+ u32 code; -+ u32 css_pixelformat; -+}; -+ -+struct sequence_info { -+ unsigned int sequence; -+ u64 timestamp; -+}; -+ -+struct output_pin_data { -+ void (*pin_ready)(struct ipu6_isys_stream *stream, -+ struct ipu6_fw_isys_resp_info_abi *info); -+ struct ipu6_isys_queue *aq; -+}; -+ -+/* -+ * Align with firmware stream. Each stream represents a CSI virtual channel. -+ * May map to multiple video devices -+ */ -+struct ipu6_isys_stream { -+ struct mutex mutex; -+ struct media_entity *source_entity; -+ atomic_t sequence; -+ unsigned int seq_index; -+ struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; -+ int stream_source; -+ int stream_handle; -+ unsigned int nr_output_pins; -+ struct ipu6_isys_subdev *asd; -+ -+ int nr_queues; /* Number of capture queues */ -+ int nr_streaming; -+ int streaming; /* Has streaming been really started? */ -+ struct list_head queues; -+ struct completion stream_open_completion; -+ struct completion stream_close_completion; -+ struct completion stream_start_completion; -+ struct completion stream_stop_completion; -+ struct ipu6_isys *isys; -+ -+ struct output_pin_data output_pins[IPU6_ISYS_OUTPUT_PINS]; -+ int error; -+ u8 vc; -+}; -+ -+struct video_stream_watermark { -+ u32 width; -+ u32 height; -+ u32 hblank; -+ u32 frame_rate; -+ u64 pixel_rate; -+ u64 stream_data_rate; -+ u16 sram_gran_shift; -+ u16 sram_gran_size; -+ struct list_head stream_node; -+}; -+ -+struct ipu6_isys_video { -+ struct ipu6_isys_queue aq; -+ /* Serialise access to other fields in the struct. */ -+ struct mutex mutex; -+ struct media_pad pad; -+ struct video_device vdev; -+ struct v4l2_pix_format_mplane mpix; -+ const struct ipu6_isys_pixelformat *pfmt; -+ struct ipu6_isys *isys; -+ struct ipu6_isys_stream *stream; -+ unsigned int streaming; -+ struct video_stream_watermark watermark; -+ u32 source_stream; -+ u8 vc; -+ u8 dt; -+}; -+ -+#define ipu6_isys_queue_to_video(__aq) \ -+ container_of(__aq, struct ipu6_isys_video, aq) -+ -+extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[]; -+extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[]; -+ -+int ipu6_isys_vidioc_querycap(struct file *file, void *fh, -+ struct v4l2_capability *cap); -+ -+int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, -+ struct v4l2_fmtdesc *f); -+int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, -+ struct media_entity *source_entity, -+ int nr_queues); -+int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, -+ struct ipu6_isys_buffer_list *bl); -+int ipu6_isys_fw_open(struct ipu6_isys *isys); -+void ipu6_isys_fw_close(struct ipu6_isys *isys); -+int ipu6_isys_setup_video(struct ipu6_isys_video *av, -+ struct media_entity **source_entity, int *nr_queues); -+int ipu6_isys_video_init(struct ipu6_isys_video *av); -+void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); -+void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); -+struct ipu6_isys_stream * -+ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle); -+struct ipu6_isys_stream * -+ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); -+ -+void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, -+ bool state); -+void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); -+ -+#endif /* IPU6_ISYS_VIDEO_H */ --- -2.43.2 - - -From cc79447bab87ce8c498b0e7a5f849c7d4f6262c0 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:26 +0800 -Subject: [PATCH 19/33] media: add Kconfig and Makefile for IPU6 - -Add IPU6 support in Kconfig and Makefile, with this patch you can -build the Intel IPU6 and input system modules by select the -CONFIG_VIDEO_INTEL_IPU6 in config. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> -Signed-off-by: Andreas Helbech Kleist <andreaskleist@gmail.com> ---- - drivers/media/pci/intel/Kconfig | 1 + - drivers/media/pci/intel/Makefile | 1 + - drivers/media/pci/intel/ipu6/Kconfig | 17 +++++++++++++++++ - drivers/media/pci/intel/ipu6/Makefile | 23 +++++++++++++++++++++++ - 4 files changed, 42 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu6/Kconfig - create mode 100644 drivers/media/pci/intel/ipu6/Makefile - -diff --git a/drivers/media/pci/intel/Kconfig b/drivers/media/pci/intel/Kconfig -index ee4684159d3d..04cb3d253486 100644 ---- a/drivers/media/pci/intel/Kconfig -+++ b/drivers/media/pci/intel/Kconfig -@@ -1,6 +1,7 @@ - # SPDX-License-Identifier: GPL-2.0-only - - source "drivers/media/pci/intel/ipu3/Kconfig" -+source "drivers/media/pci/intel/ipu6/Kconfig" - source "drivers/media/pci/intel/ivsc/Kconfig" - - config IPU_BRIDGE -diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile -index f199a97e1d78..3a2cc6567159 100644 ---- a/drivers/media/pci/intel/Makefile -+++ b/drivers/media/pci/intel/Makefile -@@ -5,3 +5,4 @@ - obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o - obj-y += ipu3/ - obj-y += ivsc/ -+obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ -diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig -new file mode 100644 -index 000000000000..5cb4f3c2d59f ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/Kconfig -@@ -0,0 +1,17 @@ -+config VIDEO_INTEL_IPU6 -+ tristate "Intel IPU6 driver" -+ depends on ACPI || COMPILE_TEST -+ depends on MEDIA_SUPPORT -+ depends on MEDIA_PCI_SUPPORT -+ depends on X86 && X86_64 -+ select IOMMU_IOVA -+ select VIDEO_V4L2_SUBDEV_API -+ select VIDEOBUF2_DMA_CONTIG -+ select V4L2_FWNODE -+ select IPU_BRIDGE -+ help -+ This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs -+ and used for capturing images and video from camera sensors. -+ -+ To compile this driver, say Y here! It contains 2 modules - -+ intel_ipu6 and intel_ipu6_isys. -diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile -new file mode 100644 -index 000000000000..a821b0a1567f ---- /dev/null -+++ b/drivers/media/pci/intel/ipu6/Makefile -@@ -0,0 +1,23 @@ -+# SPDX-License-Identifier: GPL-2.0-only -+ -+intel-ipu6-y := ipu6.o \ -+ ipu6-bus.o \ -+ ipu6-dma.o \ -+ ipu6-mmu.o \ -+ ipu6-buttress.o \ -+ ipu6-cpd.o \ -+ ipu6-fw-com.o -+ -+obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o -+ -+intel-ipu6-isys-y := ipu6-isys.o \ -+ ipu6-isys-csi2.o \ -+ ipu6-fw-isys.o \ -+ ipu6-isys-video.o \ -+ ipu6-isys-queue.o \ -+ ipu6-isys-subdev.o \ -+ ipu6-isys-mcd-phy.o \ -+ ipu6-isys-jsl-phy.o \ -+ ipu6-isys-dwc-phy.o -+ -+obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o --- -2.43.2 - - -From edc6bed6991727e64f1eb60c0392403c39b96ba4 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:27 +0800 -Subject: [PATCH 20/33] MAINTAINERS: add maintainers for Intel IPU6 input - system driver - -Update MAINTAINERS file for Intel IPU6 input system driver. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - MAINTAINERS | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/MAINTAINERS b/MAINTAINERS -index 1aabf1c15bb3..5346d472cb0f 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -10899,6 +10899,16 @@ F: Documentation/admin-guide/media/ipu3_rcb.svg - F: Documentation/userspace-api/media/v4l/metafmt-intel-ipu3.rst - F: drivers/staging/media/ipu3/ - -+INTEL IPU6 INPUT SYSTEM DRIVER -+M: Sakari Ailus <sakari.ailus@linux.intel.com> -+M: Bingbu Cao <bingbu.cao@intel.com> -+R: Tianshu Qiu <tian.shu.qiu@intel.com> -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: Documentation/admin-guide/media/ipu6-isys.rst -+F: drivers/media/pci/intel/ipu6/ -+ - INTEL ISHTP ECLITE DRIVER - M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com> - L: platform-driver-x86@vger.kernel.org --- -2.43.2 - - -From a12041e5f7fb32b93669f19b579bc1940a026bbe Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:28 +0800 -Subject: [PATCH 21/33] Documentation: add Intel IPU6 ISYS driver admin-guide - doc - -This document mainly describe the functionality of IPU6 and -IPU6 isys driver, and gives an example that how user can do -imaging capture with tools. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - Documentation/admin-guide/media/ipu6-isys.rst | 158 ++++++++++++++++ - .../admin-guide/media/ipu6_isys_graph.svg | 174 ++++++++++++++++++ - .../admin-guide/media/v4l-drivers.rst | 1 + - 3 files changed, 333 insertions(+) - create mode 100644 Documentation/admin-guide/media/ipu6-isys.rst - create mode 100644 Documentation/admin-guide/media/ipu6_isys_graph.svg - -diff --git a/Documentation/admin-guide/media/ipu6-isys.rst b/Documentation/admin-guide/media/ipu6-isys.rst -new file mode 100644 -index 000000000000..5e78ab88c649 ---- /dev/null -+++ b/Documentation/admin-guide/media/ipu6-isys.rst -@@ -0,0 +1,158 @@ -+.. SPDX-License-Identifier: GPL-2.0 -+ -+.. include:: <isonum.txt> -+ -+======================================================== -+Intel Image Processing Unit 6 (IPU6) Input System driver -+======================================================== -+ -+Copyright |copy| 2023 Intel Corporation -+ -+Introduction -+============ -+ -+This file documents the Intel IPU6 (6th generation Image Processing Unit) -+Input System (MIPI CSI2 receiver) drivers located under -+drivers/media/pci/intel/ipu6. -+ -+The Intel IPU6 can be found in certain Intel Chipsets but not in all SKUs: -+ -+* TigerLake -+* JasperLake -+* AlderLake -+* RaptorLake -+* MeteorLake -+ -+Intel IPU6 is made up of two components - Input System (ISYS) and Processing -+System (PSYS). -+ -+The Input System mainly works as MIPI CSI2 receiver which receives and -+processes the imaging data from the sensors and outputs the frames to memory. -+ -+There are 2 driver modules - intel_ipu6 and intel_ipu6_isys. intel_ipu6 is an -+IPU6 common driver which does PCI configuration, firmware loading and parsing, -+firmware authentication, DMA mapping and IPU-MMU (internal Memory mapping Unit) -+configuration. intel_ipu6_isys implements V4L2, Media Controller and V4L2 -+sub-device interfaces. The IPU6 ISYS driver supports camera sensors connected -+to the IPU6 ISYS through V4L2 sub-device sensor drivers. -+ -+.. Note:: See Documentation/driver-api/media/drivers/ipu6.rst for more -+ information about the IPU6 hardware. -+ -+ -+Input system driver -+=================== -+ -+The input System driver mainly configures CSI2 DPHY, constructs the firmware -+stream configuration, sends commands to firmware, gets response from hardware -+and firmware and then returns buffers to user. -+The ISYS is represented as several V4L2 sub-devices - 'Intel IPU6 CSI2 $port', -+which provide V4L2 subdev interfaces to the user space, there are also several -+video nodes for each CSI-2 stream capture - 'Intel IPU6 ISYS capture $num' which -+provide interface to user to set formats, queue buffers and streaming. -+ -+.. kernel-figure:: ipu6_isys_graph.svg -+ :alt: ipu6 isys media graph with multiple streams support -+ -+ ipu6 isys media graph with multiple streams support -+ -+Capturing frames by IPU6 ISYS -+----------------------------- -+ -+IPU6 ISYS is used to capture frames from the camera sensors connected to the -+CSI2 ports. The supported input formats of ISYS are listed in table below: -+ -+.. tabularcolumns:: |p{0.8cm}|p{4.0cm}|p{4.0cm}| -+ -+.. flat-table:: -+ :header-rows: 1 -+ -+ * - IPU6 ISYS supported input formats -+ -+ * - RGB565, RGB888 -+ -+ * - UYVY8, YUYV8 -+ -+ * - RAW8, RAW10, RAW12 -+ -+.. _ipu6_isys_capture_examples: -+ -+Examples -+~~~~~~~~ -+Here is an example of IPU6 ISYS raw capture on Dell XPS 9315 laptop. On this -+machine, ov01a10 sensor is connected to IPU ISYS CSI2 port 2, which can -+generate images at sBGGR10 with resolution 1280x800. -+ -+Using the media controller APIs, we can configure ov01a10 sensor by -+media-ctl [#f1]_ and yavta [#f2]_ to transmit frames to IPU6 ISYS. -+ -+.. code-block:: none -+ -+ # Example 1 capture frame from ov01a10 camera sensor -+ # This example assumes /dev/media0 as the IPU ISYS media device -+ export MDEV=/dev/media0 -+ -+ # Establish the link for the media devices using media-ctl -+ media-ctl -d $MDEV -l "\"ov01a10 3-0036\":0 -> \"Intel IPU6 CSI2 2\":0[1]" -+ -+ # Set the format for the media devices -+ media-ctl -d $MDEV -V "ov01a10:0 [fmt:SBGGR10/1280x800]" -+ media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:0 [fmt:SBGGR10/1280x800]" -+ media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:1 [fmt:SBGGR10/1280x800]" -+ -+Once the media pipeline is configured, desired sensor specific settings -+(such as exposure and gain settings) can be set, using the yavta tool. -+ -+e.g -+ -+.. code-block:: none -+ -+ # and that ov01a10 sensor is connected to i2c bus 3 with address 0x36 -+ export SDEV=$(media-ctl -d $MDEV -e "ov01a10 3-0036") -+ -+ yavta -w 0x009e0903 400 $SDEV -+ yavta -w 0x009e0913 1000 $SDEV -+ yavta -w 0x009e0911 2000 $SDEV -+ -+Once the desired sensor settings are set, frame captures can be done as below. -+ -+e.g -+ -+.. code-block:: none -+ -+ yavta --data-prefix -u -c10 -n5 -I -s 1280x800 --file=/tmp/frame-#.bin \ -+ -f SBGGR10 $(media-ctl -d $MDEV -e "Intel IPU6 ISYS Capture 0") -+ -+With the above command, 10 frames are captured at 1280x800 resolution with -+sBGGR10 format. The captured frames are available as /tmp/frame-#.bin files. -+ -+Here is another example of IPU6 ISYS RAW and metadata capture from camera -+sensor ov2740 on Lenovo X1 Yoga laptop. -+ -+.. code-block:: none -+ -+ media-ctl -l "\"ov2740 14-0036\":0 -> \"Intel IPU6 CSI2 1\":0[1]" -+ media-ctl -l "\"Intel IPU6 CSI2 1\":1 -> \"Intel IPU6 ISYS Capture 0\":0[5]" -+ media-ctl -l "\"Intel IPU6 CSI2 1\":2 -> \"Intel IPU6 ISYS Capture 1\":0[5]" -+ -+ # set routing -+ media-ctl -v -R "\"Intel IPU6 CSI2 1\" [0/0->1/0[1],0/1->2/1[1]]" -+ -+ media-ctl -v "\"Intel IPU6 CSI2 1\":0/0 [fmt:SGRBG10/1932x1092]" -+ media-ctl -v "\"Intel IPU6 CSI2 1\":0/1 [fmt:GENERIC_8/97x1]" -+ media-ctl -v "\"Intel IPU6 CSI2 1\":1/0 [fmt:SGRBG10/1932x1092]" -+ media-ctl -v "\"Intel IPU6 CSI2 1\":2/1 [fmt:GENERIC_8/97x1]" -+ -+ CAPTURE_DEV=$(media-ctl -e "Intel IPU6 ISYS Capture 0") -+ ./yavta --data-prefix -c100 -n5 -I -s1932x1092 --file=/tmp/frame-#.bin \ -+ -f SGRBG10 ${CAPTURE_DEV} -+ -+ CAPTURE_META=$(media-ctl -e "Intel IPU6 ISYS Capture 1") -+ ./yavta --data-prefix -c100 -n5 -I -s97x1 -B meta-capture \ -+ --file=/tmp/meta-#.bin -f GENERIC_8 ${CAPTURE_META} -+ -+References -+========== -+ -+.. [#f1] https://git.ideasonboard.org/?p=media-ctl.git;a=summary -+.. [#f2] https://git.ideasonboard.org/yavta.git -diff --git a/Documentation/admin-guide/media/ipu6_isys_graph.svg b/Documentation/admin-guide/media/ipu6_isys_graph.svg -new file mode 100644 -index 000000000000..707747c75280 ---- /dev/null -+++ b/Documentation/admin-guide/media/ipu6_isys_graph.svg -@@ -0,0 +1,174 @@ -+<?xml version="1.0" encoding="UTF-8" standalone="no"?> -+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" -+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -+<!-- Generated by graphviz version 2.38.0 (20140413.2041) -+ --> -+<!-- Title: board Pages: 1 --> -+<svg width="559pt" height="810pt" -+ viewBox="0.00 0.00 559.00 809.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 805.5)"> -+<title>board</title> -+<polygon fill="white" stroke="none" points="-4,4 -4,-805.5 555,-805.5 555,4 -4,4"/> -+<!-- n00000001 --> -+<g id="node1" class="node"><title>n00000001</title> -+<polygon fill="#66cd00" stroke="black" points="551,-192.5 387,-192.5 387,-154.5 551,-154.5 551,-192.5"/> -+<text text-anchor="middle" x="469" y="-177.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 0</text> -+<text text-anchor="middle" x="469" y="-162.3" font-family="Times,serif" font-size="14.00">/dev/video0</text> -+</g> -+<!-- n00000002 --> -+<g id="node2" class="node"><title>n00000002</title> -+<polygon fill="#66cd00" stroke="black" points="551,-395.5 387,-395.5 387,-357.5 551,-357.5 551,-395.5"/> -+<text text-anchor="middle" x="469" y="-380.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 1</text> -+<text text-anchor="middle" x="469" y="-365.3" font-family="Times,serif" font-size="14.00">/dev/video1</text> -+</g> -+<!-- n00000003 --> -+<g id="node3" class="node"><title>n00000003</title> -+<polygon fill="#66cd00" stroke="black" points="551,-598.5 387,-598.5 387,-560.5 551,-560.5 551,-598.5"/> -+<text text-anchor="middle" x="469" y="-583.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 2</text> -+<text text-anchor="middle" x="469" y="-568.3" font-family="Times,serif" font-size="14.00">/dev/video2</text> -+</g> -+<!-- n00000004 --> -+<g id="node4" class="node"><title>n00000004</title> -+<polygon fill="#66cd00" stroke="black" points="551,-801.5 387,-801.5 387,-763.5 551,-763.5 551,-801.5"/> -+<text text-anchor="middle" x="469" y="-786.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 3</text> -+<text text-anchor="middle" x="469" y="-771.3" font-family="Times,serif" font-size="14.00">/dev/video3</text> -+</g> -+<!-- n0000007d --> -+<g id="node5" class="node"><title>n0000007d</title> -+<path fill="#ffb90f" stroke="black" d="M201,-0.5C201,-0.5 339,-0.5 339,-0.5 345,-0.5 351,-6.5 351,-12.5 351,-12.5 351,-172.5 351,-172.5 351,-178.5 345,-184.5 339,-184.5 339,-184.5 201,-184.5 201,-184.5 195,-184.5 189,-178.5 189,-172.5 189,-172.5 189,-12.5 189,-12.5 189,-6.5 195,-0.5 201,-0.5"/> -+<text text-anchor="middle" x="200.5" y="-88.8" font-family="Times,serif" font-size="14.00">0</text> -+<polyline fill="none" stroke="black" points="212,-0.5 212,-184.5 "/> -+<text text-anchor="middle" x="270" y="-96.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 0</text> -+<text text-anchor="middle" x="270" y="-81.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev0</text> -+<polyline fill="none" stroke="black" points="328,-0.5 328,-184.5 "/> -+<text text-anchor="middle" x="339.5" y="-169.3" font-family="Times,serif" font-size="14.00">1</text> -+<polyline fill="none" stroke="black" points="328,-161.5 351,-161.5 "/> -+<text text-anchor="middle" x="339.5" y="-146.3" font-family="Times,serif" font-size="14.00">2</text> -+<polyline fill="none" stroke="black" points="328,-138.5 351,-138.5 "/> -+<text text-anchor="middle" x="339.5" y="-123.3" font-family="Times,serif" font-size="14.00">3</text> -+<polyline fill="none" stroke="black" points="328,-115.5 351,-115.5 "/> -+<text text-anchor="middle" x="339.5" y="-100.3" font-family="Times,serif" font-size="14.00">4</text> -+<polyline fill="none" stroke="black" points="328,-92.5 351,-92.5 "/> -+<text text-anchor="middle" x="339.5" y="-77.3" font-family="Times,serif" font-size="14.00">5</text> -+<polyline fill="none" stroke="black" points="328,-69.5 351,-69.5 "/> -+<text text-anchor="middle" x="339.5" y="-54.3" font-family="Times,serif" font-size="14.00">6</text> -+<polyline fill="none" stroke="black" points="328,-46.5 351,-46.5 "/> -+<text text-anchor="middle" x="339.5" y="-31.3" font-family="Times,serif" font-size="14.00">7</text> -+<polyline fill="none" stroke="black" points="328,-23.5 351,-23.5 "/> -+<text text-anchor="middle" x="339.5" y="-8.3" font-family="Times,serif" font-size="14.00">8</text> -+</g> -+<!-- n0000007d->n00000001 --> -+<g id="edge1" class="edge"><title>n0000007d:port1->n00000001</title> -+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-173.5C359.322,-173.5 367.976,-173.5 376.644,-173.5"/> -+<polygon fill="black" stroke="black" points="376.807,-177 386.807,-173.5 376.807,-170 376.807,-177"/> -+</g> -+<!-- n00000087 --> -+<g id="node6" class="node"><title>n00000087</title> -+<path fill="#ffb90f" stroke="black" d="M201,-203.5C201,-203.5 339,-203.5 339,-203.5 345,-203.5 351,-209.5 351,-215.5 351,-215.5 351,-375.5 351,-375.5 351,-381.5 345,-387.5 339,-387.5 339,-387.5 201,-387.5 201,-387.5 195,-387.5 189,-381.5 189,-375.5 189,-375.5 189,-215.5 189,-215.5 189,-209.5 195,-203.5 201,-203.5"/> -+<text text-anchor="middle" x="200.5" y="-291.8" font-family="Times,serif" font-size="14.00">0</text> -+<polyline fill="none" stroke="black" points="212,-203.5 212,-387.5 "/> -+<text text-anchor="middle" x="270" y="-299.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 1</text> -+<text text-anchor="middle" x="270" y="-284.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev1</text> -+<polyline fill="none" stroke="black" points="328,-203.5 328,-387.5 "/> -+<text text-anchor="middle" x="339.5" y="-372.3" font-family="Times,serif" font-size="14.00">1</text> -+<polyline fill="none" stroke="black" points="328,-364.5 351,-364.5 "/> -+<text text-anchor="middle" x="339.5" y="-349.3" font-family="Times,serif" font-size="14.00">2</text> -+<polyline fill="none" stroke="black" points="328,-341.5 351,-341.5 "/> -+<text text-anchor="middle" x="339.5" y="-326.3" font-family="Times,serif" font-size="14.00">3</text> -+<polyline fill="none" stroke="black" points="328,-318.5 351,-318.5 "/> -+<text text-anchor="middle" x="339.5" y="-303.3" font-family="Times,serif" font-size="14.00">4</text> -+<polyline fill="none" stroke="black" points="328,-295.5 351,-295.5 "/> -+<text text-anchor="middle" x="339.5" y="-280.3" font-family="Times,serif" font-size="14.00">5</text> -+<polyline fill="none" stroke="black" points="328,-272.5 351,-272.5 "/> -+<text text-anchor="middle" x="339.5" y="-257.3" font-family="Times,serif" font-size="14.00">6</text> -+<polyline fill="none" stroke="black" points="328,-249.5 351,-249.5 "/> -+<text text-anchor="middle" x="339.5" y="-234.3" font-family="Times,serif" font-size="14.00">7</text> -+<polyline fill="none" stroke="black" points="328,-226.5 351,-226.5 "/> -+<text text-anchor="middle" x="339.5" y="-211.3" font-family="Times,serif" font-size="14.00">8</text> -+</g> -+<!-- n00000087->n00000002 --> -+<g id="edge2" class="edge"><title>n00000087:port1->n00000002</title> -+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-376.5C359.322,-376.5 367.976,-376.5 376.644,-376.5"/> -+<polygon fill="black" stroke="black" points="376.807,-380 386.807,-376.5 376.807,-373 376.807,-380"/> -+</g> -+<!-- n00000091 --> -+<g id="node7" class="node"><title>n00000091</title> -+<path fill="#ffb90f" stroke="black" d="M201,-406.5C201,-406.5 339,-406.5 339,-406.5 345,-406.5 351,-412.5 351,-418.5 351,-418.5 351,-578.5 351,-578.5 351,-584.5 345,-590.5 339,-590.5 339,-590.5 201,-590.5 201,-590.5 195,-590.5 189,-584.5 189,-578.5 189,-578.5 189,-418.5 189,-418.5 189,-412.5 195,-406.5 201,-406.5"/> -+<text text-anchor="middle" x="200.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text> -+<polyline fill="none" stroke="black" points="212,-406.5 212,-590.5 "/> -+<text text-anchor="middle" x="270" y="-502.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 2</text> -+<text text-anchor="middle" x="270" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev2</text> -+<polyline fill="none" stroke="black" points="328,-406.5 328,-590.5 "/> -+<text text-anchor="middle" x="339.5" y="-575.3" font-family="Times,serif" font-size="14.00">1</text> -+<polyline fill="none" stroke="black" points="328,-567.5 351,-567.5 "/> -+<text text-anchor="middle" x="339.5" y="-552.3" font-family="Times,serif" font-size="14.00">2</text> -+<polyline fill="none" stroke="black" points="328,-544.5 351,-544.5 "/> -+<text text-anchor="middle" x="339.5" y="-529.3" font-family="Times,serif" font-size="14.00">3</text> -+<polyline fill="none" stroke="black" points="328,-521.5 351,-521.5 "/> -+<text text-anchor="middle" x="339.5" y="-506.3" font-family="Times,serif" font-size="14.00">4</text> -+<polyline fill="none" stroke="black" points="328,-498.5 351,-498.5 "/> -+<text text-anchor="middle" x="339.5" y="-483.3" font-family="Times,serif" font-size="14.00">5</text> -+<polyline fill="none" stroke="black" points="328,-475.5 351,-475.5 "/> -+<text text-anchor="middle" x="339.5" y="-460.3" font-family="Times,serif" font-size="14.00">6</text> -+<polyline fill="none" stroke="black" points="328,-452.5 351,-452.5 "/> -+<text text-anchor="middle" x="339.5" y="-437.3" font-family="Times,serif" font-size="14.00">7</text> -+<polyline fill="none" stroke="black" points="328,-429.5 351,-429.5 "/> -+<text text-anchor="middle" x="339.5" y="-414.3" font-family="Times,serif" font-size="14.00">8</text> -+</g> -+<!-- n00000091->n00000003 --> -+<g id="edge3" class="edge"><title>n00000091:port1->n00000003</title> -+<path fill="none" stroke="black" d="M351,-579.5C359.322,-579.5 367.976,-579.5 376.644,-579.5"/> -+<polygon fill="black" stroke="black" points="376.807,-583 386.807,-579.5 376.807,-576 376.807,-583"/> -+</g> -+<!-- n0000009b --> -+<g id="node8" class="node"><title>n0000009b</title> -+<path fill="#ffb90f" stroke="black" d="M201,-609.5C201,-609.5 339,-609.5 339,-609.5 345,-609.5 351,-615.5 351,-621.5 351,-621.5 351,-781.5 351,-781.5 351,-787.5 345,-793.5 339,-793.5 339,-793.5 201,-793.5 201,-793.5 195,-793.5 189,-787.5 189,-781.5 189,-781.5 189,-621.5 189,-621.5 189,-615.5 195,-609.5 201,-609.5"/> -+<text text-anchor="middle" x="200.5" y="-697.8" font-family="Times,serif" font-size="14.00">0</text> -+<polyline fill="none" stroke="black" points="212,-609.5 212,-793.5 "/> -+<text text-anchor="middle" x="270" y="-705.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 3</text> -+<text text-anchor="middle" x="270" y="-690.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev3</text> -+<polyline fill="none" stroke="black" points="328,-609.5 328,-793.5 "/> -+<text text-anchor="middle" x="339.5" y="-778.3" font-family="Times,serif" font-size="14.00">1</text> -+<polyline fill="none" stroke="black" points="328,-770.5 351,-770.5 "/> -+<text text-anchor="middle" x="339.5" y="-755.3" font-family="Times,serif" font-size="14.00">2</text> -+<polyline fill="none" stroke="black" points="328,-747.5 351,-747.5 "/> -+<text text-anchor="middle" x="339.5" y="-732.3" font-family="Times,serif" font-size="14.00">3</text> -+<polyline fill="none" stroke="black" points="328,-724.5 351,-724.5 "/> -+<text text-anchor="middle" x="339.5" y="-709.3" font-family="Times,serif" font-size="14.00">4</text> -+<polyline fill="none" stroke="black" points="328,-701.5 351,-701.5 "/> -+<text text-anchor="middle" x="339.5" y="-686.3" font-family="Times,serif" font-size="14.00">5</text> -+<polyline fill="none" stroke="black" points="328,-678.5 351,-678.5 "/> -+<text text-anchor="middle" x="339.5" y="-663.3" font-family="Times,serif" font-size="14.00">6</text> -+<polyline fill="none" stroke="black" points="328,-655.5 351,-655.5 "/> -+<text text-anchor="middle" x="339.5" y="-640.3" font-family="Times,serif" font-size="14.00">7</text> -+<polyline fill="none" stroke="black" points="328,-632.5 351,-632.5 "/> -+<text text-anchor="middle" x="339.5" y="-617.3" font-family="Times,serif" font-size="14.00">8</text> -+</g> -+<!-- n0000009b->n00000004 --> -+<g id="edge4" class="edge"><title>n0000009b:port1->n00000004</title> -+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-782.5C359.322,-782.5 367.976,-782.5 376.644,-782.5"/> -+<polygon fill="black" stroke="black" points="376.807,-786 386.807,-782.5 376.807,-779 376.807,-786"/> -+</g> -+<!-- n00000865 --> -+<g id="node9" class="node"><title>n00000865</title> -+<path fill="cornflowerblue" stroke="black" d="M12,-479.5C12,-479.5 141,-479.5 141,-479.5 147,-479.5 153,-485.5 153,-491.5 153,-491.5 153,-505.5 153,-505.5 153,-511.5 147,-517.5 141,-517.5 141,-517.5 12,-517.5 12,-517.5 6,-517.5 0,-511.5 0,-505.5 0,-505.5 0,-491.5 0,-491.5 0,-485.5 6,-479.5 12,-479.5"/> -+<text text-anchor="middle" x="10" y="-494.8" font-family="Times,serif" font-size="14.00"> </text> -+<polyline fill="none" stroke="black" points="20,-479.5 20,-517.5 "/> -+<text text-anchor="middle" x="75" y="-502.3" font-family="Times,serif" font-size="14.00">ov01a10 3-0036</text> -+<text text-anchor="middle" x="75" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev4</text> -+<polyline fill="none" stroke="black" points="130,-479.5 130,-517.5 "/> -+<text text-anchor="middle" x="141.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text> -+</g> -+<!-- n00000865->n00000091 --> -+<g id="edge5" class="edge"><title>n00000865:port0->n00000091:port0</title> -+<path fill="none" stroke="black" d="M153,-498.5C165,-498.5 170.25,-498.5 178.875,-498.5"/> -+<polygon fill="black" stroke="black" points="179,-502 189,-498.5 179,-495 179,-502"/> -+</g> -+<!-- n00000866 --> -+<!-- n00000866->n0000007d --> -+<!-- n00000867 --> -+<!-- n00000867->n00000087 --> -+<!-- n00000868 --> -+<!-- n00000868->n0000009b --> -+</g> -+</svg> -diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst -index f4bb2605f07e..4120eded9a13 100644 ---- a/Documentation/admin-guide/media/v4l-drivers.rst -+++ b/Documentation/admin-guide/media/v4l-drivers.rst -@@ -16,6 +16,7 @@ Video4Linux (V4L) driver-specific documentation - imx - imx7 - ipu3 -+ ipu6-isys - ivtv - mgb4 - omap3isp --- -2.43.2 - - -From 3e80683ecc9ffe38fdf6e6232089794b6019816b Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:29 +0800 -Subject: [PATCH 22/33] Documentation: add documentation of Intel IPU6 driver - and hardware overview - -Add a documentation for an overview of IPU6 hardware and describe the main -the components of IPU6 driver. - -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - .../driver-api/media/drivers/index.rst | 1 + - .../driver-api/media/drivers/ipu6.rst | 205 ++++++++++++++++++ - 2 files changed, 206 insertions(+) - create mode 100644 Documentation/driver-api/media/drivers/ipu6.rst - -diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst -index c4123a16b5f9..7f6f3dcd5c90 100644 ---- a/Documentation/driver-api/media/drivers/index.rst -+++ b/Documentation/driver-api/media/drivers/index.rst -@@ -26,6 +26,7 @@ Video4Linux (V4L) drivers - vimc-devel - zoran - ccs/ccs -+ ipu6 - - - Digital TV drivers -diff --git a/Documentation/driver-api/media/drivers/ipu6.rst b/Documentation/driver-api/media/drivers/ipu6.rst -new file mode 100644 -index 000000000000..b6357155c13b ---- /dev/null -+++ b/Documentation/driver-api/media/drivers/ipu6.rst -@@ -0,0 +1,205 @@ -+.. SPDX-License-Identifier: GPL-2.0 -+ -+================== -+Intel IPU6 Driver -+================== -+ -+Author: Bingbu Cao <bingbu.cao@intel.com> -+ -+Overview -+========= -+ -+Intel IPU6 is the sixth generation of Intel Image Processing Unit used in some -+Intel Chipsets such as Tiger Lake, Jasper Lake, Alder Lake, Raptor Lake and -+Meteor Lake. IPU6 consists of two major systems: Input System (IS) and -+Processing System (PS). IPU6 are visible on the PCI bus as a single device, -+it can be found by ``lspci``: -+ -+``0000:00:05.0 Multimedia controller: Intel Corporation Device xxxx (rev xx)`` -+ -+IPU6 has a 16 MB BAR in PCI configuration Space for MMIO registers which is -+visible for driver. -+ -+Buttress -+========= -+ -+The IPU6 is connecting to the system fabric with ``Buttress`` which is enabling -+host driver to control the IPU6, it also allows IPU6 access the system memory to -+store and load frame pixel streams and any other metadata. -+ -+``Buttress`` mainly manages several system functionalities - power management, -+interrupt handling, firmware authentication and global timer sync. -+ -+IS and PS Power flow -+--------------------------- -+ -+IPU6 driver initialize the IS and PS power up or down request by setting the -+Buttress frequency control register for IS and PS - -+``IPU6_BUTTRESS_REG_IS_FREQ_CTL`` and ``IPU6_BUTTRESS_REG_PS_FREQ_CTL`` in -+function: -+ -+.. c:function:: int ipu6_buttress_power(..., bool on) -+ -+Buttress forwards the request to Punit, after Punit execute the power up flow, -+buttress indicates driver that IS or PS is powered up by updating the power -+status registers. -+ -+.. Note:: IS power up needs take place prior to PS power up, IS power down needs -+ take place after PS power down due to hardware limitation. -+ -+ -+Interrupt -+------------ -+ -+IPU6 interrupt can be generated as MSI or INTA, interrupt will be triggered -+when IS, PS, Buttress event or error happen, driver can get the interrupt -+cause by reading the interrupt status register ``BUTTRESS_REG_ISR_STATUS``, -+driver firstly clear the irq status and then call specific IS or PS irq handler. -+ -+.. c:function:: irqreturn_t ipu6_buttress_isr(int irq, ...) -+ -+Security and firmware authentication -+------------------------------------- -+To address the IPU6 firmware security concerns, the IPU6 firmware needs to -+undergo an authentication process before it is allowed to executed on the IPU6 -+internal processors. Driver will work with Converged Security Engine (CSE) to -+complete authentication process. CSE is responsible of authenticating the -+IPU6 firmware, the authenticated firmware binary is copied into an isolated -+memory region. Firmware authentication process is implemented by CSE following -+an IPC handshake with driver. There are some Buttress registers used by CSE and -+driver to communicate with each other as IPC messages. -+ -+.. c:function:: int ipu6_buttress_authenticate(...) -+ -+Global timer sync -+------------------ -+IPU driver initiates a Hammock Harbor synchronization flow each time it starts -+camera operation. IPU will synchronizes an internal counter in the Buttress -+with a copy of SoC time, this counter keeps the updated time until camera -+operation is stopped. Driver can use this time counter to calibrate the -+timestamp based on the timestamp in response event from firmware. -+ -+.. c:function:: int ipu6_buttress_start_tsc_sync(...) -+ -+ -+DMA and MMU -+============ -+ -+IPU6 has its own scalar processor where the firmware run at, it has -+an internal 32-bits virtual address space. IPU6 has MMU address translation -+hardware to allow that scalar process access the internal memory and external -+system memory through IPU6 virtual address. The address translation is -+based on two levels of page lookup tables stored in system memory which are -+maintained by IPU6 driver. IPU6 driver sets the level-1 page table base address -+to MMU register and allow MMU to lookup the page table. -+ -+IPU6 driver exports its own DMA operations. Driver will update the page table -+entries for each DMA operation and invalidate the MMU TLB after each unmap and -+free. -+ -+.. code-block:: none -+ -+ const struct dma_map_ops ipu6_dma_ops = { -+ .alloc = ipu6_dma_alloc, -+ .free = ipu6_dma_free, -+ .mmap = ipu6_dma_mmap, -+ .map_sg = ipu6_dma_map_sg, -+ .unmap_sg = ipu6_dma_unmap_sg, -+ ... -+ }; -+ -+.. Note:: IPU6 MMU works behind IOMMU, so for each IPU6 DMA ops, driver will -+ call generic PCI DMA ops to ask IOMMU to do the additional mapping -+ if VT-d enabled. -+ -+ -+Firmware file format -+===================== -+ -+IPU6 release the firmware in Code Partition Directory (CPD) file format. The -+CPD firmware contains a CPD header, several CPD entries and CPD components. -+CPD component includes 3 entries - manifest, metadata and module data. Manifest -+and metadata are defined by CSE and used by CSE for authentication. Module data -+is defined by IPU6 which holds the binary data of firmware called package -+directory. IPU6 driver (``ipu6-cpd.c``) parses and validates the CPD firmware -+file and get the package directory binary data of IPU6 firmware, copy it to -+specific DMA buffer and sets its base address to Buttress ``FW_SOURCE_BASE`` -+register, CSE will do authentication for this firmware binary. -+ -+ -+Syscom interface -+================ -+ -+IPU6 driver communicates with firmware via syscom ABI. Syscom is an -+inter-processor communication mechanism between IPU scalar processor and CPU. -+There are a number of resources shared between firmware and software. -+A system memory region where the message queues reside, firmware can access the -+memory region via IPU MMU. Syscom queues are FIFO fixed depth queues with -+configurable elements ``token`` (message). There is also a common IPU MMIO -+registers where the queue read and write indices reside. Software and firmware -+work as producer and consumer of tokens in queue, and update the write and read -+indices separately when sending or receiving each message. -+ -+IPU6 driver must prepare and configure the number of input and output queues, -+configure the count of tokens per queue and the size of per token before -+initiate and start the communication with firmware, firmware and software must -+use same configurations. IPU6 Buttress has a number of firmware boot parameter -+registers which can be used to store the address of configuration and initiate -+the Syscom state, then driver can request firmware to start and run via setting -+the scalar processor control status register. -+ -+ -+Input System -+============== -+ -+IPU6 input system consists of MIPI D-PHY and several CSI receiver controllers, -+it can capture image pixel data from camera sensors or other MIPI CSI output -+devices. -+ -+D-PHYs and CSI-2 ports lane mapping -+----------------------------------- -+ -+IPU6 integrates different D-PHY IPs on different SoCs, on Tiger Lake and Alder -+Lake, IPU6 integrates MCD10 D-PHY, IPU6SE on Jasper Lake integrates JSL D-PHY -+and IPU6EP on Meteor Lake integrates a Synopsys DWC D-PHY. There is an adaption -+layer between D-PHY and CSI receiver controller which includes port -+configuration, PHY wrapper or private test interfaces for D-PHY. There are 3 -+D-PHY drivers ``ipu6-isys-mcd-phy.c``, ``ipu6-isys-jsl-phy.c`` and -+``ipu6-isys-dwc-phy.c`` program the above 3 D-PHYs in IPU6. -+ -+Different IPU6 version has different D-PHY lanes mappings, On Tiger Lake, there -+are 12 data lanes and 8 clock lanes, IPU6 support maximum 8 CSI-2 ports, see -+the ppi mmapping in ``ipu6-isys-mcd-phy.c`` for more information. On Jasper Lake -+and Alder Lake, D-PHY has 8 data lanes and 4 clock lanes, IPU6 support maximum 4 -+CSI-2 ports. For Meteor Lake, D-PHY has 12 data lanes and 6 clock lanes, IPU6 -+support maximum 6 CSI-2 ports. -+ -+.. Note:: Each adjacent CSI ports work as a pair and share the data lanes. -+ For example, for CSI port 0 and 1, CSI port 0 support maximum 4 -+ data lanes, CSI port 1 support maximum 2 data lanes, CSI port 0 -+ with 2 data lanes can work together with CSI port 1 with 2 data lanes. -+ If trying to use CSI port 0 with 4 lanes, CSI port 1 will not be -+ available as the 4 data lanes are shared by CSI port 0 and 1. Same -+ scenario is also applied for CSI port 2/3, 4/5 and 7/8. -+ -+IS firmware ABIs -+---------------- -+ -+IPU6 firmware define a series of ABIs to software. In general, software firstly -+prepare the stream configuration ``struct ipu6_fw_isys_stream_cfg_data_abi`` -+and send the configuration to firmware via sending ``STREAM_OPEN`` command. -+Stream configuration includes input pins and output pins, input pin -+``struct ipu6_fw_isys_input_pin_info_abi`` defines the resolution and data type -+of input source, output pin ``struct ipu6_fw_isys_output_pin_info_abi`` -+defines the output resolution, stride and frame format, etc. Once driver get the -+interrupt from firmware that indicates stream open successfully, driver will -+send the ``STREAM_START`` and ``STREAM_CAPTURE`` command to request firmware to -+start capturing image frames. ``STREAM_CAPTURE`` command queues the buffers to -+firmware with ``struct ipu6_fw_isys_frame_buff_set``, software then wait the -+interrupt and response from firmware, ``PIN_DATA_READY`` means data ready -+on specific output pin and then software return the buffers to user. -+ -+.. Note:: See :ref:`Examples<ipu6_isys_capture_examples>` about how to do -+ capture by IPU6 IS driver. -+ -+ --- -2.43.2 - - -From d883f3386e7185d9404cb25e32df986656a4e82a Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:30 +0800 -Subject: [PATCH 23/33] media: ipu6/isys: support line-based metadata capture - support - -Some camera sensor can output the embedded data in specific -data type. This patch add the support for embedded data capture -in IPU6 IS driver. - -It's based on Sakari's line-based metadata capture support change: -<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=metadata> - -Signed-off-by: Hongju Wang <hongju.wang@intel.com> -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 5 + - .../media/pci/intel/ipu6/ipu6-isys-queue.c | 44 ++-- - .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 5 + - .../media/pci/intel/ipu6/ipu6-isys-video.c | 201 +++++++++++++++--- - .../media/pci/intel/ipu6/ipu6-isys-video.h | 7 +- - 5 files changed, 216 insertions(+), 46 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -index ac9fa3e0d7ab..a6430d531129 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -@@ -42,6 +42,11 @@ static const u32 csi2_supported_codes[] = { - MEDIA_BUS_FMT_SGBRG8_1X8, - MEDIA_BUS_FMT_SGRBG8_1X8, - MEDIA_BUS_FMT_SRGGB8_1X8, -+ MEDIA_BUS_FMT_META_8, -+ MEDIA_BUS_FMT_META_10, -+ MEDIA_BUS_FMT_META_12, -+ MEDIA_BUS_FMT_META_16, -+ MEDIA_BUS_FMT_META_24, - 0 - }; - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c -index 735d2d642d87..15fa7ed22b2f 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c -@@ -35,11 +35,14 @@ static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, - /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ - if (!*num_planes) { - use_fmt = true; -- *num_planes = av->mpix.num_planes; -+ if (av->vfmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ *num_planes = av->vfmt.fmt.pix_mp.num_planes; -+ else if (av->vfmt.type == V4L2_BUF_TYPE_META_CAPTURE) -+ *num_planes = 1; - } - - for (i = 0; i < *num_planes; i++) { -- size = av->mpix.plane_fmt[i].sizeimage; -+ size = ipu6_get_data_size(&av->vfmt, i); - if (use_fmt) { - sizes[i] = size; - } else if (sizes[i] < size) { -@@ -59,16 +62,17 @@ static int ipu6_isys_buf_prepare(struct vb2_buffer *vb) - struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); - struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); - struct device *dev = &av->isys->adev->auxdev.dev; -+ u32 bytesperline = ipu6_get_bytes_per_line(&av->vfmt); -+ u32 height = ipu6_get_frame_height(&av->vfmt); -+ u32 size = ipu6_get_data_size(&av->vfmt, 0); - - dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", -- av->vdev.name, av->mpix.plane_fmt[0].sizeimage, -- vb2_plane_size(vb, 0)); -+ av->vdev.name, size, vb2_plane_size(vb, 0)); - -- if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0)) -+ if (size > vb2_plane_size(vb, 0)) - return -EINVAL; - -- vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline * -- av->mpix.height); -+ vb2_set_plane_payload(vb, 0, bytesperline * height); - vb->planes[0].data_offset = 0; - - return 0; -@@ -437,18 +441,22 @@ static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) - return ret; - } - -- if (format.width != av->mpix.width || -- format.height != av->mpix.height) { -- dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", -- av->mpix.width, av->mpix.height, -- format.width, format.height); -+ if (format.width != ipu6_get_frame_width(&av->vfmt) || -+ format.height != ipu6_get_frame_height(&av->vfmt)) { -+ dev_err(dev, "wrong width or height %ux%u (%ux%u expected)\n", -+ ipu6_get_frame_width(&av->vfmt), -+ ipu6_get_frame_height(&av->vfmt), format.width, -+ format.height); - return -EINVAL; - } - -- if (format.field != av->mpix.field) { -- dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n", -- av->mpix.field, format.field); -- return -EINVAL; -+ if (av->vfmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { -+ if (format.field != av->vfmt.fmt.pix_mp.field) { -+ dev_dbg(dev, -+ "wrong field value 0x%8.8x (%8.8x expected)\n", -+ av->vfmt.fmt.pix_mp.field, format.field); -+ return -EINVAL; -+ } - } - - if (format.code != av->pfmt->code) { -@@ -531,8 +539,8 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) - int nr_queues, ret; - - dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", -- av->vdev.name, av->mpix.width, av->mpix.height, -- av->pfmt->css_pixelformat); -+ av->vdev.name, ipu6_get_frame_width(&av->vfmt), -+ ipu6_get_frame_height(&av->vfmt), av->pfmt->css_pixelformat); - - ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); - if (ret < 0) { -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -index 510c5ca34f9f..3c9263ac02a3 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -@@ -20,25 +20,30 @@ unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) - { - switch (code) { - case MEDIA_BUS_FMT_RGB888_1X24: -+ case MEDIA_BUS_FMT_META_24: - return 24; - case MEDIA_BUS_FMT_RGB565_1X16: - case MEDIA_BUS_FMT_UYVY8_1X16: - case MEDIA_BUS_FMT_YUYV8_1X16: -+ case MEDIA_BUS_FMT_META_16: - return 16; - case MEDIA_BUS_FMT_SBGGR12_1X12: - case MEDIA_BUS_FMT_SGBRG12_1X12: - case MEDIA_BUS_FMT_SGRBG12_1X12: - case MEDIA_BUS_FMT_SRGGB12_1X12: -+ case MEDIA_BUS_FMT_META_12: - return 12; - case MEDIA_BUS_FMT_SBGGR10_1X10: - case MEDIA_BUS_FMT_SGBRG10_1X10: - case MEDIA_BUS_FMT_SGRBG10_1X10: - case MEDIA_BUS_FMT_SRGGB10_1X10: -+ case MEDIA_BUS_FMT_META_10: - return 10; - case MEDIA_BUS_FMT_SBGGR8_1X8: - case MEDIA_BUS_FMT_SGBRG8_1X8: - case MEDIA_BUS_FMT_SGRBG8_1X8: - case MEDIA_BUS_FMT_SRGGB8_1X8: -+ case MEDIA_BUS_FMT_META_8: - return 8; - default: - WARN_ON(1); -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -index 847eac26bcd6..1a023bf1e1a6 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -@@ -85,6 +85,11 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { - IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, - {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, - IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, -+ {V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, 0}, -+ {V4L2_META_FMT_GENERIC_CSI2_10, 10, 10, MEDIA_BUS_FMT_META_10, 0}, -+ {V4L2_META_FMT_GENERIC_CSI2_12, 12, 12, MEDIA_BUS_FMT_META_12, 0}, -+ {V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, 0}, -+ {V4L2_META_FMT_GENERIC_CSI2_24, 24, 24, MEDIA_BUS_FMT_META_24, 0}, - }; - - static int video_open(struct file *file) -@@ -181,12 +186,12 @@ static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, - return 0; - } - --static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh, -- struct v4l2_format *fmt) -+static int vidioc_get_format(struct file *file, void *fh, -+ struct v4l2_format *fmt) - { - struct ipu6_isys_video *av = video_drvdata(file); - -- fmt->fmt.pix_mp = av->mpix; -+ *fmt = av->vfmt; - - return 0; - } -@@ -245,30 +250,114 @@ ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av, - return pfmt; - } - --static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh, -- struct v4l2_format *f) -+static const struct ipu6_isys_pixelformat * -+ipu6_isys_video_try_fmt_meta(struct ipu6_isys_video *av, -+ struct v4l2_meta_format *meta) -+{ -+ const struct ipu6_isys_pixelformat *pfmt = -+ ipu6_isys_get_pixelformat(meta->dataformat); -+ -+ memset(&av->vfmt, 0, sizeof(av->vfmt)); -+ av->vfmt.type = V4L2_BUF_TYPE_META_CAPTURE; -+ av->pfmt = pfmt; -+ -+ meta->dataformat = pfmt->pixelformat; -+ meta->width = clamp(meta->width, IPU6_ISYS_MIN_WIDTH, -+ IPU6_ISYS_MAX_WIDTH); -+ meta->height = clamp(meta->height, IPU6_ISYS_MIN_HEIGHT, -+ IPU6_ISYS_MAX_HEIGHT); -+ -+ if (pfmt->bpp != pfmt->bpp_packed) -+ meta->bytesperline = meta->width * -+ DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); -+ else -+ meta->bytesperline = -+ DIV_ROUND_UP(meta->width * pfmt->bpp, BITS_PER_BYTE); -+ -+ meta->bytesperline = ALIGN(meta->bytesperline, av->isys->line_align); -+ meta->buffersize = -+ max(max(meta->buffersize, meta->bytesperline * meta->height + -+ max(meta->bytesperline, -+ av->isys->pdata->ipdata->isys_dma_overshoot)), 1U); -+ -+ return pfmt; -+} -+ -+static const struct ipu6_isys_pixelformat * -+ipu6_isys_video_try_fmt(struct ipu6_isys_video *av, struct v4l2_format *f) -+{ -+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ return ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); -+ else if (f->type == V4L2_BUF_TYPE_META_CAPTURE) -+ return ipu6_isys_video_try_fmt_meta(av, &f->fmt.meta); -+ else -+ return &ipu6_isys_pfmts[0]; -+} -+ -+static int vidioc_set_format(struct file *file, void *fh, -+ struct v4l2_format *f) - { - struct ipu6_isys_video *av = video_drvdata(file); - - if (av->aq.vbq.streaming) - return -EBUSY; - -- av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); -- av->mpix = f->fmt.pix_mp; -+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && -+ f->type != V4L2_BUF_TYPE_META_CAPTURE) -+ return -EINVAL; -+ -+ av->pfmt = ipu6_isys_video_try_fmt(av, f); -+ av->vfmt = *f; - - return 0; - } - --static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh, -- struct v4l2_format *f) -+static int vidioc_try_format(struct file *file, void *fh, -+ struct v4l2_format *f) - { - struct ipu6_isys_video *av = video_drvdata(file); - -- ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); -+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && -+ f->type != V4L2_BUF_TYPE_META_CAPTURE) -+ return -EINVAL; -+ -+ ipu6_isys_video_try_fmt(av, f); - - return 0; - } - -+static int vidioc_request_qbufs(struct file *file, void *priv, -+ struct v4l2_requestbuffers *p) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ int ret; -+ -+ av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->type); -+ av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->type); -+ -+ ret = vb2_queue_change_type(&av->aq.vbq, p->type); -+ if (ret) -+ return ret; -+ -+ return vb2_ioctl_reqbufs(file, priv, p); -+} -+ -+static int vidioc_create_bufs(struct file *file, void *priv, -+ struct v4l2_create_buffers *p) -+{ -+ struct ipu6_isys_video *av = video_drvdata(file); -+ int ret; -+ -+ av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->format.type); -+ av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->format.type); -+ -+ ret = vb2_queue_change_type(&av->aq.vbq, p->format.type); -+ if (ret) -+ return ret; -+ -+ return vb2_ioctl_create_bufs(file, priv, p); -+} -+ - static int link_validate(struct media_link *link) - { - struct ipu6_isys_video *av = -@@ -279,6 +368,8 @@ static int link_validate(struct media_link *link) - struct v4l2_mbus_framefmt *s_fmt; - struct media_pad *s_pad; - u32 s_stream; -+ u32 height; -+ u32 width; - int ret = -EPIPE; - - if (!link->source->entity) -@@ -305,11 +396,13 @@ static int link_validate(struct media_link *link) - goto unlock; - } - -- if (s_fmt->width != av->mpix.width || -- s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) { -+ height = ipu6_get_frame_height(&av->vfmt); -+ width = ipu6_get_frame_width(&av->vfmt); -+ if (s_fmt->width != width || s_fmt->height != height || -+ s_fmt->code != av->pfmt->code) { - dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", -- s_fmt->width, s_fmt->height, s_fmt->code, -- av->mpix.width, av->mpix.height, av->pfmt->code); -+ s_fmt->width, s_fmt->height, s_fmt->code, width, height, -+ av->pfmt->code); - goto unlock; - } - -@@ -393,10 +486,10 @@ static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, - - output_pin = &cfg->output_pins[output_pins]; - output_pin->input_pin_id = input_pins; -- output_pin->output_res.width = av->mpix.width; -- output_pin->output_res.height = av->mpix.height; -+ output_pin->output_res.width = ipu6_get_frame_width(&av->vfmt); -+ output_pin->output_res.height = ipu6_get_frame_height(&av->vfmt); - -- output_pin->stride = av->mpix.plane_fmt[0].bytesperline; -+ output_pin->stride = ipu6_get_bytes_per_line(&av->vfmt); - if (av->pfmt->bpp != av->pfmt->bpp_packed) - output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; - else -@@ -663,8 +756,8 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - - esd = media_entity_to_v4l2_subdev(av->stream->source_entity); - -- av->watermark.width = av->mpix.width; -- av->watermark.height = av->mpix.height; -+ av->watermark.width = ipu6_get_frame_width(&av->vfmt); -+ av->watermark.height = ipu6_get_frame_height(&av->vfmt); - av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; - av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; - -@@ -992,11 +1085,15 @@ static const struct v4l2_ioctl_ops ioctl_ops_mplane = { - .vidioc_querycap = ipu6_isys_vidioc_querycap, - .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, - .vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes, -- .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, -- .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, -- .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, -- .vidioc_reqbufs = vb2_ioctl_reqbufs, -- .vidioc_create_bufs = vb2_ioctl_create_bufs, -+ .vidioc_g_fmt_vid_cap_mplane = vidioc_get_format, -+ .vidioc_s_fmt_vid_cap_mplane = vidioc_set_format, -+ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_format, -+ .vidioc_enum_fmt_meta_cap = ipu6_isys_vidioc_enum_fmt, -+ .vidioc_g_fmt_meta_cap = vidioc_get_format, -+ .vidioc_s_fmt_meta_cap = vidioc_set_format, -+ .vidioc_try_fmt_meta_cap = vidioc_try_format, -+ .vidioc_reqbufs = vidioc_request_qbufs, -+ .vidioc_create_bufs = vidioc_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_qbuf = vb2_ioctl_qbuf, -@@ -1199,7 +1296,8 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av) - - mutex_init(&av->mutex); - av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | -- V4L2_CAP_VIDEO_CAPTURE_MPLANE; -+ V4L2_CAP_VIDEO_CAPTURE_MPLANE | -+ V4L2_CAP_META_CAPTURE; - av->vdev.vfl_dir = VFL_DIR_RX; - - ret = ipu6_isys_queue_init(&av->aq); -@@ -1220,8 +1318,8 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av) - av->vdev.queue = &av->aq.vbq; - av->vdev.lock = &av->mutex; - -- ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp); -- av->mpix = format.fmt.pix_mp; -+ ipu6_isys_video_try_fmt(av, &format); -+ av->vfmt = format; - - set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); - video_set_drvdata(&av->vdev, av); -@@ -1251,3 +1349,52 @@ void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) - media_entity_cleanup(&av->vdev.entity); - mutex_destroy(&av->mutex); - } -+ -+u32 ipu6_get_data_size(struct v4l2_format *vfmt, int plane) -+{ -+ if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ return vfmt->fmt.pix_mp.plane_fmt[plane].sizeimage; -+ else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE) -+ return vfmt->fmt.meta.buffersize; -+ -+ WARN_ON_ONCE(1); -+ -+ return 0; -+} -+ -+u32 ipu6_get_bytes_per_line(struct v4l2_format *vfmt) -+{ -+ if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ return vfmt->fmt.pix_mp.plane_fmt[0].bytesperline; -+ else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE) -+ return vfmt->fmt.meta.bytesperline; -+ -+ WARN_ON_ONCE(1); -+ -+ return 0; -+} -+ -+u32 ipu6_get_frame_width(struct v4l2_format *vfmt) -+{ -+ if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ return vfmt->fmt.pix_mp.width; -+ else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE) -+ return vfmt->fmt.meta.width; -+ -+ WARN_ON_ONCE(1); -+ -+ return 0; -+} -+ -+u32 ipu6_get_frame_height(struct v4l2_format *vfmt) -+{ -+ if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) -+ return vfmt->fmt.pix_mp.height; -+ else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE) -+ return vfmt->fmt.meta.height; -+ -+ WARN_ON_ONCE(1); -+ -+ return 0; -+} -+ -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h -index 21cd33c7e277..2634ec0fd68b 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h -@@ -90,7 +90,7 @@ struct ipu6_isys_video { - struct mutex mutex; - struct media_pad pad; - struct video_device vdev; -- struct v4l2_pix_format_mplane mpix; -+ struct v4l2_format vfmt; - const struct ipu6_isys_pixelformat *pfmt; - struct ipu6_isys *isys; - struct ipu6_isys_stream *stream; -@@ -133,4 +133,9 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - bool state); - void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); - -+u32 ipu6_get_data_size(struct v4l2_format *vfmt, int plane); -+u32 ipu6_get_bytes_per_line(struct v4l2_format *vfmt); -+u32 ipu6_get_frame_width(struct v4l2_format *vfmt); -+u32 ipu6_get_frame_height(struct v4l2_format *vfmt); -+ - #endif /* IPU6_ISYS_VIDEO_H */ --- -2.43.2 - - -From 9a6fb311b81433ebbd8e0769bed19958a6a5a5f6 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao <bingbu.cao@intel.com> -Date: Thu, 11 Jan 2024 14:55:31 +0800 -Subject: [PATCH 24/33] media: ipu6/isys: support new v4l2 subdev state APIs - -Add support for the upcoming v4l2-subdev API changes in kernel 6.8. -This patch is based on Sakari's branch: -<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=metadata> - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> -Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> ---- - drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 8 +++----- - .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 19 +++++++++++-------- - .../media/pci/intel/ipu6/ipu6-isys-subdev.h | 2 -- - .../media/pci/intel/ipu6/ipu6-isys-video.c | 3 +-- - 4 files changed, 15 insertions(+), 17 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -index a6430d531129..6f258cf92fc1 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c -@@ -403,12 +403,11 @@ static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, - if (!sink_ffmt) - return -EINVAL; - -- src_ffmt = v4l2_subdev_state_get_stream_format(state, sel->pad, -- sel->stream); -+ src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream); - if (!src_ffmt) - return -EINVAL; - -- crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); -+ crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); - if (!crop) - return -EINVAL; - -@@ -453,7 +452,7 @@ static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, - if (!sink_ffmt) - return -EINVAL; - -- crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); -+ crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); - if (!crop) - return -EINVAL; - -@@ -480,7 +479,6 @@ static const struct v4l2_subdev_video_ops csi2_sd_video_ops = { - }; - - static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = { -- .init_cfg = ipu6_isys_subdev_init_cfg, - .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = ipu6_isys_subdev_set_fmt, - .get_selection = ipu6_isys_csi2_get_sel, -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -index 3c9263ac02a3..aeccd6f93986 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c -@@ -156,8 +156,7 @@ int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, - format->format.field = V4L2_FIELD_NONE; - - /* Store the format and propagate it to the source pad. */ -- fmt = v4l2_subdev_state_get_stream_format(state, format->pad, -- format->stream); -+ fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); - if (!fmt) - return -EINVAL; - -@@ -182,8 +181,7 @@ int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, - if (ret) - return -EINVAL; - -- crop = v4l2_subdev_state_get_stream_crop(state, other_pad, -- other_stream); -+ crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream); - /* reset crop */ - crop->left = 0; - crop->top = 0; -@@ -241,7 +239,7 @@ int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, - return -EINVAL; - - state = v4l2_subdev_lock_and_get_active_state(sd); -- fmt = v4l2_subdev_state_get_stream_format(state, pad, stream); -+ fmt = v4l2_subdev_state_get_format(state, pad, stream); - if (fmt) - *format = *fmt; - v4l2_subdev_unlock_state(state); -@@ -259,7 +257,7 @@ int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, - return -EINVAL; - - state = v4l2_subdev_lock_and_get_active_state(sd); -- rect = v4l2_subdev_state_get_stream_crop(state, pad, stream); -+ rect = v4l2_subdev_state_get_crop(state, pad, stream); - if (rect) - *crop = *rect; - v4l2_subdev_unlock_state(state); -@@ -291,8 +289,8 @@ u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) - return source_stream; - } - --int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, -- struct v4l2_subdev_state *state) -+static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state) - { - struct v4l2_subdev_route route = { - .sink_pad = 0, -@@ -317,6 +315,10 @@ int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, - return subdev_set_routing(sd, state, routing); - } - -+static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = { -+ .init_state = ipu6_isys_subdev_init_state, -+}; -+ - int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, - const struct v4l2_subdev_ops *ops, - unsigned int nr_ctrls, -@@ -334,6 +336,7 @@ int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, - V4L2_SUBDEV_FL_STREAMS; - asd->sd.owner = THIS_MODULE; - asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; -+ asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops; - - asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, - sizeof(*asd->pad), GFP_KERNEL); -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h -index adea2a55761d..f4e32b094b5b 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h -@@ -46,8 +46,6 @@ int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_mbus_framefmt *format); - int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_rect *crop); --int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, -- struct v4l2_subdev_state *state); - int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state, - enum v4l2_subdev_format_whence which, -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -index 1a023bf1e1a6..62d4043fc2a1 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -@@ -389,8 +389,7 @@ static int link_validate(struct media_link *link) - - v4l2_subdev_lock_state(s_state); - -- s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index, -- s_stream); -+ s_fmt = v4l2_subdev_state_get_format(s_state, s_pad->index, s_stream); - if (!s_fmt) { - dev_err(dev, "failed to get source pad format\n"); - goto unlock; --- -2.43.2 - - -From 53ca77877d2cc7ecc39bb0ef26a1871a1c26afd1 Mon Sep 17 00:00:00 2001 -From: Hans de Goede <hdegoede@redhat.com> -Date: Mon, 15 Jan 2024 15:57:06 +0100 -Subject: [PATCH 25/33] media: intel/ipu6: Disable packed bayer v4l2-buffer - formats on TGL - -Using CSI2 packing to store 10bpp bayer data in the v4l2-buffers does not -work on Tiger Lake when testing with an ov01a1s sensor. - -Disable packed bayer formats on Tiger Lake for now. - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - .../media/pci/intel/ipu6/ipu6-isys-video.c | 65 ++++++++++++------- - 1 file changed, 43 insertions(+), 22 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -index 62d4043fc2a1..c971ffe0b948 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -+++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -@@ -61,6 +61,17 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { - IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, - {V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, - IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, -+ {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, -+ {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, -+ {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, -+ IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, -+ {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, -+ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, -+}; -+ -+const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[] = { - {V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, - IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, - {V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, -@@ -77,19 +88,6 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { - IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, - {V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, - IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, -- {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, -- IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, -- {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, -- IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, -- {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, -- IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, -- {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, -- IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, -- {V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, 0}, -- {V4L2_META_FMT_GENERIC_CSI2_10, 10, 10, MEDIA_BUS_FMT_META_10, 0}, -- {V4L2_META_FMT_GENERIC_CSI2_12, 12, 12, MEDIA_BUS_FMT_META_12, 0}, -- {V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, 0}, -- {V4L2_META_FMT_GENERIC_CSI2_24, 24, 24, MEDIA_BUS_FMT_META_24, 0}, - }; - - static int video_open(struct file *file) -@@ -114,14 +112,27 @@ static int video_release(struct file *file) - return vb2_fop_release(file); - } - -+static const struct ipu6_isys_pixelformat * -+ipu6_isys_get_pixelformat_by_idx(unsigned int idx) -+{ -+ if (idx < ARRAY_SIZE(ipu6_isys_pfmts)) -+ return &ipu6_isys_pfmts[idx]; -+ -+ idx -= ARRAY_SIZE(ipu6_isys_pfmts); -+ -+ if (idx < ARRAY_SIZE(ipu6_isys_pfmts_packed)) -+ return &ipu6_isys_pfmts_packed[idx]; -+ -+ return NULL; -+} -+ - static const struct ipu6_isys_pixelformat * - ipu6_isys_get_pixelformat(u32 pixelformat) - { -+ const struct ipu6_isys_pixelformat *pfmt; - unsigned int i; - -- for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { -- const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i]; -- -+ for (i = 0; (pfmt = ipu6_isys_get_pixelformat_by_idx(i)); i++) { - if (pfmt->pixelformat == pixelformat) - return pfmt; - } -@@ -143,24 +154,34 @@ int ipu6_isys_vidioc_querycap(struct file *file, void *fh, - int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, - struct v4l2_fmtdesc *f) - { -- unsigned int i, found = 0; -+ struct ipu6_isys_video *av = video_drvdata(file); -+ const struct ipu6_isys_pixelformat *fmt; -+ unsigned int i, nfmts, found = 0; -+ -+ nfmts = ARRAY_SIZE(ipu6_isys_pfmts); -+ /* Disable packed formats on TGL for now, TGL has 8 CSI ports */ -+ if (av->isys->pdata->ipdata->csi2.nports != 8) -+ nfmts += ARRAY_SIZE(ipu6_isys_pfmts_packed); - -- if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts)) -+ if (f->index >= nfmts) - return -EINVAL; - - if (!f->mbus_code) { -+ fmt = ipu6_isys_get_pixelformat_by_idx(f->index); - f->flags = 0; -- f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat; -+ f->pixelformat = fmt->pixelformat; - return 0; - } - -- for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { -- if (f->mbus_code != ipu6_isys_pfmts[i].code) -+ for (i = 0; i < nfmts; i++) { -+ fmt = ipu6_isys_get_pixelformat_by_idx(i); -+ -+ if (f->mbus_code != fmt->code) - continue; - - if (f->index == found) { - f->flags = 0; -- f->pixelformat = ipu6_isys_pfmts[i].pixelformat; -+ f->pixelformat = fmt->pixelformat; - return 0; - } - found++; --- -2.43.2 - - -From ed407043f03e9af2b09ab8ad449c2716ce7fde01 Mon Sep 17 00:00:00 2001 +From b5a82464677815dcbe6a06b0ee92ed065b1ec275 Mon Sep 17 00:00:00 2001 From: Hans de Goede <hdegoede@redhat.com> Date: Mon, 6 Nov 2023 12:13:42 +0100 -Subject: [PATCH 26/33] media: Add ov01a1s driver +Subject: [PATCH 1/3] media: Add ov01a1s driver Add ov01a1s driver from: https://github.com/intel/ipu6-drivers/ @@ -16257,10 +15,10 @@ Signed-off-by: Hans de Goede <hdegoede@redhat.com> create mode 100644 drivers/media/i2c/ov01a1s.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 4c3435921f19..08f934740980 100644 +index c6d3ee472d81..59e0d304bef2 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig -@@ -313,6 +313,15 @@ config VIDEO_OV01A10 +@@ -315,6 +315,15 @@ config VIDEO_OV01A10 To compile this driver as a module, choose M here: the module will be called ov01a10. @@ -17486,70 +1244,12 @@ index 000000000000..0dcce8b492b4 +MODULE_DESCRIPTION("OmniVision OV01A1S sensor driver"); +MODULE_LICENSE("GPL v2"); -- -2.43.2 - - -From 9f58ae728245ad7ac604737ab16781d7ccb2006e Mon Sep 17 00:00:00 2001 -From: Florian Klink <flokli@flokli.de> -Date: Sun, 17 Mar 2024 14:24:05 +0200 -Subject: [PATCH 27/33] ov01a1s.c: support Linux 6.8.0 - -Used https://github.com/intel/ipu6-drivers/pull/213 as an inspiration. ---- - drivers/media/i2c/ov01a1s.c | 13 ++++++++++--- - 1 file changed, 10 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c -index 0dcce8b492b4..923b12b2a948 100644 ---- a/drivers/media/i2c/ov01a1s.c -+++ b/drivers/media/i2c/ov01a1s.c -@@ -832,8 +832,10 @@ static int ov01a1s_set_format(struct v4l2_subdev *sd, - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; --#else -+#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) - *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; -+#else -+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; - #endif - } else { - ov01a1s->cur_mode = mode; -@@ -871,9 +873,11 @@ static int ov01a1s_get_format(struct v4l2_subdev *sd, - #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, cfg, - fmt->pad); --#else -+#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) - fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, - sd_state, fmt->pad); -+#else -+ fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); - #endif - else - ov01a1s_update_pad_format(ov01a1s->cur_mode, &fmt->format); -@@ -929,9 +933,12 @@ static int ov01a1s_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) - #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - ov01a1s_update_pad_format(&supported_modes[0], - v4l2_subdev_get_try_format(sd, fh->pad, 0)); --#else -+#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) - ov01a1s_update_pad_format(&supported_modes[0], - v4l2_subdev_get_try_format(sd, fh->state, 0)); -+#else -+ ov01a1s_update_pad_format(&supported_modes[0], -+ v4l2_subdev_state_get_format(fh->state, 0)); - #endif - mutex_unlock(&ov01a1s->mutex); - --- -2.43.2 - +2.45.2 -From 80bee1ca899ebfa4126d1e69ea821a2c30aba00c Mon Sep 17 00:00:00 2001 +From 5cdf8b2b159a7cfef03b67b7fd51da967345090c Mon Sep 17 00:00:00 2001 From: Hans de Goede <hdegoede@redhat.com> Date: Mon, 6 Nov 2023 12:33:56 +0100 -Subject: [PATCH 28/33] media: ov01a1s: Remove non upstream iVSC support +Subject: [PATCH 2/3] media: ov01a1s: Remove non upstream iVSC support Signed-off-by: Hans de Goede <hdegoede@redhat.com> --- @@ -17557,7 +1257,7 @@ Signed-off-by: Hans de Goede <hdegoede@redhat.com> 1 file changed, 71 deletions(-) diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c -index 923b12b2a948..22b406bdeae9 100644 +index 0dcce8b492b4..c97c1a661022 100644 --- a/drivers/media/i2c/ov01a1s.c +++ b/drivers/media/i2c/ov01a1s.c @@ -17,9 +17,6 @@ @@ -17683,7 +1383,7 @@ index 923b12b2a948..22b406bdeae9 100644 return ret; } -@@ -1051,18 +992,6 @@ static int ov01a1s_parse_power(struct ov01a1s *ov01a1s) +@@ -1044,18 +985,6 @@ static int ov01a1s_parse_power(struct ov01a1s *ov01a1s) { int ret = 0; @@ -17703,375 +1403,61 @@ index 923b12b2a948..22b406bdeae9 100644 ret = ov01a1s_parse_gpio(ov01a1s); #elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) -- -2.43.2 - - -From e624515c64d782b452a4676c1e117815267559ae Mon Sep 17 00:00:00 2001 -From: Hans de Goede <hdegoede@redhat.com> -Date: Tue, 23 Jan 2024 14:58:35 +0100 -Subject: [PATCH 29/33] media: hi556: Return -EPROBE_DEFER if no endpoint is - found - -With ipu bridge, endpoints may only be created when ipu bridge has -initialised. This may happen after the sensor driver has first probed. - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - drivers/media/i2c/hi556.c | 13 +++++++------ - 1 file changed, 7 insertions(+), 6 deletions(-) - -diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c -index 38c77d515786..96bae9914d52 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -1206,8 +1206,13 @@ static int hi556_check_hwcfg(struct device *dev) - int ret = 0; - unsigned int i, j; - -- if (!fwnode) -- return -ENXIO; -+ /* -+ * Sometimes the fwnode graph is initialized by the bridge driver, -+ * wait for this. -+ */ -+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!ep) -+ return -EPROBE_DEFER; - - ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); - if (ret) { -@@ -1220,10 +1225,6 @@ static int hi556_check_hwcfg(struct device *dev) - return -EINVAL; - } - -- ep = fwnode_graph_get_next_endpoint(fwnode, NULL); -- if (!ep) -- return -ENXIO; -- - ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); - fwnode_handle_put(ep); - if (ret) --- -2.43.2 - - -From b127d1003050fb894ea764b600d5f399af413b68 Mon Sep 17 00:00:00 2001 -From: Hans de Goede <hdegoede@redhat.com> -Date: Tue, 23 Jan 2024 14:48:26 +0100 -Subject: [PATCH 30/33] media: hi556: Add support for reset GPIO - -On some ACPI platforms, such as Chromebooks the ACPI methods to -change the power-state (_PS0 and _PS3) fully take care of powering -on/off the sensor. - -On other ACPI platforms, such as e.g. various HP models with IPU6 + -hi556 sensor, the sensor driver must control the reset GPIO itself. - -Add support for having the driver control an optional reset GPIO. - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - drivers/media/i2c/hi556.c | 45 ++++++++++++++++++++++++++++++++++++++- - 1 file changed, 44 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c -index 96bae9914d52..f5a39b83598b 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -4,6 +4,7 @@ - #include <asm/unaligned.h> - #include <linux/acpi.h> - #include <linux/delay.h> -+#include <linux/gpio/consumer.h> - #include <linux/i2c.h> - #include <linux/module.h> - #include <linux/pm_runtime.h> -@@ -633,6 +634,9 @@ struct hi556 { - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *exposure; - -+ /* GPIOs, clocks, etc. */ -+ struct gpio_desc *reset_gpio; -+ - /* Current mode */ - const struct hi556_mode *cur_mode; - -@@ -1276,6 +1280,25 @@ static void hi556_remove(struct i2c_client *client) - mutex_destroy(&hi556->mutex); - } - -+static int hi556_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct hi556 *hi556 = to_hi556(sd); -+ -+ gpiod_set_value_cansleep(hi556->reset_gpio, 1); -+ return 0; -+} -+ -+static int hi556_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct hi556 *hi556 = to_hi556(sd); -+ -+ gpiod_set_value_cansleep(hi556->reset_gpio, 0); -+ usleep_range(5000, 5500); -+ return 0; -+} -+ - static int hi556_probe(struct i2c_client *client) - { - struct hi556 *hi556; -@@ -1295,12 +1318,24 @@ static int hi556_probe(struct i2c_client *client) - - v4l2_i2c_subdev_init(&hi556->sd, client, &hi556_subdev_ops); - -+ hi556->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(hi556->reset_gpio)) -+ return dev_err_probe(&client->dev, PTR_ERR(hi556->reset_gpio), -+ "failed to get reset GPIO\n"); -+ - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { -+ /* Ensure non ACPI managed resources are enabled */ -+ ret = hi556_resume(&client->dev); -+ if (ret) -+ return dev_err_probe(&client->dev, ret, -+ "failed to power on sensor\n"); -+ - ret = hi556_identify_module(hi556); - if (ret) { - dev_err(&client->dev, "failed to find sensor: %d", ret); -- return ret; -+ goto probe_error_power_off; - } - } - -@@ -1345,9 +1380,16 @@ static int hi556_probe(struct i2c_client *client) - v4l2_ctrl_handler_free(hi556->sd.ctrl_handler); - mutex_destroy(&hi556->mutex); - -+probe_error_power_off: -+ if (full_power) -+ hi556_suspend(&client->dev); -+ - return ret; - } - -+static DEFINE_RUNTIME_DEV_PM_OPS(hi556_pm_ops, hi556_suspend, hi556_resume, -+ NULL); -+ - #ifdef CONFIG_ACPI - static const struct acpi_device_id hi556_acpi_ids[] = { - {"INT3537"}, -@@ -1361,6 +1403,7 @@ static struct i2c_driver hi556_i2c_driver = { - .driver = { - .name = "hi556", - .acpi_match_table = ACPI_PTR(hi556_acpi_ids), -+ .pm = pm_sleep_ptr(&hi556_pm_ops), - }, - .probe = hi556_probe, - .remove = hi556_remove, --- -2.43.2 - - -From ee651202ba2ca38da067b5379edd7b4f339cf7a8 Mon Sep 17 00:00:00 2001 -From: Hans de Goede <hdegoede@redhat.com> -Date: Tue, 23 Jan 2024 14:54:22 +0100 -Subject: [PATCH 31/33] media: hi556: Add support for external clock - -On some ACPI platforms, such as Chromebooks the ACPI methods to -change the power-state (_PS0 and _PS3) fully take care of powering -on/off the sensor. - -On other ACPI platforms, such as e.g. various HP models with IPU6 + -hi556 sensor, the sensor driver must control the sensor's clock itself. - -Add support for having the driver control an optional clock. - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - drivers/media/i2c/hi556.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c -index f5a39b83598b..b783e0f56687 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -3,6 +3,7 @@ - - #include <asm/unaligned.h> - #include <linux/acpi.h> -+#include <linux/clk.h> - #include <linux/delay.h> - #include <linux/gpio/consumer.h> - #include <linux/i2c.h> -@@ -636,6 +637,7 @@ struct hi556 { - - /* GPIOs, clocks, etc. */ - struct gpio_desc *reset_gpio; -+ struct clk *clk; - - /* Current mode */ - const struct hi556_mode *cur_mode; -@@ -1286,6 +1288,7 @@ static int hi556_suspend(struct device *dev) - struct hi556 *hi556 = to_hi556(sd); - - gpiod_set_value_cansleep(hi556->reset_gpio, 1); -+ clk_disable_unprepare(hi556->clk); - return 0; - } - -@@ -1293,6 +1296,11 @@ static int hi556_resume(struct device *dev) - { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct hi556 *hi556 = to_hi556(sd); -+ int ret; -+ -+ ret = clk_prepare_enable(hi556->clk); -+ if (ret) -+ return ret; - - gpiod_set_value_cansleep(hi556->reset_gpio, 0); - usleep_range(5000, 5500); -@@ -1324,6 +1332,11 @@ static int hi556_probe(struct i2c_client *client) - return dev_err_probe(&client->dev, PTR_ERR(hi556->reset_gpio), - "failed to get reset GPIO\n"); - -+ hi556->clk = devm_clk_get_optional(&client->dev, "clk"); -+ if (IS_ERR(hi556->clk)) -+ return dev_err_probe(&client->dev, PTR_ERR(hi556->clk), -+ "failed to get clock\n"); -+ - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { - /* Ensure non ACPI managed resources are enabled */ --- -2.43.2 - - -From 16be71996d451b8137ba63070e760448814c11a1 Mon Sep 17 00:00:00 2001 -From: Hans de Goede <hdegoede@redhat.com> -Date: Wed, 24 Jan 2024 18:45:02 +0100 -Subject: [PATCH 32/33] media: hi556: Add support for avdd regulator - -On some ACPI platforms, such as Chromebooks the ACPI methods to -change the power-state (_PS0 and _PS3) fully take care of powering -on/off the sensor. - -On other ACPI platforms, such as e.g. various HP models with IPU6 + -hi556 sensor, the sensor driver must control the avdd regulator itself. - -Add support for having the driver control the sensor's avdd regulator. -Note this relies on the regulator-core providing a dummy regulator -(which it does by default) on platforms where Linux is not aware of -the avdd regulator. - -Signed-off-by: Hans de Goede <hdegoede@redhat.com> ---- - drivers/media/i2c/hi556.c | 24 ++++++++++++++++++++++++ - 1 file changed, 24 insertions(+) - -diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c -index b783e0f56687..5641c249d4b1 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -9,6 +9,7 @@ - #include <linux/i2c.h> - #include <linux/module.h> - #include <linux/pm_runtime.h> -+#include <linux/regulator/consumer.h> - #include <media/v4l2-ctrls.h> - #include <media/v4l2-device.h> - #include <media/v4l2-fwnode.h> -@@ -638,6 +639,7 @@ struct hi556 { - /* GPIOs, clocks, etc. */ - struct gpio_desc *reset_gpio; - struct clk *clk; -+ struct regulator *avdd; - - /* Current mode */ - const struct hi556_mode *cur_mode; -@@ -1286,8 +1288,17 @@ static int hi556_suspend(struct device *dev) - { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct hi556 *hi556 = to_hi556(sd); -+ int ret; - - gpiod_set_value_cansleep(hi556->reset_gpio, 1); -+ -+ ret = regulator_disable(hi556->avdd); -+ if (ret) { -+ dev_err(dev, "failed to disable avdd: %d\n", ret); -+ gpiod_set_value_cansleep(hi556->reset_gpio, 0); -+ return ret; -+ } -+ - clk_disable_unprepare(hi556->clk); - return 0; - } -@@ -1302,6 +1313,13 @@ static int hi556_resume(struct device *dev) - if (ret) - return ret; - -+ ret = regulator_enable(hi556->avdd); -+ if (ret) { -+ dev_err(dev, "failed to enable avdd: %d\n", ret); -+ clk_disable_unprepare(hi556->clk); -+ return ret; -+ } -+ - gpiod_set_value_cansleep(hi556->reset_gpio, 0); - usleep_range(5000, 5500); - return 0; -@@ -1337,6 +1355,12 @@ static int hi556_probe(struct i2c_client *client) - return dev_err_probe(&client->dev, PTR_ERR(hi556->clk), - "failed to get clock\n"); - -+ /* The regulator core will provide a "dummy" regulator if necessary */ -+ hi556->avdd = devm_regulator_get(&client->dev, "avdd"); -+ if (IS_ERR(hi556->avdd)) -+ return dev_err_probe(&client->dev, PTR_ERR(hi556->avdd), -+ "failed to get avdd regulator\n"); -+ - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { - /* Ensure non ACPI managed resources are enabled */ --- -2.43.2 - +2.45.2 -From 6bd6e73829cf264120f629c88c552c4eb59c7eee Mon Sep 17 00:00:00 2001 +From 5ccd06ac4351f3eb8146c1109446fc80aeaa873f Mon Sep 17 00:00:00 2001 From: Florian Klink <flokli@flokli.de> -Date: Sun, 17 Mar 2024 17:07:53 +0200 -Subject: [PATCH 33/33] media: intel/ipu6: fix firmware paths +Date: Sun, 17 Mar 2024 14:24:05 +0200 +Subject: [PATCH 3/3] ov01a1s.c: support Linux 6.8.0 -linux-firmware ships them in intel/ipu, not intel/. +Used https://github.com/intel/ipu6-drivers/pull/213 as an inspiration. --- - drivers/media/pci/intel/ipu6/ipu6.h | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) + drivers/media/i2c/ov01a1s.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) -diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h -index 04e7e7e61ca5..da8a95a9edf8 100644 ---- a/drivers/media/pci/intel/ipu6/ipu6.h -+++ b/drivers/media/pci/intel/ipu6/ipu6.h -@@ -24,10 +24,10 @@ struct ipu6_bus_device; - #define IPU6_NAME "intel-ipu6" - #define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" - --#define IPU6SE_FIRMWARE_NAME "intel/ipu6se_fw.bin" --#define IPU6EP_FIRMWARE_NAME "intel/ipu6ep_fw.bin" --#define IPU6_FIRMWARE_NAME "intel/ipu6_fw.bin" --#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu6epmtl_fw.bin" -+#define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin" -+#define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin" -+#define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin" -+#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" +diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c +index c97c1a661022..22b406bdeae9 100644 +--- a/drivers/media/i2c/ov01a1s.c ++++ b/drivers/media/i2c/ov01a1s.c +@@ -773,8 +773,10 @@ static int ov01a1s_set_format(struct v4l2_subdev *sd, + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; +-#else ++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; ++#else ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + #endif + } else { + ov01a1s->cur_mode = mode; +@@ -812,9 +814,11 @@ static int ov01a1s_get_format(struct v4l2_subdev *sd, + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) + fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, cfg, + fmt->pad); +-#else ++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, + sd_state, fmt->pad); ++#else ++ fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); + #endif + else + ov01a1s_update_pad_format(ov01a1s->cur_mode, &fmt->format); +@@ -870,9 +874,12 @@ static int ov01a1s_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) + ov01a1s_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->pad, 0)); +-#else ++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + ov01a1s_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->state, 0)); ++#else ++ ov01a1s_update_pad_format(&supported_modes[0], ++ v4l2_subdev_state_get_format(fh->state, 0)); + #endif + mutex_unlock(&ov01a1s->mutex); - enum ipu6_version { - IPU6_VER_INVALID = 0, -- -2.43.2 +2.45.2 |