media updates for v6.10-rc1

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+QmuaPwR3wnBdVwACF8+vY7k4RUFAmZFpvgACgkQCF8+vY7k
 4RXffg//UOFGd12GwhBtkU1a3cBqT1DAUG8GRnmhLnGypRaiP7ypRhI/LV1ZZ0SQ
 vjKuDuXrbk+JJ4hxNTH8GoisYpnRqqC2vIm5cnjCiMxN/pY/GkzPm7MU5zEhuWMB
 Rtz5RS4UrTtpJ95XxuDhXY5rRb3uPXMF2LUHLUbYq3IoUGz8x/ta1aKE56B35vY+
 jDg9JQugR1ciIf0OL7kvDJJfDUKkGGsr/u4gRWBxntYHtVMdUJXso3tYa78F1mBX
 oTWKc8IFms1JgA7NdDnKttOCO0Ykb0IJxE0qO094xuOPW50wLsLByJXdxJtOBj/Q
 iLvSIVrk//U+re0j6xLJgKES6ldZvDKn5AU3O22lbm9cgeXrbONIHQOSqLumYPCi
 HLnuc0eq4oED1UHj695pNyjgigUmZL9mDMB31AU92r0pfOKpGFRnexT1tyhqFonN
 88HMKInudnLsE7lVPzbUSVZxJfhOFj7jf8LILnRzqzy0HOD7te5KhxdjxtBmXvoN
 lpQ3Cs+i/n3Fe510mO0rcpeR73nYkNnX7EoJWOjojCK+Cz7/GnXICF53T0yAYANA
 W6ZGKNCEEgs8ce6dFrRG33jv0I8b/u6L5BVuWT/Ndam+KwMw59OjKlNPDiTvtwSR
 OZDL9eifturMuMUe0HT6k6k3u6VYWWjn2cvMFHg4g7Y6JOrllfQ=
 =JM5r
 -----END PGP SIGNATURE-----

Merge tag 'media/v6.10-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media

Pull media updates from Mauro Carvalho Chehab:

 - New V4L2 ioctl VIDIOC_REMOVE_BUFS

 - experimental support for using generic metaformats on V4L2 core

 - New drivers: Intel IPU6 controller driver, Broadcom BCM283x/BCM271x

 - More cleanups at atomisp driver

 - Usual bunch of driver cleanups, improvements and fixes

* tag 'media/v6.10-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (328 commits)
  media: bcm2835-unicam: Depend on COMMON_CLK
  Revert "media: v4l2-ctrls: show all owned controls in log_status"
  media: ov2740: Ensure proper reset sequence on probe()
  media: intel/ipu6: Don't print user-triggerable errors to kernel log
  media: bcm2835-unicam: Fix driver path in MAINTAINERS
  media: bcm2835-unicam: Fix a NULL vs IS_ERR() check
  media: bcm2835-unicam: Do not print error when irq not found
  media: bcm2835-unicam: Do not replace IRQ retcode during probe
  media: bcm2835-unicam: Convert to platform remove callback returning void
  media: media: intel/ipu6: Fix spelling mistake "remappinp" -> "remapping"
  media: intel/ipu6: explicitly include vmalloc.h
  media: cec.h: Fix kerneldoc
  media: uvcvideo: Refactor iterators
  media: v4l: async: refactor v4l2_async_create_ancillary_links
  media: intel/ipu6: Don't re-allocate memory for firmware
  media: dvb-frontends: tda10048: Fix integer overflow
  media: tc358746: Use the correct div_ function
  media: i2c: st-mipid02: Use the correct div function
  media: tegra-vde: Refactor timeout handling
  media: stk1160: Use min macro
  ...
This commit is contained in:
Linus Torvalds 2024-05-16 08:45:44 -07:00
commit 6fd600d742
307 changed files with 22914 additions and 5434 deletions

View File

@ -0,0 +1,161 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
========================================================
Intel Image Processing Unit 6 (IPU6) Input System driver
========================================================
Copyright |copy| 2023--2024 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 SoCs but not in all SKUs:
* Tiger Lake
* Jasper Lake
* Alder Lake
* Raptor Lake
* Meteor Lake
Intel IPU6 is made up of two components - Input System (ISYS) and Processing
System (PSYS).
The Input System mainly works as MIPI CSI-2 receiver which receives and
processes the image 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 CSI-2 D-PHY, 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 as well as video nodes.
.. kernel-figure:: ipu6_isys_graph.svg
:alt: ipu6 isys media graph with multiple streams support
IPU6 ISYS media graph with multiple streams support
The graph has been produced using the following command:
.. code-block:: none
fdp -Gsplines=true -Tsvg < dot > dot.svg
Capturing frames with 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 CSI-2 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/media-ctl.git
.. [#f2] https://git.ideasonboard.org/yavta.git

View File

@ -0,0 +1,548 @@
<?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.43.0 (0)
-->
<!-- Title: board Pages: 1 -->
<svg width="1703pt" height="1473pt"
viewBox="0.00 0.00 1703.00 1473.00" 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 1469)">
<title>board</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1469 1699,-1469 1699,4 -4,4"/>
<!-- n00000001 -->
<g id="node1" class="node">
<title>n00000001</title>
<polygon fill="yellow" stroke="black" points="832.99,-750.08 629.99,-750.08 629.99,-712.08 832.99,-712.08 832.99,-750.08"/>
<text text-anchor="middle" x="731.49" y="-734.88" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 0</text>
<text text-anchor="middle" x="731.49" y="-719.88" font-family="Times,serif" font-size="14.00">/dev/video0</text>
</g>
<!-- n00000005 -->
<g id="node2" class="node">
<title>n00000005</title>
<polygon fill="yellow" stroke="black" points="1396.59,-771.88 1193.59,-771.88 1193.59,-733.88 1396.59,-733.88 1396.59,-771.88"/>
<text text-anchor="middle" x="1295.09" y="-756.68" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 1</text>
<text text-anchor="middle" x="1295.09" y="-741.68" font-family="Times,serif" font-size="14.00">/dev/video1</text>
</g>
<!-- n00000009 -->
<g id="node3" class="node">
<title>n00000009</title>
<polygon fill="yellow" stroke="black" points="1118.52,-690.47 915.52,-690.47 915.52,-652.47 1118.52,-652.47 1118.52,-690.47"/>
<text text-anchor="middle" x="1017.02" y="-675.27" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 2</text>
<text text-anchor="middle" x="1017.02" y="-660.27" font-family="Times,serif" font-size="14.00">/dev/video2</text>
</g>
<!-- n0000000d -->
<g id="node4" class="node">
<title>n0000000d</title>
<polygon fill="yellow" stroke="black" points="1105.89,-838.84 902.89,-838.84 902.89,-800.84 1105.89,-800.84 1105.89,-838.84"/>
<text text-anchor="middle" x="1004.39" y="-823.64" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 3</text>
<text text-anchor="middle" x="1004.39" y="-808.64" font-family="Times,serif" font-size="14.00">/dev/video3</text>
</g>
<!-- n00000011 -->
<g id="node5" class="node">
<title>n00000011</title>
<polygon fill="yellow" stroke="black" points="1279.22,-992.95 1076.22,-992.95 1076.22,-954.95 1279.22,-954.95 1279.22,-992.95"/>
<text text-anchor="middle" x="1177.72" y="-977.75" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 4</text>
<text text-anchor="middle" x="1177.72" y="-962.75" font-family="Times,serif" font-size="14.00">/dev/video4</text>
</g>
<!-- n00000015 -->
<g id="node6" class="node">
<title>n00000015</title>
<polygon fill="yellow" stroke="black" points="939.18,-984.91 736.18,-984.91 736.18,-946.91 939.18,-946.91 939.18,-984.91"/>
<text text-anchor="middle" x="837.68" y="-969.71" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 5</text>
<text text-anchor="middle" x="837.68" y="-954.71" font-family="Times,serif" font-size="14.00">/dev/video5</text>
</g>
<!-- n00000019 -->
<g id="node7" class="node">
<title>n00000019</title>
<polygon fill="yellow" stroke="black" points="957.87,-527.99 754.87,-527.99 754.87,-489.99 957.87,-489.99 957.87,-527.99"/>
<text text-anchor="middle" x="856.37" y="-512.79" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 6</text>
<text text-anchor="middle" x="856.37" y="-497.79" font-family="Times,serif" font-size="14.00">/dev/video6</text>
</g>
<!-- n0000001d -->
<g id="node8" class="node">
<title>n0000001d</title>
<polygon fill="yellow" stroke="black" points="1291.02,-542.15 1088.02,-542.15 1088.02,-504.15 1291.02,-504.15 1291.02,-542.15"/>
<text text-anchor="middle" x="1189.52" y="-526.95" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 7</text>
<text text-anchor="middle" x="1189.52" y="-511.95" font-family="Times,serif" font-size="14.00">/dev/video7</text>
</g>
<!-- n00000021 -->
<g id="node9" class="node">
<title>n00000021</title>
<polygon fill="yellow" stroke="black" points="202.74,-611.46 -0.26,-611.46 -0.26,-573.46 202.74,-573.46 202.74,-611.46"/>
<text text-anchor="middle" x="101.24" y="-596.26" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 8</text>
<text text-anchor="middle" x="101.24" y="-581.26" font-family="Times,serif" font-size="14.00">/dev/video8</text>
</g>
<!-- n00000025 -->
<g id="node10" class="node">
<title>n00000025</title>
<polygon fill="yellow" stroke="black" points="764.86,-637.89 561.86,-637.89 561.86,-599.89 764.86,-599.89 764.86,-637.89"/>
<text text-anchor="middle" x="663.36" y="-622.69" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 9</text>
<text text-anchor="middle" x="663.36" y="-607.69" font-family="Times,serif" font-size="14.00">/dev/video9</text>
</g>
<!-- n00000029 -->
<g id="node11" class="node">
<title>n00000029</title>
<polygon fill="yellow" stroke="black" points="358.62,-519.5 146.62,-519.5 146.62,-481.5 358.62,-481.5 358.62,-519.5"/>
<text text-anchor="middle" x="252.62" y="-504.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 10</text>
<text text-anchor="middle" x="252.62" y="-489.3" font-family="Times,serif" font-size="14.00">/dev/video10</text>
</g>
<!-- n0000002d -->
<g id="node12" class="node">
<title>n0000002d</title>
<polygon fill="yellow" stroke="black" points="481.4,-662.59 269.4,-662.59 269.4,-624.59 481.4,-624.59 481.4,-662.59"/>
<text text-anchor="middle" x="375.4" y="-647.39" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 11</text>
<text text-anchor="middle" x="375.4" y="-632.39" font-family="Times,serif" font-size="14.00">/dev/video11</text>
</g>
<!-- n00000031 -->
<g id="node13" class="node">
<title>n00000031</title>
<polygon fill="yellow" stroke="black" points="637.17,-837.47 425.17,-837.47 425.17,-799.47 637.17,-799.47 637.17,-837.47"/>
<text text-anchor="middle" x="531.17" y="-822.27" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 12</text>
<text text-anchor="middle" x="531.17" y="-807.27" font-family="Times,serif" font-size="14.00">/dev/video12</text>
</g>
<!-- n00000035 -->
<g id="node14" class="node">
<title>n00000035</title>
<polygon fill="yellow" stroke="black" points="337.75,-833.67 125.75,-833.67 125.75,-795.67 337.75,-795.67 337.75,-833.67"/>
<text text-anchor="middle" x="231.75" y="-818.47" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 13</text>
<text text-anchor="middle" x="231.75" y="-803.47" font-family="Times,serif" font-size="14.00">/dev/video13</text>
</g>
<!-- n00000039 -->
<g id="node15" class="node">
<title>n00000039</title>
<polygon fill="yellow" stroke="black" points="393.07,-317.96 181.07,-317.96 181.07,-279.96 393.07,-279.96 393.07,-317.96"/>
<text text-anchor="middle" x="287.07" y="-302.76" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 14</text>
<text text-anchor="middle" x="287.07" y="-287.76" font-family="Times,serif" font-size="14.00">/dev/video14</text>
</g>
<!-- n0000003d -->
<g id="node16" class="node">
<title>n0000003d</title>
<polygon fill="yellow" stroke="black" points="701.46,-391.04 489.46,-391.04 489.46,-353.04 701.46,-353.04 701.46,-391.04"/>
<text text-anchor="middle" x="595.46" y="-375.84" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 15</text>
<text text-anchor="middle" x="595.46" y="-360.84" font-family="Times,serif" font-size="14.00">/dev/video15</text>
</g>
<!-- n00000041 -->
<g id="node17" class="node">
<title>n00000041</title>
<polygon fill="yellow" stroke="black" points="212.45,-1228.8 0.45,-1228.8 0.45,-1190.8 212.45,-1190.8 212.45,-1228.8"/>
<text text-anchor="middle" x="106.45" y="-1213.6" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 16</text>
<text text-anchor="middle" x="106.45" y="-1198.6" font-family="Times,serif" font-size="14.00">/dev/video16</text>
</g>
<!-- n00000045 -->
<g id="node18" class="node">
<title>n00000045</title>
<polygon fill="yellow" stroke="black" points="784.86,-1252.38 572.86,-1252.38 572.86,-1214.38 784.86,-1214.38 784.86,-1252.38"/>
<text text-anchor="middle" x="678.86" y="-1237.18" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 17</text>
<text text-anchor="middle" x="678.86" y="-1222.18" font-family="Times,serif" font-size="14.00">/dev/video17</text>
</g>
<!-- n00000049 -->
<g id="node19" class="node">
<title>n00000049</title>
<polygon fill="yellow" stroke="black" points="503.14,-1169.96 291.14,-1169.96 291.14,-1131.96 503.14,-1131.96 503.14,-1169.96"/>
<text text-anchor="middle" x="397.14" y="-1154.76" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 18</text>
<text text-anchor="middle" x="397.14" y="-1139.76" font-family="Times,serif" font-size="14.00">/dev/video18</text>
</g>
<!-- n0000004d -->
<g id="node20" class="node">
<title>n0000004d</title>
<polygon fill="yellow" stroke="black" points="492.62,-1319.4 280.62,-1319.4 280.62,-1281.4 492.62,-1281.4 492.62,-1319.4"/>
<text text-anchor="middle" x="386.62" y="-1304.2" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 19</text>
<text text-anchor="middle" x="386.62" y="-1289.2" font-family="Times,serif" font-size="14.00">/dev/video19</text>
</g>
<!-- n00000051 -->
<g id="node21" class="node">
<title>n00000051</title>
<polygon fill="yellow" stroke="black" points="680.74,-1464.66 468.74,-1464.66 468.74,-1426.66 680.74,-1426.66 680.74,-1464.66"/>
<text text-anchor="middle" x="574.74" y="-1449.46" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 20</text>
<text text-anchor="middle" x="574.74" y="-1434.46" font-family="Times,serif" font-size="14.00">/dev/video20</text>
</g>
<!-- n00000055 -->
<g id="node22" class="node">
<title>n00000055</title>
<polygon fill="yellow" stroke="black" points="302.42,-1452.56 90.42,-1452.56 90.42,-1414.56 302.42,-1414.56 302.42,-1452.56"/>
<text text-anchor="middle" x="196.42" y="-1437.36" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 21</text>
<text text-anchor="middle" x="196.42" y="-1422.36" font-family="Times,serif" font-size="14.00">/dev/video21</text>
</g>
<!-- n00000059 -->
<g id="node23" class="node">
<title>n00000059</title>
<polygon fill="yellow" stroke="black" points="319.89,-1018.32 107.89,-1018.32 107.89,-980.32 319.89,-980.32 319.89,-1018.32"/>
<text text-anchor="middle" x="213.89" y="-1003.12" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 22</text>
<text text-anchor="middle" x="213.89" y="-988.12" font-family="Times,serif" font-size="14.00">/dev/video22</text>
</g>
<!-- n0000005d -->
<g id="node24" class="node">
<title>n0000005d</title>
<polygon fill="yellow" stroke="black" points="692.62,-1031.39 480.62,-1031.39 480.62,-993.39 692.62,-993.39 692.62,-1031.39"/>
<text text-anchor="middle" x="586.62" y="-1016.19" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 23</text>
<text text-anchor="middle" x="586.62" y="-1001.19" font-family="Times,serif" font-size="14.00">/dev/video23</text>
</g>
<!-- n00000061 -->
<g id="node25" class="node">
<title>n00000061</title>
<polygon fill="yellow" stroke="black" points="1122.45,-248.8 910.45,-248.8 910.45,-210.8 1122.45,-210.8 1122.45,-248.8"/>
<text text-anchor="middle" x="1016.45" y="-233.6" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 24</text>
<text text-anchor="middle" x="1016.45" y="-218.6" font-family="Times,serif" font-size="14.00">/dev/video24</text>
</g>
<!-- n00000065 -->
<g id="node26" class="node">
<title>n00000065</title>
<polygon fill="yellow" stroke="black" points="1694.86,-272.38 1482.86,-272.38 1482.86,-234.38 1694.86,-234.38 1694.86,-272.38"/>
<text text-anchor="middle" x="1588.86" y="-257.18" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 25</text>
<text text-anchor="middle" x="1588.86" y="-242.18" font-family="Times,serif" font-size="14.00">/dev/video25</text>
</g>
<!-- n00000069 -->
<g id="node27" class="node">
<title>n00000069</title>
<polygon fill="yellow" stroke="black" points="1413.14,-189.96 1201.14,-189.96 1201.14,-151.96 1413.14,-151.96 1413.14,-189.96"/>
<text text-anchor="middle" x="1307.14" y="-174.76" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 26</text>
<text text-anchor="middle" x="1307.14" y="-159.76" font-family="Times,serif" font-size="14.00">/dev/video26</text>
</g>
<!-- n0000006d -->
<g id="node28" class="node">
<title>n0000006d</title>
<polygon fill="yellow" stroke="black" points="1402.62,-339.4 1190.62,-339.4 1190.62,-301.4 1402.62,-301.4 1402.62,-339.4"/>
<text text-anchor="middle" x="1296.62" y="-324.2" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 27</text>
<text text-anchor="middle" x="1296.62" y="-309.2" font-family="Times,serif" font-size="14.00">/dev/video27</text>
</g>
<!-- n00000071 -->
<g id="node29" class="node">
<title>n00000071</title>
<polygon fill="yellow" stroke="black" points="1590.74,-484.66 1378.74,-484.66 1378.74,-446.66 1590.74,-446.66 1590.74,-484.66"/>
<text text-anchor="middle" x="1484.74" y="-469.46" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 28</text>
<text text-anchor="middle" x="1484.74" y="-454.46" font-family="Times,serif" font-size="14.00">/dev/video28</text>
</g>
<!-- n00000075 -->
<g id="node30" class="node">
<title>n00000075</title>
<polygon fill="yellow" stroke="black" points="1212.42,-472.56 1000.42,-472.56 1000.42,-434.56 1212.42,-434.56 1212.42,-472.56"/>
<text text-anchor="middle" x="1106.42" y="-457.36" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 29</text>
<text text-anchor="middle" x="1106.42" y="-442.36" font-family="Times,serif" font-size="14.00">/dev/video29</text>
</g>
<!-- n00000079 -->
<g id="node31" class="node">
<title>n00000079</title>
<polygon fill="yellow" stroke="black" points="1229.89,-38.32 1017.89,-38.32 1017.89,-0.32 1229.89,-0.32 1229.89,-38.32"/>
<text text-anchor="middle" x="1123.89" y="-23.12" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 30</text>
<text text-anchor="middle" x="1123.89" y="-8.12" font-family="Times,serif" font-size="14.00">/dev/video30</text>
</g>
<!-- n0000007d -->
<g id="node32" class="node">
<title>n0000007d</title>
<polygon fill="yellow" stroke="black" points="1602.62,-51.39 1390.62,-51.39 1390.62,-13.39 1602.62,-13.39 1602.62,-51.39"/>
<text text-anchor="middle" x="1496.62" y="-36.19" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 31</text>
<text text-anchor="middle" x="1496.62" y="-21.19" font-family="Times,serif" font-size="14.00">/dev/video31</text>
</g>
<!-- n00000081 -->
<g id="node33" class="node">
<title>n00000081</title>
<path fill="green" stroke="black" d="M924.28,-700.28C924.28,-700.28 1108.28,-700.28 1108.28,-700.28 1114.28,-700.28 1120.28,-706.28 1120.28,-712.28 1120.28,-712.28 1120.28,-772.28 1120.28,-772.28 1120.28,-778.28 1114.28,-784.28 1108.28,-784.28 1108.28,-784.28 924.28,-784.28 924.28,-784.28 918.28,-784.28 912.28,-778.28 912.28,-772.28 912.28,-772.28 912.28,-712.28 912.28,-712.28 912.28,-706.28 918.28,-700.28 924.28,-700.28"/>
<text text-anchor="middle" x="1016.28" y="-769.08" font-family="Times,serif" font-size="14.00">0</text>
<polyline fill="none" stroke="black" points="912.28,-761.28 1120.28,-761.28 "/>
<text text-anchor="middle" x="1016.28" y="-746.08" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 0</text>
<text text-anchor="middle" x="1016.28" y="-731.08" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev0</text>
<polyline fill="none" stroke="black" points="912.28,-723.28 1120.28,-723.28 "/>
<text text-anchor="middle" x="925.28" y="-708.08" font-family="Times,serif" font-size="14.00">1</text>
<polyline fill="none" stroke="black" points="938.28,-700.28 938.28,-723.28 "/>
<text text-anchor="middle" x="951.28" y="-708.08" font-family="Times,serif" font-size="14.00">2</text>
<polyline fill="none" stroke="black" points="964.28,-700.28 964.28,-723.28 "/>
<text text-anchor="middle" x="977.28" y="-708.08" font-family="Times,serif" font-size="14.00">3</text>
<polyline fill="none" stroke="black" points="990.28,-700.28 990.28,-723.28 "/>
<text text-anchor="middle" x="1003.28" y="-708.08" font-family="Times,serif" font-size="14.00">4</text>
<polyline fill="none" stroke="black" points="1016.28,-700.28 1016.28,-723.28 "/>
<text text-anchor="middle" x="1029.28" y="-708.08" font-family="Times,serif" font-size="14.00">5</text>
<polyline fill="none" stroke="black" points="1042.28,-700.28 1042.28,-723.28 "/>
<text text-anchor="middle" x="1055.28" y="-708.08" font-family="Times,serif" font-size="14.00">6</text>
<polyline fill="none" stroke="black" points="1068.28,-700.28 1068.28,-723.28 "/>
<text text-anchor="middle" x="1081.28" y="-708.08" font-family="Times,serif" font-size="14.00">7</text>
<polyline fill="none" stroke="black" points="1094.28,-700.28 1094.28,-723.28 "/>
<text text-anchor="middle" x="1107.28" y="-708.08" font-family="Times,serif" font-size="14.00">8</text>
</g>
<!-- n00000081&#45;&gt;n00000001 -->
<g id="edge1" class="edge">
<title>n00000081:port1&#45;&gt;n00000001</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M912.28,-711.28C912.28,-711.28 880.33,-714.78 843.28,-718.84"/>
<polygon fill="black" stroke="black" points="842.81,-715.37 833.25,-719.94 843.57,-722.33 842.81,-715.37"/>
</g>
<!-- n00000081&#45;&gt;n00000005 -->
<g id="edge2" class="edge">
<title>n00000081:port2&#45;&gt;n00000005</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M951.38,-700.28C951.38,-700.28 1086.18,-688.61 1123.48,-697.08 1155.93,-704.45 1158.99,-719.67 1190.39,-730.68 1190.49,-730.71 1190.59,-730.75 1190.69,-730.78"/>
<polygon fill="black" stroke="black" points="1189.45,-734.06 1200.05,-733.86 1191.64,-727.41 1189.45,-734.06"/>
</g>
<!-- n00000081&#45;&gt;n00000009 -->
<g id="edge3" class="edge">
<title>n00000081:port3&#45;&gt;n00000009</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M977.28,-700.28C977.28,-700.28 979.31,-698.81 982.45,-696.54"/>
<polygon fill="black" stroke="black" points="984.7,-699.23 990.74,-690.53 980.59,-693.56 984.7,-699.23"/>
</g>
<!-- n00000081&#45;&gt;n0000000d -->
<g id="edge4" class="edge">
<title>n00000081:port4&#45;&gt;n0000000d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1003.38,-700.26C1003.38,-700.26 916.62,-689.8 909.08,-697.08 880.2,-725.01 885.68,-754.82 909.08,-787.48 910.88,-789.99 918.96,-793.59 929.7,-797.47"/>
<polygon fill="black" stroke="black" points="928.69,-800.82 939.28,-800.79 930.98,-794.21 928.69,-800.82"/>
</g>
<!-- n00000081&#45;&gt;n00000011 -->
<g id="edge5" class="edge">
<title>n00000081:port5&#45;&gt;n00000011</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1029.19,-700.26C1029.19,-700.26 1115.28,-690.56 1123.48,-697.08 1198.37,-756.64 1190.55,-886.51 1182.64,-944.71"/>
<polygon fill="black" stroke="black" points="1179.16,-944.31 1181.18,-954.71 1186.09,-945.32 1179.16,-944.31"/>
</g>
<!-- n00000081&#45;&gt;n00000015 -->
<g id="edge6" class="edge">
<title>n00000081:port6&#45;&gt;n00000015</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1055.18,-700.28C1055.18,-700.28 915.57,-692.2 909.08,-697.08 834.02,-753.51 831.79,-879.34 835.06,-936.56"/>
<polygon fill="black" stroke="black" points="831.58,-936.99 835.74,-946.73 838.56,-936.52 831.58,-936.99"/>
</g>
<!-- n00000081&#45;&gt;n00000019 -->
<g id="edge7" class="edge">
<title>n00000081:port7&#45;&gt;n00000019</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1081.28,-700.28C1081.28,-700.28 916.04,-696.54 912.32,-693.67 864.52,-656.73 856.3,-580.22 855.62,-538.2"/>
<polygon fill="black" stroke="black" points="859.11,-538.05 855.59,-528.06 852.11,-538.07 859.11,-538.05"/>
</g>
<!-- n00000081&#45;&gt;n0000001d -->
<g id="edge8" class="edge">
<title>n00000081:port8&#45;&gt;n0000001d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1107.28,-700.28C1107.28,-700.28 1119.29,-696.23 1121.72,-693.67 1159.76,-653.62 1177.38,-589.6 1184.78,-552.46"/>
<polygon fill="black" stroke="black" points="1188.29,-552.76 1186.69,-542.29 1181.41,-551.47 1188.29,-552.76"/>
</g>
<!-- n0000008b -->
<g id="node34" class="node">
<title>n0000008b</title>
<path fill="green" stroke="black" d="M293.1,-532.08C293.1,-532.08 477.1,-532.08 477.1,-532.08 483.1,-532.08 489.1,-538.08 489.1,-544.08 489.1,-544.08 489.1,-604.08 489.1,-604.08 489.1,-610.08 483.1,-616.08 477.1,-616.08 477.1,-616.08 293.1,-616.08 293.1,-616.08 287.1,-616.08 281.1,-610.08 281.1,-604.08 281.1,-604.08 281.1,-544.08 281.1,-544.08 281.1,-538.08 287.1,-532.08 293.1,-532.08"/>
<text text-anchor="middle" x="385.1" y="-600.88" font-family="Times,serif" font-size="14.00">0</text>
<polyline fill="none" stroke="black" points="281.1,-593.08 489.1,-593.08 "/>
<text text-anchor="middle" x="385.1" y="-577.88" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 1</text>
<text text-anchor="middle" x="385.1" y="-562.88" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev1</text>
<polyline fill="none" stroke="black" points="281.1,-555.08 489.1,-555.08 "/>
<text text-anchor="middle" x="294.1" y="-539.88" font-family="Times,serif" font-size="14.00">1</text>
<polyline fill="none" stroke="black" points="307.1,-532.08 307.1,-555.08 "/>
<text text-anchor="middle" x="320.1" y="-539.88" font-family="Times,serif" font-size="14.00">2</text>
<polyline fill="none" stroke="black" points="333.1,-532.08 333.1,-555.08 "/>
<text text-anchor="middle" x="346.1" y="-539.88" font-family="Times,serif" font-size="14.00">3</text>
<polyline fill="none" stroke="black" points="359.1,-532.08 359.1,-555.08 "/>
<text text-anchor="middle" x="372.1" y="-539.88" font-family="Times,serif" font-size="14.00">4</text>
<polyline fill="none" stroke="black" points="385.1,-532.08 385.1,-555.08 "/>
<text text-anchor="middle" x="398.1" y="-539.88" font-family="Times,serif" font-size="14.00">5</text>
<polyline fill="none" stroke="black" points="411.1,-532.08 411.1,-555.08 "/>
<text text-anchor="middle" x="424.1" y="-539.88" font-family="Times,serif" font-size="14.00">6</text>
<polyline fill="none" stroke="black" points="437.1,-532.08 437.1,-555.08 "/>
<text text-anchor="middle" x="450.1" y="-539.88" font-family="Times,serif" font-size="14.00">7</text>
<polyline fill="none" stroke="black" points="463.1,-532.08 463.1,-555.08 "/>
<text text-anchor="middle" x="476.1" y="-539.88" font-family="Times,serif" font-size="14.00">8</text>
</g>
<!-- n0000008b&#45;&gt;n00000021 -->
<g id="edge9" class="edge">
<title>n0000008b:port1&#45;&gt;n00000021</title>
<path fill="none" stroke="black" d="M281.1,-543.08C281.1,-543.08 240.1,-560.51 205.94,-570.26 205.35,-570.43 204.77,-570.59 204.18,-570.76"/>
<polygon fill="black" stroke="black" points="203.2,-567.39 194.47,-573.39 205.03,-574.15 203.2,-567.39"/>
</g>
<!-- n0000008b&#45;&gt;n00000025 -->
<g id="edge10" class="edge">
<title>n0000008b:port2&#45;&gt;n00000025</title>
<path fill="none" stroke="black" d="M320.2,-532.07C320.2,-532.07 456.9,-514.37 492.3,-528.88 528.42,-543.68 522.86,-571.78 556.11,-594.53"/>
<polygon fill="black" stroke="black" points="554.54,-597.67 564.9,-599.88 558.18,-591.69 554.54,-597.67"/>
</g>
<!-- n0000008b&#45;&gt;n00000029 -->
<g id="edge11" class="edge">
<title>n0000008b:port3&#45;&gt;n00000029</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M346.1,-532.08C346.1,-532.08 333.93,-527.96 318.37,-522.71"/>
<polygon fill="black" stroke="black" points="319.48,-519.39 308.88,-519.5 317.24,-526.02 319.48,-519.39"/>
</g>
<!-- n0000008b&#45;&gt;n0000002d -->
<g id="edge12" class="edge">
<title>n0000008b:port4&#45;&gt;n0000002d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M372.19,-532.05C372.19,-532.05 292.97,-514.3 277.9,-528.88 249.01,-556.8 253.16,-587.62 277.9,-619.28 278.34,-619.85 280.33,-620.69 283.45,-621.71"/>
<polygon fill="black" stroke="black" points="282.71,-625.14 293.29,-624.58 284.67,-618.42 282.71,-625.14"/>
</g>
<!-- n0000008b&#45;&gt;n00000031 -->
<g id="edge13" class="edge">
<title>n0000008b:port5&#45;&gt;n00000031</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M398,-532.05C398,-532.05 476.28,-515.34 492.3,-528.88 568.49,-593.29 550.55,-729.67 538.14,-789.41"/>
<polygon fill="black" stroke="black" points="534.69,-788.79 535.99,-799.31 541.53,-790.28 534.69,-788.79"/>
</g>
<!-- n0000008b&#45;&gt;n00000035 -->
<g id="edge14" class="edge">
<title>n0000008b:port6&#45;&gt;n00000035</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M424,-532.07C424,-532.07 290.37,-518.48 277.9,-528.88 202.27,-591.86 215.34,-725.69 225.66,-785.15"/>
<polygon fill="black" stroke="black" points="222.29,-786.14 227.54,-795.35 229.17,-784.88 222.29,-786.14"/>
</g>
<!-- n0000008b&#45;&gt;n00000039 -->
<g id="edge15" class="edge">
<title>n0000008b:port7&#45;&gt;n00000039</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M450.1,-532.08C450.1,-532.08 395.22,-528.13 383.45,-518.65 375.46,-512.21 322.64,-385.46 298.76,-327.47"/>
<polygon fill="black" stroke="black" points="301.96,-326.05 294.92,-318.14 295.49,-328.72 301.96,-326.05"/>
</g>
<!-- n0000008b&#45;&gt;n0000003d -->
<g id="edge16" class="edge">
<title>n0000008b:port8&#45;&gt;n0000003d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M476.1,-532.08C476.1,-532.08 522.37,-522.39 526.85,-518.65 563.15,-488.33 581.38,-434.52 589.6,-401.2"/>
<polygon fill="black" stroke="black" points="593.08,-401.69 591.93,-391.16 586.26,-400.11 593.08,-401.69"/>
</g>
<!-- n00000095 -->
<g id="node35" class="node">
<title>n00000095</title>
<path fill="green" stroke="black" d="M301.38,-1180.11C301.38,-1180.11 485.38,-1180.11 485.38,-1180.11 491.38,-1180.11 497.38,-1186.11 497.38,-1192.11 497.38,-1192.11 497.38,-1252.11 497.38,-1252.11 497.38,-1258.11 491.38,-1264.11 485.38,-1264.11 485.38,-1264.11 301.38,-1264.11 301.38,-1264.11 295.38,-1264.11 289.38,-1258.11 289.38,-1252.11 289.38,-1252.11 289.38,-1192.11 289.38,-1192.11 289.38,-1186.11 295.38,-1180.11 301.38,-1180.11"/>
<text text-anchor="middle" x="393.38" y="-1248.91" font-family="Times,serif" font-size="14.00">0</text>
<polyline fill="none" stroke="black" points="289.38,-1241.11 497.38,-1241.11 "/>
<text text-anchor="middle" x="393.38" y="-1225.91" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 2</text>
<text text-anchor="middle" x="393.38" y="-1210.91" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev2</text>
<polyline fill="none" stroke="black" points="289.38,-1203.11 497.38,-1203.11 "/>
<text text-anchor="middle" x="302.38" y="-1187.91" font-family="Times,serif" font-size="14.00">1</text>
<polyline fill="none" stroke="black" points="315.38,-1180.11 315.38,-1203.11 "/>
<text text-anchor="middle" x="328.38" y="-1187.91" font-family="Times,serif" font-size="14.00">2</text>
<polyline fill="none" stroke="black" points="341.38,-1180.11 341.38,-1203.11 "/>
<text text-anchor="middle" x="354.38" y="-1187.91" font-family="Times,serif" font-size="14.00">3</text>
<polyline fill="none" stroke="black" points="367.38,-1180.11 367.38,-1203.11 "/>
<text text-anchor="middle" x="380.38" y="-1187.91" font-family="Times,serif" font-size="14.00">4</text>
<polyline fill="none" stroke="black" points="393.38,-1180.11 393.38,-1203.11 "/>
<text text-anchor="middle" x="406.38" y="-1187.91" font-family="Times,serif" font-size="14.00">5</text>
<polyline fill="none" stroke="black" points="419.38,-1180.11 419.38,-1203.11 "/>
<text text-anchor="middle" x="432.38" y="-1187.91" font-family="Times,serif" font-size="14.00">6</text>
<polyline fill="none" stroke="black" points="445.38,-1180.11 445.38,-1203.11 "/>
<text text-anchor="middle" x="458.38" y="-1187.91" font-family="Times,serif" font-size="14.00">7</text>
<polyline fill="none" stroke="black" points="471.38,-1180.11 471.38,-1203.11 "/>
<text text-anchor="middle" x="484.38" y="-1187.91" font-family="Times,serif" font-size="14.00">8</text>
</g>
<!-- n00000095&#45;&gt;n00000041 -->
<g id="edge17" class="edge">
<title>n00000095:port1&#45;&gt;n00000041</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M289.38,-1191.11C289.38,-1191.11 258.94,-1194.22 222.89,-1197.91"/>
<polygon fill="black" stroke="black" points="222.19,-1194.46 212.6,-1198.96 222.9,-1201.42 222.19,-1194.46"/>
</g>
<!-- n00000095&#45;&gt;n00000045 -->
<g id="edge18" class="edge">
<title>n00000095:port2&#45;&gt;n00000045</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M328.48,-1180.11C328.48,-1180.11 463.26,-1168.53 500.58,-1176.91 534.02,-1184.43 537.24,-1200.06 569.66,-1211.18 569.76,-1211.22 569.86,-1211.25 569.96,-1211.29"/>
<polygon fill="black" stroke="black" points="568.86,-1214.61 579.45,-1214.34 571,-1207.95 568.86,-1214.61"/>
</g>
<!-- n00000095&#45;&gt;n00000049 -->
<g id="edge19" class="edge">
<title>n00000095:port3&#45;&gt;n00000049</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M354.38,-1180.11C354.38,-1180.11 356.8,-1178.46 360.49,-1175.94"/>
<polygon fill="black" stroke="black" points="362.56,-1178.77 368.86,-1170.24 358.62,-1172.98 362.56,-1178.77"/>
</g>
<!-- n00000095&#45;&gt;n0000004d -->
<g id="edge20" class="edge">
<title>n00000095:port4&#45;&gt;n0000004d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M380.47,-1180.09C380.47,-1180.09 293.71,-1169.63 286.18,-1176.91 257.29,-1204.84 262.63,-1234.76 286.18,-1267.31 288.16,-1270.05 297.33,-1273.96 309.38,-1278.13"/>
<polygon fill="black" stroke="black" points="308.49,-1281.53 319.09,-1281.36 310.7,-1274.88 308.49,-1281.53"/>
</g>
<!-- n00000095&#45;&gt;n00000051 -->
<g id="edge21" class="edge">
<title>n00000095:port5&#45;&gt;n00000051</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M406.28,-1180.09C406.28,-1180.09 492.13,-1170.7 500.58,-1176.91 576.41,-1232.66 579.83,-1358.79 577.09,-1416.2"/>
<polygon fill="black" stroke="black" points="573.59,-1416.23 576.51,-1426.41 580.58,-1416.63 573.59,-1416.23"/>
</g>
<!-- n00000095&#45;&gt;n00000055 -->
<g id="edge22" class="edge">
<title>n00000095:port6&#45;&gt;n00000055</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M432.28,-1180.11C432.28,-1180.11 292.85,-1172.29 286.18,-1176.91 211.26,-1228.86 198.3,-1348.49 196.45,-1404.12"/>
<polygon fill="black" stroke="black" points="192.94,-1404.28 196.21,-1414.36 199.94,-1404.44 192.94,-1404.28"/>
</g>
<!-- n00000095&#45;&gt;n00000059 -->
<g id="edge23" class="edge">
<title>n00000095:port7&#45;&gt;n00000059</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M458.38,-1180.11C458.38,-1180.11 291.84,-1175.85 287.94,-1173.16 239.87,-1139.96 222.85,-1068.83 216.94,-1028.6"/>
<polygon fill="black" stroke="black" points="220.39,-1028.06 215.6,-1018.61 213.46,-1028.98 220.39,-1028.06"/>
</g>
<!-- n00000095&#45;&gt;n0000005d -->
<g id="edge24" class="edge">
<title>n00000095:port8&#45;&gt;n0000005d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M484.38,-1180.11C484.38,-1180.11 502.45,-1176.49 506.34,-1173.16 547.25,-1138.2 569.47,-1077.38 579.62,-1041.41"/>
<polygon fill="black" stroke="black" points="583.06,-1042.09 582.28,-1031.53 576.3,-1040.27 583.06,-1042.09"/>
</g>
<!-- n0000009f -->
<g id="node36" class="node">
<title>n0000009f</title>
<path fill="green" stroke="black" d="M1211.38,-200.11C1211.38,-200.11 1395.38,-200.11 1395.38,-200.11 1401.38,-200.11 1407.38,-206.11 1407.38,-212.11 1407.38,-212.11 1407.38,-272.11 1407.38,-272.11 1407.38,-278.11 1401.38,-284.11 1395.38,-284.11 1395.38,-284.11 1211.38,-284.11 1211.38,-284.11 1205.38,-284.11 1199.38,-278.11 1199.38,-272.11 1199.38,-272.11 1199.38,-212.11 1199.38,-212.11 1199.38,-206.11 1205.38,-200.11 1211.38,-200.11"/>
<text text-anchor="middle" x="1303.38" y="-268.91" font-family="Times,serif" font-size="14.00">0</text>
<polyline fill="none" stroke="black" points="1199.38,-261.11 1407.38,-261.11 "/>
<text text-anchor="middle" x="1303.38" y="-245.91" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 3</text>
<text text-anchor="middle" x="1303.38" y="-230.91" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev3</text>
<polyline fill="none" stroke="black" points="1199.38,-223.11 1407.38,-223.11 "/>
<text text-anchor="middle" x="1212.38" y="-207.91" font-family="Times,serif" font-size="14.00">1</text>
<polyline fill="none" stroke="black" points="1225.38,-200.11 1225.38,-223.11 "/>
<text text-anchor="middle" x="1238.38" y="-207.91" font-family="Times,serif" font-size="14.00">2</text>
<polyline fill="none" stroke="black" points="1251.38,-200.11 1251.38,-223.11 "/>
<text text-anchor="middle" x="1264.38" y="-207.91" font-family="Times,serif" font-size="14.00">3</text>
<polyline fill="none" stroke="black" points="1277.38,-200.11 1277.38,-223.11 "/>
<text text-anchor="middle" x="1290.38" y="-207.91" font-family="Times,serif" font-size="14.00">4</text>
<polyline fill="none" stroke="black" points="1303.38,-200.11 1303.38,-223.11 "/>
<text text-anchor="middle" x="1316.38" y="-207.91" font-family="Times,serif" font-size="14.00">5</text>
<polyline fill="none" stroke="black" points="1329.38,-200.11 1329.38,-223.11 "/>
<text text-anchor="middle" x="1342.38" y="-207.91" font-family="Times,serif" font-size="14.00">6</text>
<polyline fill="none" stroke="black" points="1355.38,-200.11 1355.38,-223.11 "/>
<text text-anchor="middle" x="1368.38" y="-207.91" font-family="Times,serif" font-size="14.00">7</text>
<polyline fill="none" stroke="black" points="1381.38,-200.11 1381.38,-223.11 "/>
<text text-anchor="middle" x="1394.38" y="-207.91" font-family="Times,serif" font-size="14.00">8</text>
</g>
<!-- n0000009f&#45;&gt;n00000061 -->
<g id="edge25" class="edge">
<title>n0000009f:port1&#45;&gt;n00000061</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1199.38,-211.11C1199.38,-211.11 1168.94,-214.22 1132.89,-217.91"/>
<polygon fill="black" stroke="black" points="1132.19,-214.46 1122.6,-218.96 1132.9,-221.42 1132.19,-214.46"/>
</g>
<!-- n0000009f&#45;&gt;n00000065 -->
<g id="edge26" class="edge">
<title>n0000009f:port2&#45;&gt;n00000065</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1238.48,-200.11C1238.48,-200.11 1373.26,-188.53 1410.58,-196.91 1444.02,-204.43 1447.24,-220.06 1479.66,-231.18 1479.76,-231.22 1479.86,-231.25 1479.96,-231.29"/>
<polygon fill="black" stroke="black" points="1478.86,-234.61 1489.45,-234.34 1481,-227.95 1478.86,-234.61"/>
</g>
<!-- n0000009f&#45;&gt;n00000069 -->
<g id="edge27" class="edge">
<title>n0000009f:port3&#45;&gt;n00000069</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1264.38,-200.11C1264.38,-200.11 1266.8,-198.46 1270.49,-195.94"/>
<polygon fill="black" stroke="black" points="1272.56,-198.77 1278.86,-190.24 1268.62,-192.98 1272.56,-198.77"/>
</g>
<!-- n0000009f&#45;&gt;n0000006d -->
<g id="edge28" class="edge">
<title>n0000009f:port4&#45;&gt;n0000006d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1290.47,-200.09C1290.47,-200.09 1203.71,-189.63 1196.18,-196.91 1167.29,-224.84 1172.63,-254.76 1196.18,-287.31 1198.16,-290.05 1207.33,-293.96 1219.38,-298.13"/>
<polygon fill="black" stroke="black" points="1218.49,-301.53 1229.09,-301.36 1220.7,-294.88 1218.49,-301.53"/>
</g>
<!-- n0000009f&#45;&gt;n00000071 -->
<g id="edge29" class="edge">
<title>n0000009f:port5&#45;&gt;n00000071</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1316.28,-200.09C1316.28,-200.09 1402.13,-190.7 1410.58,-196.91 1486.41,-252.66 1489.83,-378.79 1487.09,-436.2"/>
<polygon fill="black" stroke="black" points="1483.59,-436.23 1486.51,-446.41 1490.58,-436.63 1483.59,-436.23"/>
</g>
<!-- n0000009f&#45;&gt;n00000075 -->
<g id="edge30" class="edge">
<title>n0000009f:port6&#45;&gt;n00000075</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1342.28,-200.11C1342.28,-200.11 1202.85,-192.29 1196.18,-196.91 1121.26,-248.86 1108.3,-368.49 1106.45,-424.12"/>
<polygon fill="black" stroke="black" points="1102.94,-424.28 1106.21,-434.36 1109.94,-424.44 1102.94,-424.28"/>
</g>
<!-- n0000009f&#45;&gt;n00000079 -->
<g id="edge31" class="edge">
<title>n0000009f:port7&#45;&gt;n00000079</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1368.38,-200.11C1368.38,-200.11 1201.84,-195.85 1197.94,-193.16 1149.87,-159.96 1132.85,-88.83 1126.94,-48.6"/>
<polygon fill="black" stroke="black" points="1130.39,-48.06 1125.6,-38.61 1123.46,-48.98 1130.39,-48.06"/>
</g>
<!-- n0000009f&#45;&gt;n0000007d -->
<g id="edge32" class="edge">
<title>n0000009f:port8&#45;&gt;n0000007d</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M1394.38,-200.11C1394.38,-200.11 1412.45,-196.49 1416.34,-193.16 1457.25,-158.2 1479.47,-97.38 1489.62,-61.41"/>
<polygon fill="black" stroke="black" points="1493.06,-62.09 1492.28,-51.53 1486.3,-60.27 1493.06,-62.09"/>
</g>
<!-- n000000e9 -->
<g id="node37" class="node">
<title>n000000e9</title>
<path fill="green" stroke="black" d="M398.65,-431.45C398.65,-431.45 511.65,-431.45 511.65,-431.45 517.65,-431.45 523.65,-437.45 523.65,-443.45 523.65,-443.45 523.65,-503.45 523.65,-503.45 523.65,-509.45 517.65,-515.45 511.65,-515.45 511.65,-515.45 398.65,-515.45 398.65,-515.45 392.65,-515.45 386.65,-509.45 386.65,-503.45 386.65,-503.45 386.65,-443.45 386.65,-443.45 386.65,-437.45 392.65,-431.45 398.65,-431.45"/>
<text text-anchor="middle" x="420.65" y="-500.25" font-family="Times,serif" font-size="14.00">1</text>
<polyline fill="none" stroke="black" points="454.65,-492.45 454.65,-515.45 "/>
<text text-anchor="middle" x="489.15" y="-500.25" font-family="Times,serif" font-size="14.00">2</text>
<polyline fill="none" stroke="black" points="386.65,-492.45 523.65,-492.45 "/>
<text text-anchor="middle" x="455.15" y="-477.25" font-family="Times,serif" font-size="14.00">ov2740 4&#45;0036</text>
<text text-anchor="middle" x="455.15" y="-462.25" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev4</text>
<polyline fill="none" stroke="black" points="386.65,-454.45 523.65,-454.45 "/>
<text text-anchor="middle" x="455.15" y="-439.25" font-family="Times,serif" font-size="14.00">0</text>
</g>
<!-- n000000e9&#45;&gt;n0000008b -->
<g id="edge33" class="edge">
<title>n000000e9:port0&#45;&gt;n0000008b:port0</title>
<path fill="none" stroke="black" stroke-width="2" d="M386.14,-442.55C386.14,-442.55 361.11,-493.23 383.45,-518.65 391.47,-527.78 484.31,-519.72 492.3,-528.88 508.64,-547.6 499.26,-579.87 493.12,-595.68"/>
<polygon fill="black" stroke="black" stroke-width="2" points="489.86,-594.41 489.11,-604.98 496.29,-597.19 489.86,-594.41"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,8 +1,10 @@
.. SPDX-License-Identifier: GPL-2.0 .. SPDX-License-Identifier: GPL-2.0
==================== The mgb4 driver
mgb4 sysfs interface ===============
====================
sysfs interface
---------------
The mgb4 driver provides a sysfs interface, that is used to configure video The mgb4 driver provides a sysfs interface, that is used to configure video
stream related parameters (some of them must be set properly before the v4l2 stream related parameters (some of them must be set properly before the v4l2
@ -12,9 +14,8 @@ There are two types of parameters - global / PCI card related, found under
``/sys/class/video4linux/videoX/device`` and module specific found under ``/sys/class/video4linux/videoX/device`` and module specific found under
``/sys/class/video4linux/videoX``. ``/sys/class/video4linux/videoX``.
Global (PCI card) parameters Global (PCI card) parameters
============================ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**module_type** (R): **module_type** (R):
Module type. Module type.
@ -42,9 +43,8 @@ Global (PCI card) parameters
where each component is a 8b number. where each component is a 8b number.
Common FPDL3/GMSL input parameters Common FPDL3/GMSL input parameters
================================== ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**input_id** (R): **input_id** (R):
Input number ID, zero based. Input number ID, zero based.
@ -190,9 +190,8 @@ Common FPDL3/GMSL input parameters
*Note: This parameter can not be changed while the input v4l2 device is *Note: This parameter can not be changed while the input v4l2 device is
open.* open.*
Common FPDL3/GMSL output parameters Common FPDL3/GMSL output parameters
=================================== ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**output_id** (R): **output_id** (R):
Output number ID, zero based. Output number ID, zero based.
@ -282,9 +281,8 @@ Common FPDL3/GMSL output parameters
Number of video lines between the end of the last valid pixel line (marked Number of video lines between the end of the last valid pixel line (marked
by DE=1) and assertion of the VSYNC signal. The default value is 2. by DE=1) and assertion of the VSYNC signal. The default value is 2.
FPDL3 specific input parameters FPDL3 specific input parameters
=============================== ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**fpdl3_input_width** (RW): **fpdl3_input_width** (RW):
Number of deserializer input lines. Number of deserializer input lines.
@ -294,7 +292,7 @@ FPDL3 specific input parameters
| 2 - dual | 2 - dual
FPDL3 specific output parameters FPDL3 specific output parameters
================================ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**fpdl3_output_width** (RW): **fpdl3_output_width** (RW):
Number of serializer output lines. Number of serializer output lines.
@ -304,7 +302,7 @@ FPDL3 specific output parameters
| 2 - dual | 2 - dual
GMSL specific input parameters GMSL specific input parameters
============================== ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**gmsl_mode** (RW): **gmsl_mode** (RW):
GMSL speed mode. GMSL speed mode.
@ -328,10 +326,8 @@ GMSL specific input parameters
| 0 - disabled | 0 - disabled
| 1 - enabled (default) | 1 - enabled (default)
MTD partitions
==================== --------------
mgb4 mtd partitions
====================
The mgb4 driver creates a MTD device with two partitions: The mgb4 driver creates a MTD device with two partitions:
- mgb4-fw.X - FPGA firmware. - mgb4-fw.X - FPGA firmware.
@ -344,9 +340,8 @@ also have a third partition named *mgb4-flash* available in the system. This
partition represents the whole, unpartitioned, card's FLASH memory and one should partition represents the whole, unpartitioned, card's FLASH memory and one should
not fiddle with it... not fiddle with it...
==================== IIO (triggers)
mgb4 iio (triggers) --------------
====================
The mgb4 driver creates an Industrial I/O (IIO) device that provides trigger and The mgb4 driver creates an Industrial I/O (IIO) device that provides trigger and
signal level status capability. The following scan elements are available: signal level status capability. The following scan elements are available:

View File

@ -16,6 +16,7 @@ Video4Linux (V4L) driver-specific documentation
imx imx
imx7 imx7
ipu3 ipu3
ipu6-isys
ivtv ivtv
mgb4 mgb4
omap3isp omap3isp

View File

@ -0,0 +1,127 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/media/brcm,bcm2835-unicam.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Broadcom BCM283x Camera Interface (Unicam)
maintainers:
- Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
description: |-
The Unicam block on BCM283x SoCs is the receiver for either
CSI-2 or CCP2 data from image sensors or similar devices.
The main platform using this SoC is the Raspberry Pi family of boards. On
the Pi the VideoCore firmware can also control this hardware block, and
driving it from two different processors will cause issues. To avoid this,
the firmware checks the device tree configuration during boot. If it finds
device tree nodes whose name starts with 'csi' then it will stop the firmware
accessing the block, and it can then safely be used via the device tree
binding.
properties:
compatible:
const: brcm,bcm2835-unicam
reg:
items:
- description: Unicam block.
- description: Clock Manager Image (CMI) block.
reg-names:
items:
- const: unicam
- const: cmi
interrupts:
maxItems: 1
clocks:
items:
- description: Clock to drive the LP state machine of Unicam.
- description: Clock for the VPU (core clock).
clock-names:
items:
- const: lp
- const: vpu
power-domains:
items:
- description: Unicam power domain
brcm,num-data-lanes:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [ 2, 4 ]
description: |
Number of CSI-2 data lanes supported by this Unicam instance. The number
of data lanes actively used is specified with the data-lanes endpoint
property.
port:
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
properties:
endpoint:
$ref: /schemas/media/video-interfaces.yaml#
additionalProperties: false
properties:
bus-type:
enum: [ 3, 4 ]
clock-noncontinuous: true
data-lanes: true
remote-endpoint: true
required:
- bus-type
- data-lanes
- remote-endpoint
required:
- endpoint
required:
- compatible
- reg
- reg-names
- interrupts
- clocks
- clock-names
- power-domains
- brcm,num-data-lanes
- port
additionalProperties: False
examples:
- |
#include <dt-bindings/clock/bcm2835.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/media/video-interfaces.h>
#include <dt-bindings/power/raspberrypi-power.h>
csi1: csi@7e801000 {
compatible = "brcm,bcm2835-unicam";
reg = <0x7e801000 0x800>,
<0x7e802004 0x4>;
reg-names = "unicam", "cmi";
interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clocks BCM2835_CLOCK_CAM1>,
<&firmware_clocks 4>;
clock-names = "lp", "vpu";
power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
brcm,num-data-lanes = <2>;
port {
csi1_ep: endpoint {
remote-endpoint = <&imx219_0>;
bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>;
data-lanes = <1 2>;
};
};
};
...

View File

@ -15,7 +15,7 @@ description: |
They include an ISP capable of auto exposure and auto white balance. They include an ISP capable of auto exposure and auto white balance.
allOf: allOf:
- $ref: ../video-interface-devices.yaml# - $ref: /schemas/media/video-interface-devices.yaml#
properties: properties:
compatible: compatible:

View File

@ -19,7 +19,7 @@ description:
either through a parallel interface or through MIPI CSI-2. either through a parallel interface or through MIPI CSI-2.
allOf: allOf:
- $ref: ../video-interface-devices.yaml# - $ref: /schemas/media/video-interface-devices.yaml#
properties: properties:
compatible: compatible:

View File

@ -37,31 +37,45 @@ properties:
active low. active low.
maxItems: 1 maxItems: 1
dovdd-supply: DOVDD-supply:
description: description:
Definition of the regulator used as interface power supply. Definition of the regulator used as interface power supply.
avdd-supply: AVDD-supply:
description: description:
Definition of the regulator used as analog power supply. Definition of the regulator used as analog power supply.
dvdd-supply: DVDD-supply:
description: description:
Definition of the regulator used as digital power supply. Definition of the regulator used as digital power supply.
port: port:
$ref: /schemas/graph.yaml#/properties/port
description: description:
A node containing an output port node. A node containing an output port node.
$ref: /schemas/graph.yaml#/$defs/port-base
additionalProperties: false
properties:
endpoint:
$ref: /schemas/media/video-interfaces.yaml#
additionalProperties: false
properties:
link-frequencies: true
remote-endpoint: true
required:
- link-frequencies
required: required:
- compatible - compatible
- reg - reg
- clocks - clocks
- clock-names - clock-names
- dovdd-supply - DOVDD-supply
- avdd-supply - AVDD-supply
- dvdd-supply - DVDD-supply
- reset-gpios - reset-gpios
- port - port
@ -82,13 +96,14 @@ examples:
clock-names = "xvclk"; clock-names = "xvclk";
reset-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; reset-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
dovdd-supply = <&sw2_reg>; DOVDD-supply = <&sw2_reg>;
dvdd-supply = <&sw2_reg>; DVDD-supply = <&sw2_reg>;
avdd-supply = <&reg_peri_3p15v>; AVDD-supply = <&reg_peri_3p15v>;
port { port {
ov2680_to_mipi: endpoint { ov2680_to_mipi: endpoint {
remote-endpoint = <&mipi_from_sensor>; remote-endpoint = <&mipi_from_sensor>;
link-frequencies = /bits/ 64 <330000000>;
}; };
}; };
}; };

View File

@ -2,7 +2,7 @@
# Copyright (c) 2019 MediaTek Inc. # Copyright (c) 2019 MediaTek Inc.
%YAML 1.2 %YAML 1.2
--- ---
$id: http://devicetree.org/schemas/media/i2c/ov8856.yaml# $id: http://devicetree.org/schemas/media/i2c/ovti,ov8856.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml#
title: Omnivision OV8856 CMOS Sensor title: Omnivision OV8856 CMOS Sensor

View File

@ -16,7 +16,7 @@ description: |
maximum throughput of 1.2Gbps/lane. maximum throughput of 1.2Gbps/lane.
allOf: allOf:
- $ref: ../video-interface-devices.yaml# - $ref: /schemas/media/video-interface-devices.yaml#
properties: properties:
compatible: compatible:

View File

@ -23,6 +23,9 @@ description: |-
is treated the same as this as it was the original compatible string. is treated the same as this as it was the original compatible string.
imx290llr is the mono version of the sensor. imx290llr is the mono version of the sensor.
allOf:
- $ref: /schemas/media/video-interface-devices.yaml#
properties: properties:
compatible: compatible:
oneOf: oneOf:
@ -101,7 +104,7 @@ required:
- vdddo-supply - vdddo-supply
- port - port
additionalProperties: false unevaluatedProperties: false
examples: examples:
- | - |

View File

@ -18,7 +18,7 @@ description: |-
available via CSI-2 serial data output (two or four lanes). available via CSI-2 serial data output (two or four lanes).
allOf: allOf:
- $ref: ../video-interface-devices.yaml# - $ref: /schemas/media/video-interface-devices.yaml#
properties: properties:
compatible: compatible:

View File

@ -84,6 +84,7 @@ allOf:
properties: properties:
port@0: port@0:
description: MIPI CSI-2 RX description: MIPI CSI-2 RX
port@1: false
required: required:
- port@0 - port@0

View File

@ -31,6 +31,11 @@ properties:
reg: reg:
maxItems: 1 maxItems: 1
clocks:
items:
- description: AXI DMA engine clock for fetching JPEG bitstream from memory (per)
- description: IP bus clock for register access (ipg)
interrupts: interrupts:
description: | description: |
There are 4 slots available in the IP, which the driver may use There are 4 slots available in the IP, which the driver may use
@ -49,6 +54,7 @@ properties:
required: required:
- compatible - compatible
- reg - reg
- clocks
- interrupts - interrupts
- power-domains - power-domains
@ -56,12 +62,15 @@ additionalProperties: false
examples: examples:
- | - |
#include <dt-bindings/clock/imx8-lpcg.h>
#include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/firmware/imx/rsrc.h> #include <dt-bindings/firmware/imx/rsrc.h>
jpegdec: jpegdec@58400000 { jpegdec: jpegdec@58400000 {
compatible = "nxp,imx8qxp-jpgdec"; compatible = "nxp,imx8qxp-jpgdec";
reg = <0x58400000 0x00050000 >; reg = <0x58400000 0x00050000 >;
clocks = <&img_jpeg_dec_lpcg IMX_LPCG_CLK_0>,
<&img_jpeg_dec_lpcg IMX_LPCG_CLK_4>;
interrupts = <GIC_SPI 309 IRQ_TYPE_LEVEL_HIGH>, interrupts = <GIC_SPI 309 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 310 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 310 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 311 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 311 IRQ_TYPE_LEVEL_HIGH>,
@ -76,6 +85,8 @@ examples:
jpegenc: jpegenc@58450000 { jpegenc: jpegenc@58450000 {
compatible = "nxp,imx8qm-jpgenc", "nxp,imx8qxp-jpgenc"; compatible = "nxp,imx8qm-jpgenc", "nxp,imx8qxp-jpgenc";
reg = <0x58450000 0x00050000 >; reg = <0x58450000 0x00050000 >;
clocks = <&img_jpeg_enc_lpcg IMX_LPCG_CLK_0>,
<&img_jpeg__lpcg IMX_LPCG_CLK_4>;
interrupts = <GIC_SPI 305 IRQ_TYPE_LEVEL_HIGH>, interrupts = <GIC_SPI 305 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 306 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 306 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 307 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 307 IRQ_TYPE_LEVEL_HIGH>,

View File

@ -0,0 +1,512 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/media/qcom,sc8280xp-camss.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm SC8280XP Camera Subsystem (CAMSS)
maintainers:
- Bryan O'Donoghue <bryan.odonoghue@linaro.org>
description: |
The CAMSS IP is a CSI decoder and ISP present on Qualcomm platforms.
properties:
compatible:
const: qcom,sc8280xp-camss
clocks:
maxItems: 40
clock-names:
items:
- const: camnoc_axi
- const: cpas_ahb
- const: csiphy0
- const: csiphy0_timer
- const: csiphy1
- const: csiphy1_timer
- const: csiphy2
- const: csiphy2_timer
- const: csiphy3
- const: csiphy3_timer
- const: vfe0_axi
- const: vfe0
- const: vfe0_cphy_rx
- const: vfe0_csid
- const: vfe1_axi
- const: vfe1
- const: vfe1_cphy_rx
- const: vfe1_csid
- const: vfe2_axi
- const: vfe2
- const: vfe2_cphy_rx
- const: vfe2_csid
- const: vfe3_axi
- const: vfe3
- const: vfe3_cphy_rx
- const: vfe3_csid
- const: vfe_lite0
- const: vfe_lite0_cphy_rx
- const: vfe_lite0_csid
- const: vfe_lite1
- const: vfe_lite1_cphy_rx
- const: vfe_lite1_csid
- const: vfe_lite2
- const: vfe_lite2_cphy_rx
- const: vfe_lite2_csid
- const: vfe_lite3
- const: vfe_lite3_cphy_rx
- const: vfe_lite3_csid
- const: gcc_axi_hf
- const: gcc_axi_sf
interrupts:
maxItems: 20
interrupt-names:
items:
- const: csid1_lite
- const: vfe_lite1
- const: csiphy3
- const: csid0
- const: vfe0
- const: csid1
- const: vfe1
- const: csid0_lite
- const: vfe_lite0
- const: csiphy0
- const: csiphy1
- const: csiphy2
- const: csid2
- const: vfe2
- const: csid3_lite
- const: csid2_lite
- const: vfe_lite3
- const: vfe_lite2
- const: csid3
- const: vfe3
iommus:
maxItems: 16
interconnects:
maxItems: 4
interconnect-names:
items:
- const: cam_ahb
- const: cam_hf_mnoc
- const: cam_sf_mnoc
- const: cam_sf_icp_mnoc
power-domains:
items:
- description: IFE0 GDSC - Image Front End, Global Distributed Switch Controller.
- description: IFE1 GDSC - Image Front End, Global Distributed Switch Controller.
- description: IFE2 GDSC - Image Front End, Global Distributed Switch Controller.
- description: IFE3 GDSC - Image Front End, Global Distributed Switch Controller.
- description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller.
power-domain-names:
items:
- const: ife0
- const: ife1
- const: ife2
- const: ife3
- const: top
ports:
$ref: /schemas/graph.yaml#/properties/ports
description:
CSI input ports.
properties:
port@0:
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
description:
Input port for receiving CSI data from CSIPHY0.
properties:
endpoint:
$ref: video-interfaces.yaml#
unevaluatedProperties: false
properties:
clock-lanes:
maxItems: 1
data-lanes:
minItems: 1
maxItems: 4
required:
- clock-lanes
- data-lanes
port@1:
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
description:
Input port for receiving CSI data from CSIPHY1.
properties:
endpoint:
$ref: video-interfaces.yaml#
unevaluatedProperties: false
properties:
clock-lanes:
maxItems: 1
data-lanes:
minItems: 1
maxItems: 4
required:
- clock-lanes
- data-lanes
port@2:
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
description:
Input port for receiving CSI data from CSIPHY2.
properties:
endpoint:
$ref: video-interfaces.yaml#
unevaluatedProperties: false
properties:
clock-lanes:
maxItems: 1
data-lanes:
minItems: 1
maxItems: 4
required:
- clock-lanes
- data-lanes
port@3:
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
description:
Input port for receiving CSI data from CSIPHY3.
properties:
endpoint:
$ref: video-interfaces.yaml#
unevaluatedProperties: false
properties:
clock-lanes:
maxItems: 1
data-lanes:
minItems: 1
maxItems: 4
required:
- clock-lanes
- data-lanes
reg:
maxItems: 20
reg-names:
items:
- const: csiphy2
- const: csiphy3
- const: csiphy0
- const: csiphy1
- const: vfe0
- const: csid0
- const: vfe1
- const: csid1
- const: vfe2
- const: csid2
- const: vfe_lite0
- const: csid0_lite
- const: vfe_lite1
- const: csid1_lite
- const: vfe_lite2
- const: csid2_lite
- const: vfe_lite3
- const: csid3_lite
- const: vfe3
- const: csid3
vdda-phy-supply:
description:
Phandle to a regulator supply to PHY core block.
vdda-pll-supply:
description:
Phandle to 1.8V regulator supply to PHY refclk pll block.
required:
- clock-names
- clocks
- compatible
- interconnects
- interconnect-names
- interrupts
- interrupt-names
- iommus
- power-domains
- power-domain-names
- reg
- reg-names
- vdda-phy-supply
- vdda-pll-supply
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/qcom,gcc-sc8280xp.h>
#include <dt-bindings/clock/qcom,sc8280xp-camcc.h>
#include <dt-bindings/interconnect/qcom,sc8280xp.h>
#include <dt-bindings/power/qcom-rpmpd.h>
soc {
#address-cells = <2>;
#size-cells = <2>;
camss: camss@ac5a000 {
compatible = "qcom,sc8280xp-camss";
reg = <0 0x0ac5a000 0 0x2000>,
<0 0x0ac5c000 0 0x2000>,
<0 0x0ac65000 0 0x2000>,
<0 0x0ac67000 0 0x2000>,
<0 0x0acaf000 0 0x4000>,
<0 0x0acb3000 0 0x1000>,
<0 0x0acb6000 0 0x4000>,
<0 0x0acba000 0 0x1000>,
<0 0x0acbd000 0 0x4000>,
<0 0x0acc1000 0 0x1000>,
<0 0x0acc4000 0 0x4000>,
<0 0x0acc8000 0 0x1000>,
<0 0x0accb000 0 0x4000>,
<0 0x0accf000 0 0x1000>,
<0 0x0acd2000 0 0x4000>,
<0 0x0acd6000 0 0x1000>,
<0 0x0acd9000 0 0x4000>,
<0 0x0acdd000 0 0x1000>,
<0 0x0ace0000 0 0x4000>,
<0 0x0ace4000 0 0x1000>;
reg-names = "csiphy2",
"csiphy3",
"csiphy0",
"csiphy1",
"vfe0",
"csid0",
"vfe1",
"csid1",
"vfe2",
"csid2",
"vfe_lite0",
"csid0_lite",
"vfe_lite1",
"csid1_lite",
"vfe_lite2",
"csid2_lite",
"vfe_lite3",
"csid3_lite",
"vfe3",
"csid3";
vdda-phy-supply = <&vreg_l6d>;
vdda-pll-supply = <&vreg_l4d>;
interrupts = <GIC_SPI 359 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 360 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 448 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 464 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 465 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 466 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 467 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 468 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 469 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 477 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 478 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 479 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 640 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 641 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 758 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 759 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 760 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 761 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 762 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 764 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "csid1_lite",
"vfe_lite1",
"csiphy3",
"csid0",
"vfe0",
"csid1",
"vfe1",
"csid0_lite",
"vfe_lite0",
"csiphy0",
"csiphy1",
"csiphy2",
"csid2",
"vfe2",
"csid3_lite",
"csid2_lite",
"vfe_lite3",
"vfe_lite2",
"csid3",
"vfe3";
power-domains = <&camcc IFE_0_GDSC>,
<&camcc IFE_1_GDSC>,
<&camcc IFE_2_GDSC>,
<&camcc IFE_3_GDSC>,
<&camcc TITAN_TOP_GDSC>;
power-domain-names = "ife0",
"ife1",
"ife2",
"ife3",
"top";
clocks = <&camcc CAMCC_CAMNOC_AXI_CLK>,
<&camcc CAMCC_CPAS_AHB_CLK>,
<&camcc CAMCC_CSIPHY0_CLK>,
<&camcc CAMCC_CSI0PHYTIMER_CLK>,
<&camcc CAMCC_CSIPHY1_CLK>,
<&camcc CAMCC_CSI1PHYTIMER_CLK>,
<&camcc CAMCC_CSIPHY2_CLK>,
<&camcc CAMCC_CSI2PHYTIMER_CLK>,
<&camcc CAMCC_CSIPHY3_CLK>,
<&camcc CAMCC_CSI3PHYTIMER_CLK>,
<&camcc CAMCC_IFE_0_AXI_CLK>,
<&camcc CAMCC_IFE_0_CLK>,
<&camcc CAMCC_IFE_0_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_0_CSID_CLK>,
<&camcc CAMCC_IFE_1_AXI_CLK>,
<&camcc CAMCC_IFE_1_CLK>,
<&camcc CAMCC_IFE_1_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_1_CSID_CLK>,
<&camcc CAMCC_IFE_2_AXI_CLK>,
<&camcc CAMCC_IFE_2_CLK>,
<&camcc CAMCC_IFE_2_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_2_CSID_CLK>,
<&camcc CAMCC_IFE_3_AXI_CLK>,
<&camcc CAMCC_IFE_3_CLK>,
<&camcc CAMCC_IFE_3_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_3_CSID_CLK>,
<&camcc CAMCC_IFE_LITE_0_CLK>,
<&camcc CAMCC_IFE_LITE_0_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_LITE_0_CSID_CLK>,
<&camcc CAMCC_IFE_LITE_1_CLK>,
<&camcc CAMCC_IFE_LITE_1_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_LITE_1_CSID_CLK>,
<&camcc CAMCC_IFE_LITE_2_CLK>,
<&camcc CAMCC_IFE_LITE_2_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_LITE_2_CSID_CLK>,
<&camcc CAMCC_IFE_LITE_3_CLK>,
<&camcc CAMCC_IFE_LITE_3_CPHY_RX_CLK>,
<&camcc CAMCC_IFE_LITE_3_CSID_CLK>,
<&gcc GCC_CAMERA_HF_AXI_CLK>,
<&gcc GCC_CAMERA_SF_AXI_CLK>;
clock-names = "camnoc_axi",
"cpas_ahb",
"csiphy0",
"csiphy0_timer",
"csiphy1",
"csiphy1_timer",
"csiphy2",
"csiphy2_timer",
"csiphy3",
"csiphy3_timer",
"vfe0_axi",
"vfe0",
"vfe0_cphy_rx",
"vfe0_csid",
"vfe1_axi",
"vfe1",
"vfe1_cphy_rx",
"vfe1_csid",
"vfe2_axi",
"vfe2",
"vfe2_cphy_rx",
"vfe2_csid",
"vfe3_axi",
"vfe3",
"vfe3_cphy_rx",
"vfe3_csid",
"vfe_lite0",
"vfe_lite0_cphy_rx",
"vfe_lite0_csid",
"vfe_lite1",
"vfe_lite1_cphy_rx",
"vfe_lite1_csid",
"vfe_lite2",
"vfe_lite2_cphy_rx",
"vfe_lite2_csid",
"vfe_lite3",
"vfe_lite3_cphy_rx",
"vfe_lite3_csid",
"gcc_axi_hf",
"gcc_axi_sf";
iommus = <&apps_smmu 0x2000 0x4e0>,
<&apps_smmu 0x2020 0x4e0>,
<&apps_smmu 0x2040 0x4e0>,
<&apps_smmu 0x2060 0x4e0>,
<&apps_smmu 0x2080 0x4e0>,
<&apps_smmu 0x20e0 0x4e0>,
<&apps_smmu 0x20c0 0x4e0>,
<&apps_smmu 0x20a0 0x4e0>,
<&apps_smmu 0x2400 0x4e0>,
<&apps_smmu 0x2420 0x4e0>,
<&apps_smmu 0x2440 0x4e0>,
<&apps_smmu 0x2460 0x4e0>,
<&apps_smmu 0x2480 0x4e0>,
<&apps_smmu 0x24e0 0x4e0>,
<&apps_smmu 0x24c0 0x4e0>,
<&apps_smmu 0x24a0 0x4e0>;
interconnects = <&gem_noc MASTER_APPSS_PROC 0 &config_noc SLAVE_CAMERA_CFG 0>,
<&mmss_noc MASTER_CAMNOC_HF 0 &mc_virt SLAVE_EBI1 0>,
<&mmss_noc MASTER_CAMNOC_SF 0 &mc_virt SLAVE_EBI1 0>,
<&mmss_noc MASTER_CAMNOC_ICP 0 &mc_virt SLAVE_EBI1 0>;
interconnect-names = "cam_ahb",
"cam_hf_mnoc",
"cam_sf_mnoc",
"cam_sf_icp_mnoc";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
csiphy_ep0: endpoint@0 {
reg = <0>;
clock-lanes = <7>;
data-lanes = <0 1>;
remote-endpoint = <&sensor_ep>;
};
};
};
};
};

View File

@ -26,6 +26,7 @@ Video4Linux (V4L) drivers
vimc-devel vimc-devel
zoran zoran
ccs/ccs ccs/ccs
ipu6
Digital TV drivers Digital TV drivers

View File

@ -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 (ISYS) and
Processing System (PSYS). 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.
ISYS and PSYS Power flow
------------------------
IPU6 driver initialize the ISYS and PSYS power up or down request by setting the
Buttress frequency control register for ISYS and PSYS
(``IPU6_BUTTRESS_REG_IS_FREQ_CTL`` and ``IPU6_BUTTRESS_REG_PS_FREQ_CTL``) in
function:
.. c:function:: int ipu6_buttress_power(...)
Buttress forwards the request to Punit, after Punit execute the power up flow,
Buttress indicates driver that ISYS or PSYS is powered up by updating the power
status registers.
.. Note:: ISYS power up needs take place prior to PSYS power up, ISYS power down
needs take place after PSYS power down due to hardware limitation.
Interrupt
---------
IPU6 interrupt can be generated as MSI or INTA, interrupt will be triggered when
ISYS, PSYS, Buttress event or error happen, driver can get the interrupt cause
by reading the interrupt status register ``BUTTRESS_REG_ISR_STATUS``, driver
clears the irq status and then calls specific ISYS or PSYS 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. The IPU6 driver will work with Converged Security Engine
(CSE) to complete authentication process. The 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 the IPU6 driver. There are some Buttress
registers used by the CSE and the IPU6 driver to communicate with each other via
IPC.
.. c:function:: int ipu6_buttress_authenticate(...)
Global timer sync
-----------------
The IPU6 driver initiates a Hammock Harbor synchronization flow each time it
starts camera operation. The IPU6 will synchronizes an internal counter in the
Buttress with a copy of the SoC time, this counter maintains the up-to-date time
until camera operation is stopped. The IPU6 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
============
The IPU6 has its own scalar processor where the firmware run at and an internal
32-bit virtual address space. The IPU6 has MMU address translation hardware to
allow that scalar processors to 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 the
IPU6 driver. The IPU6 driver sets the level-1 page table base address to MMU
register and allows MMU to perform page table lookups.
The IPU6 driver exports its own DMA operations. The IPU6 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
====================
The IPU6 firmware is in Code Partition Directory (CPD) file format. The CPD
firmware contains a CPD header, several CPD entries and components. The 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
specific to IPU6 which holds the binary data of firmware called package
directory. The IPU6 driver (``ipu6-cpd.c`` in particular) parses and validates
the CPD firmware file and gets the package directory binary data of the IPU6
firmware, copies it to specific DMA buffer and sets its base address to Buttress
``FW_SOURCE_BASE`` register. Finally the CSE will do authentication for this
firmware binary.
Syscom interface
================
The IPU6 driver communicates with firmware via the Syscom ABI. Syscom is an
inter-processor communication mechanism between the IPU scalar processors and
the 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 the IPU MMU. The Syscom queues are FIFO fixed depth queues
with a configurable number of tokens (messages). There are also common IPU6 MMIO
registers where the queue read and write indices reside. Software and firmware
function as producer and consumer of tokens in the queues and update the write
and read indices separately when sending or receiving each message.
The 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
initiating and starting the communication with firmware. Firmware and software
must use same configurations. The IPU6 Buttress has a number of firmware boot
parameter registers which can be used to store the address of configuration and
initialise 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-2 receivers. It can
capture image pixel data from camera sensors or other MIPI CSI-2 output devices.
D-PHYs and CSI-2 ports lane mapping
-----------------------------------
The 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
adaptional layer between D-PHY and CSI-2 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 versions have 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, the IPU6 supports
maximum 4 CSI-2 ports. For Meteor Lake, D-PHY has 12 data lanes and 6 clock
lanes so IPU6 support maximum 6 CSI-2 ports.
.. Note:: Each pair of CSI-2 two ports is a single unit that can share the data
lanes. For example, for CSI-2 port 0 and 1, CSI-2 port 0 support
maximum 4 data lanes, CSI-2 port 1 support maximum 2 data lanes, CSI-2
port 0 with 2 data lanes can work together with CSI-2 port 1 with 2
data lanes. If trying to use CSI-2 port 0 with 4 lanes, CSI-2 port 1
will not be available as the 4 data lanes are shared by CSI-2 port 0
and 1. The same applies to CSI ports 2/3, 4/5 and 7/8.
ISYS firmware ABIs
------------------
The IPU6 firmware implements a series of ABIs for software access. In general,
software firstly prepares the stream configuration ``struct
ipu6_fw_isys_stream_cfg_data_abi`` and sends 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 the driver gets the interrupt from firmware that indicates stream open
successfully, the 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 waits for the interrupt and
response from firmware, ``PIN_DATA_READY`` means a buffer is ready on a specific
output pin and then software can return the buffer to user.
.. Note:: See :ref:`Examples<ipu6_isys_capture_examples>` about how to do
capture by IPU6 ISYS driver.

View File

@ -70,5 +70,5 @@ include:
``ENOMEM`` ``ENOMEM``
Insufficient kernel memory was available. Insufficient kernel memory was available.
``ENXIO`` ``ENODEV``
No device corresponding to this device special file exists. Device not found or was removed.

View File

@ -91,7 +91,7 @@ appropriately.
- The caller has no permission to access the device. - The caller has no permission to access the device.
- - ``EBUSY`` - - ``EBUSY``
- The the device driver is already in use. - The device driver is already in use.
- - ``EMFILE`` - - ``EMFILE``
- The process already has the maximum number of files open. - The process already has the maximum number of files open.

View File

@ -25,6 +25,13 @@ Glossary
See :ref:`cec`. See :ref:`cec`.
Data Unit
Unit of data transported by a bus. On parallel buses, the data unit
consists of one or more related samples while on serial buses the data
unit is logical. If the data unit is image data, it may also be called a
pixel.
Device Driver Device Driver
Part of the Linux Kernel that implements support for a hardware Part of the Linux Kernel that implements support for a hardware
component. component.
@ -173,6 +180,11 @@ Glossary
An integrated circuit that integrates all components of a computer An integrated circuit that integrates all components of a computer
or other electronic systems. or other electronic systems.
Stream
A distinct flow of data (image data or metadata) from an initial source
to a final sink. The initial source may be e.g. an image sensor and the
final sink e.g. a memory buffer.
V4L2 API V4L2 API
**V4L2 userspace API** **V4L2 userspace API**

View File

@ -47,6 +47,12 @@ member of the ``fmt`` union as needed per the desired operation. Both drivers
and applications must set the remainder of the :c:type:`v4l2_format` structure and applications must set the remainder of the :c:type:`v4l2_format` structure
to 0. to 0.
Devices that capture metadata by line have the struct v4l2_fmtdesc
``V4L2_FMT_FLAG_META_LINE_BASED`` flag set for :c:func:`VIDIOC_ENUM_FMT`. Such
devices can typically also :ref:`capture image data <capture>`. This primarily
involves devices that receive the data from a different devices such as a camera
sensor.
.. c:type:: v4l2_meta_format .. c:type:: v4l2_meta_format
.. tabularcolumns:: |p{1.4cm}|p{2.4cm}|p{13.5cm}| .. tabularcolumns:: |p{1.4cm}|p{2.4cm}|p{13.5cm}|
@ -65,3 +71,18 @@ to 0.
- ``buffersize`` - ``buffersize``
- Maximum buffer size in bytes required for data. The value is set by the - Maximum buffer size in bytes required for data. The value is set by the
driver. driver.
* - __u32
- ``width``
- Width of a line of metadata in Data Units. 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`.

View File

@ -506,6 +506,8 @@ source pads.
subdev-formats subdev-formats
.. _subdev-routing:
Streams, multiplexed media pads and internal routing Streams, multiplexed media pads and internal routing
---------------------------------------------------- ----------------------------------------------------
@ -527,9 +529,9 @@ the its sink pad and allows to route them individually to one of its source
pads. pads.
Subdevice drivers that support multiplexed streams are compatible with Subdevice drivers that support multiplexed streams are compatible with
non-multiplexed subdev drivers, but, of course, require a routing configuration non-multiplexed subdev drivers. However, if the driver at the sink end of a link
where the link between those two types of drivers contains only a single does not support streams, then only stream 0 of source end may be captured.
stream. There may be additional limitations specific to the sink device.
Understanding streams Understanding streams
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
@ -570,6 +572,29 @@ Any configurations of a stream within a pad, such as format or selections,
are independent of similar configurations on other streams. This is are independent of similar configurations on other streams. This is
subject to change in the future. subject to change in the future.
Device types and routing setup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Different kinds of sub-devices have differing behaviour for route activation,
depending on the hardware. In all cases, however, only routes that have the
``V4L2_SUBDEV_STREAM_FL_ACTIVE`` flag set are active.
Devices generating the streams may allow enabling and disabling some of the
routes or have a fixed routing configuration. If the routes can be disabled, not
declaring the routes (or declaring them without
``VIDIOC_SUBDEV_STREAM_FL_ACTIVE`` flag set) in ``VIDIOC_SUBDEV_S_ROUTING`` will
disable the routes. ``VIDIOC_SUBDEV_S_ROUTING`` will still return such routes
back to the user in the routes array, with the ``V4L2_SUBDEV_STREAM_FL_ACTIVE``
flag unset.
Devices transporting the streams almost always have more configurability with
respect to routing. Typically any route between the sub-device's sink and source
pads is possible, and multiple routes (usually up to certain limited number) may
be active simultaneously. For such devices, no routes are created by the driver
and user-created routes are fully replaced when ``VIDIOC_SUBDEV_S_ROUTING`` is
called on the sub-device. Such newly created routes have the device's default
configuration for format and selection rectangles.
Configuring streams Configuring streams
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^

View File

@ -65,8 +65,8 @@ EBUSY
The driver does not support multiple opens and the device is already The driver does not support multiple opens and the device is already
in use. in use.
ENXIO ENODEV
No device corresponding to this device special file exists. Device not found or was removed.
ENOMEM ENOMEM
Not enough kernel memory was available to complete the request. Not enough kernel memory was available to complete the request.

View File

@ -13,9 +13,10 @@ These formats are used for the :ref:`metadata` interface only.
:maxdepth: 1 :maxdepth: 1
metafmt-d4xx metafmt-d4xx
metafmt-generic
metafmt-intel-ipu3 metafmt-intel-ipu3
metafmt-rkisp1 metafmt-rkisp1
metafmt-uvc metafmt-uvc
metafmt-vivid
metafmt-vsp1-hgo metafmt-vsp1-hgo
metafmt-vsp1-hgt metafmt-vsp1-hgt
metafmt-vivid

View File

@ -0,0 +1,340 @@
.. 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')
********************************************************************************************************************************************************************************************************************************************************************************
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.
.. _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 used on CSI-2 for 8 bits per :term:`Data Unit`.
Additionally it is used for 16 bits per Data Unit when two bytes of metadata are
packed into one 16-bit Data Unit. Otherwise the 16 bits per pixel dataformat is
:ref:`V4L2_META_FMT_GENERIC_CSI2_16 <v4l2-meta-fmt-generic-csi2-16>`.
**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:: Sample 4x2 Metadata Frame
: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 8-bit generic metadata packed in 10-bit
Data Units, with one padding byte after every four bytes of metadata. This
format is typically used by CSI-2 receivers with a source that transmits
MEDIA_BUS_FMT_META_10 and the CSI-2 receiver writes the received data to memory
as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS specification.
This format is also used in conjunction with 20 bits per :term:`Data Unit`
formats that pack two bytes of metadata into one Data Unit. Otherwise the
20 bits per pixel dataformat is :ref:`V4L2_META_FMT_GENERIC_CSI2_20
<v4l2-meta-fmt-generic-csi2-20>`.
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 "x" a byte of padding.
.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|
.. flat-table:: Sample 4x2 Metadata Frame
: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`
- x
* - start + 5:
- M\ :sub:`01`
- M\ :sub:`11`
- M\ :sub:`21`
- M\ :sub:`31`
- x
.. _v4l2-meta-fmt-generic-csi2-12:
V4L2_META_FMT_GENERIC_CSI2_12
-----------------------------
V4L2_META_FMT_GENERIC_CSI2_12 contains 8-bit generic metadata packed in 12-bit
Data Units, with one padding byte after every two bytes of metadata. This format
is typically used by CSI-2 receivers with a source that transmits
MEDIA_BUS_FMT_META_12 and the CSI-2 receiver writes the received data to memory
as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS specification.
This format is also used in conjunction with 24 bits per :term:`Data Unit`
formats that pack two bytes of metadata into one Data Unit. Otherwise the
24 bits per pixel dataformat is :ref:`V4L2_META_FMT_GENERIC_CSI2_24
<v4l2-meta-fmt-generic-csi2-24>`.
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 "x" 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:: Sample 4x2 Metadata Frame
:header-rows: 0
:stub-columns: 0
:widths: 12 8 8 8 8 8 8
* - start + 0:
- M\ :sub:`00`
- M\ :sub:`10`
- x
- M\ :sub:`20`
- M\ :sub:`30`
- x
* - start + 6:
- M\ :sub:`01`
- M\ :sub:`11`
- x
- M\ :sub:`21`
- M\ :sub:`31`
- x
.. _v4l2-meta-fmt-generic-csi2-14:
V4L2_META_FMT_GENERIC_CSI2_14
-----------------------------
V4L2_META_FMT_GENERIC_CSI2_14 contains 8-bit generic metadata packed in 14-bit
Data Units, with three padding bytes after every four bytes of metadata. This
format is typically used by CSI-2 receivers with a source that transmits
MEDIA_BUS_FMT_META_14 and the CSI-2 receiver writes the received data to memory
as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS 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 "x" 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:: Sample 4x2 Metadata Frame
: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`
- x
- x
- x
* - start + 7:
- M\ :sub:`01`
- M\ :sub:`11`
- M\ :sub:`21`
- M\ :sub:`31`
- x
- x
- x
.. _v4l2-meta-fmt-generic-csi2-16:
V4L2_META_FMT_GENERIC_CSI2_16
-----------------------------
V4L2_META_FMT_GENERIC_CSI2_16 contains 8-bit generic metadata packed in 16-bit
Data Units, with one padding byte after every byte of metadata. This format is
typically used by CSI-2 receivers with a source that transmits
MEDIA_BUS_FMT_META_16 and the CSI-2 receiver writes the received data to memory
as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS specification.
Some devices support more efficient packing of metadata in conjunction with
16-bit image data. In that case the dataformat is
:ref:`V4L2_META_FMT_GENERIC_8 <v4l2-meta-fmt-generic-8>`.
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 "x" 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:: Sample 4x2 Metadata Frame
:header-rows: 0
:stub-columns: 0
:widths: 12 8 8 8 8 8 8 8 8
* - start + 0:
- M\ :sub:`00`
- x
- M\ :sub:`10`
- x
- M\ :sub:`20`
- x
- M\ :sub:`30`
- x
* - start + 8:
- M\ :sub:`01`
- x
- M\ :sub:`11`
- x
- M\ :sub:`21`
- x
- M\ :sub:`31`
- x
.. _v4l2-meta-fmt-generic-csi2-20:
V4L2_META_FMT_GENERIC_CSI2_20
-----------------------------
V4L2_META_FMT_GENERIC_CSI2_20 contains 8-bit generic metadata packed in 20-bit
Data Units, with alternating one or two padding bytes after every byte of
metadata. This format is typically used by CSI-2 receivers with a source that
transmits MEDIA_BUS_FMT_META_20 and the CSI-2 receiver writes the received data
to memory as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS specification.
Some devices support more efficient packing of metadata in conjunction with
16-bit image data. In that case the dataformat is
:ref:`V4L2_META_FMT_GENERIC_CSI2_10 <v4l2-meta-fmt-generic-csi2-10>`.
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 "x" 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:: Sample 4x2 Metadata Frame
:header-rows: 0
:stub-columns: 0
:widths: 12 8 8 8 8 8 8 8 8 8 8
* - start + 0:
- M\ :sub:`00`
- x
- M\ :sub:`10`
- x
- x
- M\ :sub:`20`
- x
- M\ :sub:`30`
- x
- x
* - start + 10:
- M\ :sub:`01`
- x
- M\ :sub:`11`
- x
- x
- M\ :sub:`21`
- x
- M\ :sub:`31`
- x
- x
.. _v4l2-meta-fmt-generic-csi2-24:
V4L2_META_FMT_GENERIC_CSI2_24
-----------------------------
V4L2_META_FMT_GENERIC_CSI2_24 contains 8-bit generic metadata packed in 24-bit
Data Units, with two padding bytes after every byte of metadata. This format is
typically used by CSI-2 receivers with a source that transmits
MEDIA_BUS_FMT_META_24 and the CSI-2 receiver writes the received data to memory
as-is.
The packing of the data follows the MIPI CSI-2 specification and the padding of
the data is defined in the MIPI CCS specification.
Some devices support more efficient packing of metadata in conjunction with
16-bit image data. In that case the dataformat is
:ref:`V4L2_META_FMT_GENERIC_CSI2_12 <v4l2-meta-fmt-generic-csi2-12>`.
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 "x" 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:: Sample 4x2 Metadata Frame
: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`
- x
- x
- M\ :sub:`10`
- x
- x
- M\ :sub:`20`
- x
- x
- M\ :sub:`30`
- x
- x
* - start + 12:
- M\ :sub:`01`
- x
- x
- M\ :sub:`11`
- x
- x
- M\ :sub:`21`
- x
- x
- M\ :sub:`31`
- x
- x

View File

@ -188,7 +188,7 @@ Example: Mapping buffers in the multi-planar API
buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length, buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length,
PROT_READ | PROT_WRITE, /* recommended */ PROT_READ | PROT_WRITE, /* recommended */
MAP_SHARED, /* recommended */ MAP_SHARED, /* recommended */
fd, buffer.m.planes[j].m.offset); fd, buffer.m.planes[j].m.mem_offset);
if (MAP_FAILED == buffers[i].start[j]) { if (MAP_FAILED == buffers[i].start[j]) {
/* If you do not exit here you should unmap() and free() /* If you do not exit here you should unmap() and free()

View File

@ -36,6 +36,8 @@ are often referred to as greyscale formats.
- Byte 2 - Byte 2
- Byte 3 - Byte 3
- Byte 4 - Byte 4
- Byte 5
- Byte 6
* .. _V4L2-PIX-FMT-GREY: * .. _V4L2-PIX-FMT-GREY:
@ -47,6 +49,8 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-IPU3-Y10: * .. _V4L2-PIX-FMT-IPU3-Y10:
@ -58,6 +62,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`2`\ [3:0] Y'\ :sub:`1`\ [9:6] - Y'\ :sub:`2`\ [3:0] Y'\ :sub:`1`\ [9:6]
- Y'\ :sub:`3`\ [1:0] Y'\ :sub:`2`\ [9:4] - Y'\ :sub:`3`\ [1:0] Y'\ :sub:`2`\ [9:4]
- Y'\ :sub:`3`\ [9:2] - Y'\ :sub:`3`\ [9:2]
- ...
- ...
* .. _V4L2-PIX-FMT-Y10: * .. _V4L2-PIX-FMT-Y10:
@ -69,6 +75,8 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y10BPACK: * .. _V4L2-PIX-FMT-Y10BPACK:
@ -80,6 +88,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`1`\ [3:0] Y'\ :sub:`2`\ [9:6] - Y'\ :sub:`1`\ [3:0] Y'\ :sub:`2`\ [9:6]
- Y'\ :sub:`2`\ [5:0] Y'\ :sub:`3`\ [9:8] - Y'\ :sub:`2`\ [5:0] Y'\ :sub:`3`\ [9:8]
- Y'\ :sub:`3`\ [7:0] - Y'\ :sub:`3`\ [7:0]
- ...
- ...
* .. _V4L2-PIX-FMT-Y10P: * .. _V4L2-PIX-FMT-Y10P:
@ -91,6 +101,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`2`\ [9:2] - Y'\ :sub:`2`\ [9:2]
- Y'\ :sub:`3`\ [9:2] - Y'\ :sub:`3`\ [9:2]
- Y'\ :sub:`3`\ [1:0] Y'\ :sub:`2`\ [1:0] Y'\ :sub:`1`\ [1:0] Y'\ :sub:`0`\ [1:0] - Y'\ :sub:`3`\ [1:0] Y'\ :sub:`2`\ [1:0] Y'\ :sub:`1`\ [1:0] Y'\ :sub:`0`\ [1:0]
- ...
- ...
* .. _V4L2-PIX-FMT-Y12: * .. _V4L2-PIX-FMT-Y12:
@ -102,6 +114,8 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y012: * .. _V4L2-PIX-FMT-Y012:
@ -113,6 +127,21 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y12P:
- ``V4L2_PIX_FMT_Y12P``
- 'Y12P'
- Y'\ :sub:`0`\ [11:4]
- Y'\ :sub:`1`\ [11:4]
- Y'\ :sub:`1`\ [3:0] Y'\ :sub:`0`\ [3:0]
- ...
- ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y14: * .. _V4L2-PIX-FMT-Y14:
@ -124,6 +153,21 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y14P:
- ``V4L2_PIX_FMT_Y14P``
- 'Y14P'
- Y'\ :sub:`0`\ [13:6]
- Y'\ :sub:`1`\ [13:6]
- Y'\ :sub:`2`\ [13:6]
- Y'\ :sub:`3`\ [13:6]
- Y'\ :sub:`1`\ [1:0] Y'\ :sub:`0`\ [5:0]
- Y'\ :sub:`2`\ [3:0] Y'\ :sub:`1`\ [5:2]
- Y'\ :sub:`3`\ [5:0] Y'\ :sub:`2`\ [5:4]
* .. _V4L2-PIX-FMT-Y16: * .. _V4L2-PIX-FMT-Y16:
@ -135,6 +179,8 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
* .. _V4L2-PIX-FMT-Y16-BE: * .. _V4L2-PIX-FMT-Y16-BE:
@ -146,6 +192,8 @@ are often referred to as greyscale formats.
- ... - ...
- ... - ...
- ... - ...
- ...
- ...
.. raw:: latex .. raw:: latex

View File

@ -33,7 +33,7 @@ Media Bus Formats
* - __u32 * - __u32
- ``field`` - ``field``
- Field order, from enum :c:type:`v4l2_field`. See - Field order, from enum :c:type:`v4l2_field`. See
:ref:`field-order` for details. :ref:`field-order` for details. Zero for metadata mbus codes.
* - __u32 * - __u32
- ``colorspace`` - ``colorspace``
- Image colorspace, from enum :c:type:`v4l2_colorspace`. - Image colorspace, from enum :c:type:`v4l2_colorspace`.
@ -45,7 +45,7 @@ Media Bus Formats
conversion is supported by setting the flag conversion is supported by setting the flag
V4L2_SUBDEV_MBUS_CODE_CSC_COLORSPACE in the corresponding struct V4L2_SUBDEV_MBUS_CODE_CSC_COLORSPACE in the corresponding struct
:c:type:`v4l2_subdev_mbus_code_enum` during enumeration. :c:type:`v4l2_subdev_mbus_code_enum` during enumeration.
See :ref:`v4l2-subdev-mbus-code-flags`. See :ref:`v4l2-subdev-mbus-code-flags`. Zero for metadata mbus codes.
* - union { * - union {
- (anonymous) - (anonymous)
* - __u16 * - __u16
@ -61,7 +61,7 @@ Media Bus Formats
that ycbcr_enc conversion is supported by setting the flag that ycbcr_enc conversion is supported by setting the flag
V4L2_SUBDEV_MBUS_CODE_CSC_YCBCR_ENC in the corresponding struct V4L2_SUBDEV_MBUS_CODE_CSC_YCBCR_ENC in the corresponding struct
:c:type:`v4l2_subdev_mbus_code_enum` during enumeration. :c:type:`v4l2_subdev_mbus_code_enum` during enumeration.
See :ref:`v4l2-subdev-mbus-code-flags`. See :ref:`v4l2-subdev-mbus-code-flags`. Zero for metadata mbus codes.
* - __u16 * - __u16
- ``hsv_enc`` - ``hsv_enc``
- HSV encoding, from enum :c:type:`v4l2_hsv_encoding`. - HSV encoding, from enum :c:type:`v4l2_hsv_encoding`.
@ -75,7 +75,7 @@ Media Bus Formats
that hsv_enc conversion is supported by setting the flag that hsv_enc conversion is supported by setting the flag
V4L2_SUBDEV_MBUS_CODE_CSC_HSV_ENC in the corresponding struct V4L2_SUBDEV_MBUS_CODE_CSC_HSV_ENC in the corresponding struct
:c:type:`v4l2_subdev_mbus_code_enum` during enumeration. :c:type:`v4l2_subdev_mbus_code_enum` during enumeration.
See :ref:`v4l2-subdev-mbus-code-flags` See :ref:`v4l2-subdev-mbus-code-flags`. Zero for metadata mbus codes.
* - } * - }
- -
* - __u16 * - __u16
@ -90,8 +90,8 @@ Media Bus Formats
The driver indicates that quantization conversion is supported by The driver indicates that quantization conversion is supported by
setting the flag V4L2_SUBDEV_MBUS_CODE_CSC_QUANTIZATION in the setting the flag V4L2_SUBDEV_MBUS_CODE_CSC_QUANTIZATION in the
corresponding struct :c:type:`v4l2_subdev_mbus_code_enum` corresponding struct :c:type:`v4l2_subdev_mbus_code_enum`
during enumeration. See :ref:`v4l2-subdev-mbus-code-flags`. during enumeration. See :ref:`v4l2-subdev-mbus-code-flags`. Zero for
metadata mbus codes.
* - __u16 * - __u16
- ``xfer_func`` - ``xfer_func``
- Transfer function, from enum :c:type:`v4l2_xfer_func`. - Transfer function, from enum :c:type:`v4l2_xfer_func`.
@ -104,7 +104,8 @@ Media Bus Formats
The driver indicates that the transfer function conversion is supported by The driver indicates that the transfer function conversion is supported by
setting the flag V4L2_SUBDEV_MBUS_CODE_CSC_XFER_FUNC in the setting the flag V4L2_SUBDEV_MBUS_CODE_CSC_XFER_FUNC in the
corresponding struct :c:type:`v4l2_subdev_mbus_code_enum` corresponding struct :c:type:`v4l2_subdev_mbus_code_enum`
during enumeration. See :ref:`v4l2-subdev-mbus-code-flags`. during enumeration. See :ref:`v4l2-subdev-mbus-code-flags`. Zero for
metadata mbus codes.
* - __u16 * - __u16
- ``flags`` - ``flags``
- flags See: :ref:v4l2-mbus-framefmt-flags - flags See: :ref:v4l2-mbus-framefmt-flags
@ -8306,3 +8307,257 @@ The following table lists the existing metadata formats.
both sides of the link and the bus format is a fixed both sides of the link and the bus format is a fixed
metadata format that is not configurable from userspace. metadata format that is not configurable from userspace.
Width and height will be set to 0 for this format. Width and height will be set to 0 for this format.
Generic Serial Metadata Formats
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Generic serial metadata formats are used on serial buses 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.
"b" in an array cell signifies a byte of data, followed by the number of the bit
and finally the bit number in subscript. "x" 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 within bus :term:`Data Unit`
* -
-
- 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`
- x
- x
* .. _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`
- x
- x
- x
- x
* .. _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`
- x
- x
- x
- x
- x
- x
* .. _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`
- x
- x
- x
- x
- x
- x
- x
- x
* .. _MEDIA-BUS-FMT-META-20:
- MEDIA_BUS_FMT_META_20
- 0x8006
-
-
-
-
-
- b0\ :sub:`7`
- b0\ :sub:`6`
- b0\ :sub:`5`
- b0\ :sub:`4`
- b0\ :sub:`3`
- b0\ :sub:`2`
- b0\ :sub:`1`
- b0\ :sub:`0`
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
* .. _MEDIA-BUS-FMT-META-24:
- MEDIA_BUS_FMT_META_24
- 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`
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x
- x

View File

@ -62,6 +62,7 @@ Function Reference
vidioc-query-dv-timings vidioc-query-dv-timings
vidioc-querystd vidioc-querystd
vidioc-reqbufs vidioc-reqbufs
vidioc-remove-bufs
vidioc-s-hw-freq-seek vidioc-s-hw-freq-seek
vidioc-streamon vidioc-streamon
vidioc-subdev-enum-frame-interval vidioc-subdev-enum-frame-interval

View File

@ -227,6 +227,13 @@ the ``mbus_code`` field is handled differently:
The application can ask to configure the quantization of the capture The application can ask to configure the quantization of the capture
device when calling the :ref:`VIDIOC_S_FMT <VIDIOC_G_FMT>` ioctl with 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. :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``
Data Units of data and the offset (in bytes) between the beginning of
each two consecutive lines is ``bytesperline``.
Return Value Return Value
============ ============

View File

@ -0,0 +1,86 @@
.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later
.. c:namespace:: V4L
.. _VIDIOC_REMOVE_BUFS:
************************
ioctl VIDIOC_REMOVE_BUFS
************************
Name
====
VIDIOC_REMOVE_BUFS - Removes buffers from a queue
Synopsis
========
.. c:macro:: VIDIOC_REMOVE_BUFS
``int ioctl(int fd, VIDIOC_REMOVE_BUFS, struct v4l2_remove_buffers *argp)``
Arguments
=========
``fd``
File descriptor returned by :c:func:`open()`.
``argp``
Pointer to struct :c:type:`v4l2_remove_buffers`.
Description
===========
Applications can optionally call the :ref:`VIDIOC_REMOVE_BUFS` ioctl to
remove buffers from a queue.
:ref:`VIDIOC_CREATE_BUFS` ioctl support is mandatory to enable :ref:`VIDIOC_REMOVE_BUFS`.
This ioctl is available if the ``V4L2_BUF_CAP_SUPPORTS_REMOVE_BUFS`` capability
is set on the queue when :c:func:`VIDIOC_REQBUFS` or :c:func:`VIDIOC_CREATE_BUFS`
are invoked.
.. c:type:: v4l2_remove_buffers
.. tabularcolumns:: |p{4.4cm}|p{4.4cm}|p{8.5cm}|
.. flat-table:: struct v4l2_remove_buffers
:header-rows: 0
:stub-columns: 0
:widths: 1 1 2
* - __u32
- ``index``
- The starting buffer index to remove. This field is ignored if count == 0.
* - __u32
- ``count``
- The number of buffers to be removed with indices 'index' until 'index + count - 1'.
All buffers in this range must be valid and in DEQUEUED state.
:ref:`VIDIOC_REMOVE_BUFS` will always check the validity of ``type`, if it is
invalid it returns ``EINVAL`` error code.
If count is set to 0 :ref:`VIDIOC_REMOVE_BUFS` will do nothing and return 0.
* - __u32
- ``type``
- Type of the stream or buffers, this is the same as the struct
:c:type:`v4l2_format` ``type`` field. See
:c:type:`v4l2_buf_type` for valid values.
* - __u32
- ``reserved``\ [13]
- A place holder for future extensions. Drivers and applications
must set the array to zero.
Return Value
============
On success 0 is returned, on error -1 and the ``errno`` variable is set
appropriately. The generic error codes are described at the
:ref:`Generic Error Codes <gen-errors>` chapter. If an error occurs, no
buffers will be freed and one of the error codes below will be returned:
EBUSY
File I/O is in progress.
One or more of the buffers in the range ``index`` to ``index + count - 1`` are not
in DEQUEUED state.
EINVAL
One or more of the buffers in the range ``index`` to ``index + count - 1`` do not
exist in the queue.
The buffer type (``type`` field) is not valid.

View File

@ -121,6 +121,7 @@ aborting or finishing any DMA in progress, an implicit
.. _V4L2-BUF-CAP-SUPPORTS-M2M-HOLD-CAPTURE-BUF: .. _V4L2-BUF-CAP-SUPPORTS-M2M-HOLD-CAPTURE-BUF:
.. _V4L2-BUF-CAP-SUPPORTS-MMAP-CACHE-HINTS: .. _V4L2-BUF-CAP-SUPPORTS-MMAP-CACHE-HINTS:
.. _V4L2-BUF-CAP-SUPPORTS-MAX-NUM-BUFFERS: .. _V4L2-BUF-CAP-SUPPORTS-MAX-NUM-BUFFERS:
.. _V4L2-BUF-CAP-SUPPORTS-REMOVE-BUFS:
.. raw:: latex .. raw:: latex

View File

@ -37,9 +37,9 @@ Description
.. note:: .. note::
This is an :ref:`obsolete` interface and may be removed This is an :ref:`obsolete` interface and may be removed in the future. It is
in the future. It is superseded by superseded by :ref:`the selection API <VIDIOC_SUBDEV_G_SELECTION>`. No new
:ref:`the selection API <VIDIOC_SUBDEV_G_SELECTION>`. extensions to the :c:type:`v4l2_subdev_crop` structure will be accepted.
To retrieve the current crop rectangle applications set the ``pad`` To retrieve the current crop rectangle applications set the ``pad``
field of a struct :c:type:`v4l2_subdev_crop` to the field of a struct :c:type:`v4l2_subdev_crop` to the

View File

@ -43,23 +43,39 @@ The routing configuration determines the flows of data inside an entity.
Drivers report their current routing tables using the Drivers report their current routing tables using the
``VIDIOC_SUBDEV_G_ROUTING`` ioctl and application may enable or disable routes ``VIDIOC_SUBDEV_G_ROUTING`` ioctl and application may enable or disable routes
with the ``VIDIOC_SUBDEV_S_ROUTING`` ioctl, by adding or removing routes and with the ``VIDIOC_SUBDEV_S_ROUTING`` ioctl, by adding or removing routes and
setting or clearing flags of the ``flags`` field of a setting or clearing flags of the ``flags`` field of a struct
struct :c:type:`v4l2_subdev_route`. :c:type:`v4l2_subdev_route`. Similarly to ``VIDIOC_SUBDEV_G_ROUTING``, also
``VIDIOC_SUBDEV_S_ROUTING`` returns the routes back to the user.
All stream configurations are reset when ``VIDIOC_SUBDEV_S_ROUTING`` is called. This All stream configurations are reset when ``VIDIOC_SUBDEV_S_ROUTING`` is called.
means that the userspace must reconfigure all streams after calling the ioctl This means that the userspace must reconfigure all stream formats and selections
with e.g. ``VIDIOC_SUBDEV_S_FMT``. after calling the ioctl with e.g. ``VIDIOC_SUBDEV_S_FMT``.
Only subdevices which have both sink and source pads can support routing. Only subdevices which have both sink and source pads can support routing.
When inspecting routes through ``VIDIOC_SUBDEV_G_ROUTING`` and the application The ``len_routes`` field indicates the number of routes that can fit in the
provided ``num_routes`` is not big enough to contain all the available routes ``routes`` array allocated by userspace. It is set by applications for both
the subdevice exposes, drivers return the ENOSPC error code and adjust the ioctls to indicate how many routes the kernel can return, and is never modified
value of the ``num_routes`` field. Application should then reserve enough memory by the kernel.
for all the route entries and call ``VIDIOC_SUBDEV_G_ROUTING`` again.
On a successful ``VIDIOC_SUBDEV_G_ROUTING`` call the driver updates the The ``num_routes`` field indicates the number of routes in the routing
``num_routes`` field to reflect the actual number of routes returned. table. For ``VIDIOC_SUBDEV_S_ROUTING``, it is set by userspace to the number of
routes that the application stored in the ``routes`` array. For both ioctls, it
is returned by the kernel and indicates how many routes are stored in the
subdevice routing table. This may be smaller or larger than the value of
``num_routes`` set by the application for ``VIDIOC_SUBDEV_S_ROUTING``, as
drivers may adjust the requested routing table.
The kernel can return a ``num_routes`` value larger than ``len_routes`` from
both ioctls. This indicates thare are more routes in the routing table than fits
the ``routes`` array. In this case, the ``routes`` array is filled by the kernel
with the first ``len_routes`` entries of the subdevice routing table. This is
not considered to be an error, and the ioctl call succeeds. If the applications
wants to retrieve the missing routes, it can issue a new
``VIDIOC_SUBDEV_G_ROUTING`` call with a large enough ``routes`` array.
``VIDIOC_SUBDEV_S_ROUTING`` may return more routes than the user provided in
``num_routes`` field due to e.g. hardware properties.
.. tabularcolumns:: |p{4.4cm}|p{4.4cm}|p{8.7cm}| .. tabularcolumns:: |p{4.4cm}|p{4.4cm}|p{8.7cm}|
@ -74,6 +90,9 @@ On a successful ``VIDIOC_SUBDEV_G_ROUTING`` call the driver updates the
- ``which`` - ``which``
- Routing table to be accessed, from enum - Routing table to be accessed, from enum
:ref:`v4l2_subdev_format_whence <v4l2-subdev-format-whence>`. :ref:`v4l2_subdev_format_whence <v4l2-subdev-format-whence>`.
* - __u32
- ``len_routes``
- The length of the array (as in memory reserved for the array)
* - struct :c:type:`v4l2_subdev_route` * - struct :c:type:`v4l2_subdev_route`
- ``routes[]`` - ``routes[]``
- Array of struct :c:type:`v4l2_subdev_route` entries - Array of struct :c:type:`v4l2_subdev_route` entries
@ -81,7 +100,7 @@ On a successful ``VIDIOC_SUBDEV_G_ROUTING`` call the driver updates the
- ``num_routes`` - ``num_routes``
- Number of entries of the routes array - Number of entries of the routes array
* - __u32 * - __u32
- ``reserved``\ [5] - ``reserved``\ [11]
- Reserved for future extensions. Applications and drivers must set - Reserved for future extensions. Applications and drivers must set
the array to zero. the array to zero.
@ -135,10 +154,6 @@ On success 0 is returned, on error -1 and the ``errno`` variable is set
appropriately. The generic error codes are described at the appropriately. The generic error codes are described at the
:ref:`Generic Error Codes <gen-errors>` chapter. :ref:`Generic Error Codes <gen-errors>` chapter.
ENOSPC
The application provided ``num_routes`` is not big enough to contain
all the available routes the subdevice exposes.
EINVAL EINVAL
The sink or source pad identifiers reference a non-existing pad or reference The sink or source pad identifiers reference a non-existing pad or reference
pads of different types (ie. the sink_pad identifiers refers to a source pads of different types (ie. the sink_pad identifiers refers to a source

View File

@ -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_YCBCR_ENC fmtdesc-flags
replace define V4L2_FMT_FLAG_CSC_HSV_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_CSC_QUANTIZATION fmtdesc-flags
replace define V4L2_FMT_FLAG_META_LINE_BASED fmtdesc-flags
# V4L2 timecode types # V4L2 timecode types
replace define V4L2_TC_TYPE_24FPS timecode-type replace define V4L2_TC_TYPE_24FPS timecode-type

View File

@ -4110,6 +4110,13 @@ N: bcm113*
N: bcm216* N: bcm216*
N: kona N: kona
BROADCOM BCM2835 CAMERA DRIVERS
M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
F: drivers/media/platform/broadcom/bcm2835-unicam*
BROADCOM BCM47XX MIPS ARCHITECTURE BROADCOM BCM47XX MIPS ARCHITECTURE
M: Hauke Mehrtens <hauke@hauke-m.de> M: Hauke Mehrtens <hauke@hauke-m.de>
M: Rafał Miłecki <zajec5@gmail.com> M: Rafał Miłecki <zajec5@gmail.com>
@ -11091,6 +11098,16 @@ F: Documentation/admin-guide/media/ipu3_rcb.svg
F: Documentation/userspace-api/media/v4l/metafmt-intel-ipu3.rst F: Documentation/userspace-api/media/v4l/metafmt-intel-ipu3.rst
F: drivers/staging/media/ipu3/ 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 INTEL ISHTP ECLITE DRIVER
M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com> M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com>
L: platform-driver-x86@vger.kernel.org L: platform-driver-x86@vger.kernel.org
@ -16539,7 +16556,7 @@ M: Sakari Ailus <sakari.ailus@linux.intel.com>
L: linux-media@vger.kernel.org L: linux-media@vger.kernel.org
S: Maintained S: Maintained
T: git git://linuxtv.org/media_tree.git T: git git://linuxtv.org/media_tree.git
F: Documentation/devicetree/bindings/media/i2c/ov8856.yaml F: Documentation/devicetree/bindings/media/i2c/ovti,ov8856.yaml
F: drivers/media/i2c/ov8856.c F: drivers/media/i2c/ov8856.c
OMNIVISION OV8858 SENSOR DRIVER OMNIVISION OV8858 SENSOR DRIVER

View File

@ -490,6 +490,15 @@ int cec_thread_func(void *_adap)
goto unlock; goto unlock;
} }
if (adap->transmit_in_progress &&
adap->transmit_in_progress_aborted) {
if (adap->transmitting)
cec_data_cancel(adap->transmitting,
CEC_TX_STATUS_ABORTED, 0);
adap->transmit_in_progress = false;
adap->transmit_in_progress_aborted = false;
goto unlock;
}
if (adap->transmit_in_progress && timeout) { if (adap->transmit_in_progress && timeout) {
/* /*
* If we timeout, then log that. Normally this does * If we timeout, then log that. Normally this does
@ -771,6 +780,7 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
{ {
struct cec_data *data; struct cec_data *data;
bool is_raw = msg_is_raw(msg); bool is_raw = msg_is_raw(msg);
int err;
if (adap->devnode.unregistered) if (adap->devnode.unregistered)
return -ENODEV; return -ENODEV;
@ -935,11 +945,13 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
* Release the lock and wait, retake the lock afterwards. * Release the lock and wait, retake the lock afterwards.
*/ */
mutex_unlock(&adap->lock); mutex_unlock(&adap->lock);
wait_for_completion_killable(&data->c); err = wait_for_completion_killable(&data->c);
if (!data->completed) cancel_delayed_work_sync(&data->work);
cancel_delayed_work_sync(&data->work);
mutex_lock(&adap->lock); mutex_lock(&adap->lock);
if (err)
adap->transmit_in_progress_aborted = true;
/* Cancel the transmit if it was interrupted */ /* Cancel the transmit if it was interrupted */
if (!data->completed) { if (!data->completed) {
if (data->msg.tx_status & CEC_TX_STATUS_OK) if (data->msg.tx_status & CEC_TX_STATUS_OK)
@ -1575,9 +1587,12 @@ unconfigure:
*/ */
static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) static void cec_claim_log_addrs(struct cec_adapter *adap, bool block)
{ {
if (WARN_ON(adap->is_configuring || adap->is_configured)) if (WARN_ON(adap->is_claiming_log_addrs ||
adap->is_configuring || adap->is_configured))
return; return;
adap->is_claiming_log_addrs = true;
init_completion(&adap->config_completion); init_completion(&adap->config_completion);
/* Ready to kick off the thread */ /* Ready to kick off the thread */
@ -1592,6 +1607,7 @@ static void cec_claim_log_addrs(struct cec_adapter *adap, bool block)
wait_for_completion(&adap->config_completion); wait_for_completion(&adap->config_completion);
mutex_lock(&adap->lock); mutex_lock(&adap->lock);
} }
adap->is_claiming_log_addrs = false;
} }
/* /*

View File

@ -178,7 +178,7 @@ static long cec_adap_s_log_addrs(struct cec_adapter *adap, struct cec_fh *fh,
CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU | CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU |
CEC_LOG_ADDRS_FL_CDC_ONLY; CEC_LOG_ADDRS_FL_CDC_ONLY;
mutex_lock(&adap->lock); mutex_lock(&adap->lock);
if (!adap->is_configuring && if (!adap->is_claiming_log_addrs && !adap->is_configuring &&
(!log_addrs.num_log_addrs || !adap->is_configured) && (!log_addrs.num_log_addrs || !adap->is_configured) &&
!cec_is_busy(adap, fh)) { !cec_is_busy(adap, fh)) {
err = __cec_s_log_addrs(adap, &log_addrs, block); err = __cec_s_log_addrs(adap, &log_addrs, block);
@ -664,6 +664,8 @@ static int cec_release(struct inode *inode, struct file *filp)
list_del_init(&data->xfer_list); list_del_init(&data->xfer_list);
} }
mutex_unlock(&adap->lock); mutex_unlock(&adap->lock);
mutex_lock(&fh->lock);
while (!list_empty(&fh->msgs)) { while (!list_empty(&fh->msgs)) {
struct cec_msg_entry *entry = struct cec_msg_entry *entry =
list_first_entry(&fh->msgs, struct cec_msg_entry, list); list_first_entry(&fh->msgs, struct cec_msg_entry, list);
@ -681,6 +683,7 @@ static int cec_release(struct inode *inode, struct file *filp)
kfree(entry); kfree(entry);
} }
} }
mutex_unlock(&fh->lock);
kfree(fh); kfree(fh);
cec_put_device(devnode); cec_put_device(devnode);

View File

@ -62,12 +62,12 @@ int cec_get_device(struct cec_devnode *devnode)
*/ */
mutex_lock(&devnode->lock); mutex_lock(&devnode->lock);
/* /*
* return ENXIO if the cec device has been removed * return ENODEV if the cec device has been removed
* already or if it is not registered anymore. * already or if it is not registered anymore.
*/ */
if (!devnode->registered) { if (!devnode->registered) {
mutex_unlock(&devnode->lock); mutex_unlock(&devnode->lock);
return -ENXIO; return -ENODEV;
} }
/* and increase the device refcount */ /* and increase the device refcount */
get_device(&devnode->dev); get_device(&devnode->dev);

View File

@ -8,6 +8,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/dmi.h> #include <linux/dmi.h>
#include <linux/pci.h> #include <linux/pci.h>
@ -573,6 +574,12 @@ static void cros_ec_cec_remove(struct platform_device *pdev)
} }
} }
static const struct platform_device_id cros_ec_cec_id[] = {
{ DRV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(platform, cros_ec_cec_id);
static struct platform_driver cros_ec_cec_driver = { static struct platform_driver cros_ec_cec_driver = {
.probe = cros_ec_cec_probe, .probe = cros_ec_cec_probe,
.remove_new = cros_ec_cec_remove, .remove_new = cros_ec_cec_remove,
@ -580,6 +587,7 @@ static struct platform_driver cros_ec_cec_driver = {
.name = DRV_NAME, .name = DRV_NAME,
.pm = &cros_ec_cec_pm_ops, .pm = &cros_ec_cec_pm_ops,
}, },
.id_table = cros_ec_cec_id,
}; };
module_platform_driver(cros_ec_cec_driver); module_platform_driver(cros_ec_cec_driver);
@ -587,4 +595,3 @@ module_platform_driver(cros_ec_cec_driver);
MODULE_DESCRIPTION("CEC driver for ChromeOS ECs"); MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@ -122,7 +122,7 @@ static int calculate_h_scale_registers(struct saa7146_dev *dev,
xacm = 0; xacm = 0;
/* set horizontal filter parameters (CXY = CXUV) */ /* set horizontal filter parameters (CXY = CXUV) */
cxy = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].hps_coeff; cxy = hps_h_coeff_tab[min(xpsc - 1, 63)].hps_coeff;
cxuv = cxy; cxuv = cxy;
/* calculate and set horizontal fine scale (xsci) */ /* calculate and set horizontal fine scale (xsci) */
@ -151,7 +151,7 @@ static int calculate_h_scale_registers(struct saa7146_dev *dev,
xacm = 0; xacm = 0;
/* get best match in the table of attenuations /* get best match in the table of attenuations
for horizontal scaling */ for horizontal scaling */
h_atten = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].weight_sum; h_atten = hps_h_coeff_tab[min(xpsc - 1, 63)].weight_sum;
for (i = 0; h_attenuation[i] != 0; i++) { for (i = 0; h_attenuation[i] != 0; i++) {
if (h_attenuation[i] >= h_atten) if (h_attenuation[i] >= h_atten)
@ -283,10 +283,10 @@ static int calculate_v_scale_registers(struct saa7146_dev *dev, enum v4l2_field
} }
/* get filter coefficients for cya, cyb from table hps_v_coeff_tab */ /* get filter coefficients for cya, cyb from table hps_v_coeff_tab */
cya_cyb = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].hps_coeff; cya_cyb = hps_v_coeff_tab[min(yacl, 63)].hps_coeff;
/* get best match in the table of attenuations for vertical scaling */ /* get best match in the table of attenuations for vertical scaling */
v_atten = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].weight_sum; v_atten = hps_v_coeff_tab[min(yacl, 63)].weight_sum;
for (i = 0; v_attenuation[i] != 0; i++) { for (i = 0; v_attenuation[i] != 0; i++) {
if (v_attenuation[i] >= v_atten) if (v_attenuation[i] >= v_atten)

View File

@ -421,11 +421,12 @@ static void init_buffer_cache_hints(struct vb2_queue *q, struct vb2_buffer *vb)
*/ */
static void vb2_queue_add_buffer(struct vb2_queue *q, struct vb2_buffer *vb, unsigned int index) static void vb2_queue_add_buffer(struct vb2_queue *q, struct vb2_buffer *vb, unsigned int index)
{ {
WARN_ON(index >= q->max_num_buffers || q->bufs[index] || vb->vb2_queue); WARN_ON(index >= q->max_num_buffers || test_bit(index, q->bufs_bitmap) || vb->vb2_queue);
q->bufs[index] = vb; q->bufs[index] = vb;
vb->index = index; vb->index = index;
vb->vb2_queue = q; vb->vb2_queue = q;
set_bit(index, q->bufs_bitmap);
} }
/** /**
@ -434,6 +435,7 @@ static void vb2_queue_add_buffer(struct vb2_queue *q, struct vb2_buffer *vb, uns
*/ */
static void vb2_queue_remove_buffer(struct vb2_buffer *vb) static void vb2_queue_remove_buffer(struct vb2_buffer *vb)
{ {
clear_bit(vb->index, vb->vb2_queue->bufs_bitmap);
vb->vb2_queue->bufs[vb->index] = NULL; vb->vb2_queue->bufs[vb->index] = NULL;
vb->vb2_queue = NULL; vb->vb2_queue = NULL;
} }
@ -442,16 +444,19 @@ static void vb2_queue_remove_buffer(struct vb2_buffer *vb)
* __vb2_queue_alloc() - allocate vb2 buffer structures and (for MMAP type) * __vb2_queue_alloc() - allocate vb2 buffer structures and (for MMAP type)
* video buffer memory for all buffers/planes on the queue and initializes the * video buffer memory for all buffers/planes on the queue and initializes the
* queue * queue
* @first_index: index of the first created buffer, all newly allocated buffers
* have indices in the range [first_index..first_index+count-1]
* *
* Returns the number of buffers successfully allocated. * Returns the number of buffers successfully allocated.
*/ */
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory, static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes, unsigned int num_buffers, unsigned int num_planes,
const unsigned plane_sizes[VB2_MAX_PLANES]) const unsigned int plane_sizes[VB2_MAX_PLANES],
unsigned int *first_index)
{ {
unsigned int q_num_buffers = vb2_get_num_buffers(q);
unsigned int buffer, plane; unsigned int buffer, plane;
struct vb2_buffer *vb; struct vb2_buffer *vb;
unsigned long index = q->max_num_buffers;
int ret; int ret;
/* /*
@ -459,7 +464,25 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
* in the queue is below q->max_num_buffers * in the queue is below q->max_num_buffers
*/ */
num_buffers = min_t(unsigned int, num_buffers, num_buffers = min_t(unsigned int, num_buffers,
q->max_num_buffers - q_num_buffers); q->max_num_buffers - vb2_get_num_buffers(q));
while (num_buffers) {
index = bitmap_find_next_zero_area(q->bufs_bitmap, q->max_num_buffers,
0, num_buffers, 0);
if (index < q->max_num_buffers)
break;
/* Try to find free space for less buffers */
num_buffers--;
}
/* If there is no space left to allocate buffers return 0 to indicate the error */
if (!num_buffers) {
*first_index = 0;
return 0;
}
*first_index = index;
for (buffer = 0; buffer < num_buffers; ++buffer) { for (buffer = 0; buffer < num_buffers; ++buffer) {
/* Allocate vb2 buffer structures */ /* Allocate vb2 buffer structures */
@ -479,7 +502,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
vb->planes[plane].min_length = plane_sizes[plane]; vb->planes[plane].min_length = plane_sizes[plane];
} }
vb2_queue_add_buffer(q, vb, q_num_buffers + buffer); vb2_queue_add_buffer(q, vb, index++);
call_void_bufop(q, init_buffer, vb); call_void_bufop(q, init_buffer, vb);
/* Allocate video buffer memory for the MMAP type */ /* Allocate video buffer memory for the MMAP type */
@ -517,17 +540,16 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
} }
/* /*
* __vb2_free_mem() - release all video buffer memory for a given queue * __vb2_free_mem() - release video buffer memory for a given range of
* buffers in a given queue
*/ */
static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers) static void __vb2_free_mem(struct vb2_queue *q, unsigned int start, unsigned int count)
{ {
unsigned int buffer; unsigned int i;
struct vb2_buffer *vb; struct vb2_buffer *vb;
unsigned int q_num_buffers = vb2_get_num_buffers(q);
for (buffer = q_num_buffers - buffers; buffer < q_num_buffers; for (i = start; i < start + count; i++) {
++buffer) { vb = vb2_get_buffer(q, i);
vb = vb2_get_buffer(q, buffer);
if (!vb) if (!vb)
continue; continue;
@ -542,35 +564,33 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers)
} }
/* /*
* __vb2_queue_free() - free buffers at the end of the queue - video memory and * __vb2_queue_free() - free @count buffers from @start index of the queue - video memory and
* related information, if no buffers are left return the queue to an * related information, if no buffers are left return the queue to an
* uninitialized state. Might be called even if the queue has already been freed. * uninitialized state. Might be called even if the queue has already been freed.
*/ */
static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) static void __vb2_queue_free(struct vb2_queue *q, unsigned int start, unsigned int count)
{ {
unsigned int buffer; unsigned int i;
unsigned int q_num_buffers = vb2_get_num_buffers(q);
lockdep_assert_held(&q->mmap_lock); lockdep_assert_held(&q->mmap_lock);
/* Call driver-provided cleanup function for each buffer, if provided */ /* Call driver-provided cleanup function for each buffer, if provided */
for (buffer = q_num_buffers - buffers; buffer < q_num_buffers; for (i = start; i < start + count; i++) {
++buffer) { struct vb2_buffer *vb = vb2_get_buffer(q, i);
struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
if (vb && vb->planes[0].mem_priv) if (vb && vb->planes[0].mem_priv)
call_void_vb_qop(vb, buf_cleanup, vb); call_void_vb_qop(vb, buf_cleanup, vb);
} }
/* Release video buffer memory */ /* Release video buffer memory */
__vb2_free_mem(q, buffers); __vb2_free_mem(q, start, count);
#ifdef CONFIG_VIDEO_ADV_DEBUG #ifdef CONFIG_VIDEO_ADV_DEBUG
/* /*
* Check that all the calls were balanced during the life-time of this * Check that all the calls were balanced during the life-time of this
* queue. If not then dump the counters to the kernel log. * queue. If not then dump the counters to the kernel log.
*/ */
if (q_num_buffers) { if (vb2_get_num_buffers(q)) {
bool unbalanced = q->cnt_start_streaming != q->cnt_stop_streaming || bool unbalanced = q->cnt_start_streaming != q->cnt_stop_streaming ||
q->cnt_prepare_streaming != q->cnt_unprepare_streaming || q->cnt_prepare_streaming != q->cnt_unprepare_streaming ||
q->cnt_wait_prepare != q->cnt_wait_finish; q->cnt_wait_prepare != q->cnt_wait_finish;
@ -596,8 +616,8 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
q->cnt_stop_streaming = 0; q->cnt_stop_streaming = 0;
q->cnt_unprepare_streaming = 0; q->cnt_unprepare_streaming = 0;
} }
for (buffer = 0; buffer < vb2_get_num_buffers(q); buffer++) { for (i = start; i < start + count; i++) {
struct vb2_buffer *vb = vb2_get_buffer(q, buffer); struct vb2_buffer *vb = vb2_get_buffer(q, i);
bool unbalanced; bool unbalanced;
if (!vb) if (!vb)
@ -614,7 +634,7 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
if (unbalanced) { if (unbalanced) {
pr_info("unbalanced counters for queue %p, buffer %d:\n", pr_info("unbalanced counters for queue %p, buffer %d:\n",
q, buffer); q, i);
if (vb->cnt_buf_init != vb->cnt_buf_cleanup) if (vb->cnt_buf_init != vb->cnt_buf_cleanup)
pr_info(" buf_init: %u buf_cleanup: %u\n", pr_info(" buf_init: %u buf_cleanup: %u\n",
vb->cnt_buf_init, vb->cnt_buf_cleanup); vb->cnt_buf_init, vb->cnt_buf_cleanup);
@ -648,9 +668,8 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
#endif #endif
/* Free vb2 buffers */ /* Free vb2 buffers */
for (buffer = q_num_buffers - buffers; buffer < q_num_buffers; for (i = start; i < start + count; i++) {
++buffer) { struct vb2_buffer *vb = vb2_get_buffer(q, i);
struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
if (!vb) if (!vb)
continue; continue;
@ -659,7 +678,6 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
kfree(vb); kfree(vb);
} }
q->num_buffers -= buffers;
if (!vb2_get_num_buffers(q)) { if (!vb2_get_num_buffers(q)) {
q->memory = VB2_MEMORY_UNKNOWN; q->memory = VB2_MEMORY_UNKNOWN;
INIT_LIST_HEAD(&q->queued_list); INIT_LIST_HEAD(&q->queued_list);
@ -691,7 +709,7 @@ EXPORT_SYMBOL(vb2_buffer_in_use);
static bool __buffers_in_use(struct vb2_queue *q) static bool __buffers_in_use(struct vb2_queue *q)
{ {
unsigned int buffer; unsigned int buffer;
for (buffer = 0; buffer < vb2_get_num_buffers(q); ++buffer) { for (buffer = 0; buffer < q->max_num_buffers; ++buffer) {
struct vb2_buffer *vb = vb2_get_buffer(q, buffer); struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
if (!vb) if (!vb)
@ -813,6 +831,32 @@ static bool verify_coherency_flags(struct vb2_queue *q, bool non_coherent_mem)
return true; return true;
} }
static int vb2_core_allocated_buffers_storage(struct vb2_queue *q)
{
if (!q->bufs)
q->bufs = kcalloc(q->max_num_buffers, sizeof(*q->bufs), GFP_KERNEL);
if (!q->bufs)
return -ENOMEM;
if (!q->bufs_bitmap)
q->bufs_bitmap = bitmap_zalloc(q->max_num_buffers, GFP_KERNEL);
if (!q->bufs_bitmap) {
kfree(q->bufs);
q->bufs = NULL;
return -ENOMEM;
}
return 0;
}
static void vb2_core_free_buffers_storage(struct vb2_queue *q)
{
kfree(q->bufs);
q->bufs = NULL;
bitmap_free(q->bufs_bitmap);
q->bufs_bitmap = NULL;
}
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int flags, unsigned int *count) unsigned int flags, unsigned int *count)
{ {
@ -820,7 +864,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int q_num_bufs = vb2_get_num_buffers(q); unsigned int q_num_bufs = vb2_get_num_buffers(q);
unsigned plane_sizes[VB2_MAX_PLANES] = { }; unsigned plane_sizes[VB2_MAX_PLANES] = { };
bool non_coherent_mem = flags & V4L2_MEMORY_FLAG_NON_COHERENT; bool non_coherent_mem = flags & V4L2_MEMORY_FLAG_NON_COHERENT;
unsigned int i; unsigned int i, first_index;
int ret = 0; int ret = 0;
if (q->streaming) { if (q->streaming) {
@ -851,9 +895,10 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
* queued without ever calling STREAMON. * queued without ever calling STREAMON.
*/ */
__vb2_queue_cancel(q); __vb2_queue_cancel(q);
__vb2_queue_free(q, q_num_bufs); __vb2_queue_free(q, 0, q->max_num_buffers);
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
q->is_busy = 0;
/* /*
* In case of REQBUFS(0) return immediately without calling * In case of REQBUFS(0) return immediately without calling
* driver's queue_setup() callback and allocating resources. * driver's queue_setup() callback and allocating resources.
@ -865,7 +910,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
/* /*
* Make sure the requested values and current defaults are sane. * Make sure the requested values and current defaults are sane.
*/ */
num_buffers = max_t(unsigned int, *count, q->min_queued_buffers); num_buffers = max_t(unsigned int, *count, q->min_reqbufs_allocation);
num_buffers = min_t(unsigned int, num_buffers, q->max_num_buffers); num_buffers = min_t(unsigned int, num_buffers, q->max_num_buffers);
memset(q->alloc_devs, 0, sizeof(q->alloc_devs)); memset(q->alloc_devs, 0, sizeof(q->alloc_devs));
/* /*
@ -873,10 +918,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
* in the queue_setup op. * in the queue_setup op.
*/ */
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
if (!q->bufs) ret = vb2_core_allocated_buffers_storage(q);
q->bufs = kcalloc(q->max_num_buffers, sizeof(*q->bufs), GFP_KERNEL);
if (!q->bufs)
ret = -ENOMEM;
q->memory = memory; q->memory = memory;
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
if (ret) if (ret)
@ -906,8 +948,10 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
/* Finally, allocate buffers and video memory */ /* Finally, allocate buffers and video memory */
allocated_buffers = allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes); __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes, &first_index);
if (allocated_buffers == 0) { if (allocated_buffers == 0) {
/* There shouldn't be any buffers allocated, so first_index == 0 */
WARN_ON(first_index);
dprintk(q, 1, "memory allocation failed\n"); dprintk(q, 1, "memory allocation failed\n");
ret = -ENOMEM; ret = -ENOMEM;
goto error; goto error;
@ -917,7 +961,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
* There is no point in continuing if we can't allocate the minimum * There is no point in continuing if we can't allocate the minimum
* number of buffers needed by this vb2_queue. * number of buffers needed by this vb2_queue.
*/ */
if (allocated_buffers < q->min_queued_buffers) if (allocated_buffers < q->min_reqbufs_allocation)
ret = -ENOMEM; ret = -ENOMEM;
/* /*
@ -946,7 +990,6 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
} }
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
q->num_buffers = allocated_buffers;
if (ret < 0) { if (ret < 0) {
/* /*
@ -954,7 +997,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
* from already queued buffers and it will reset q->memory to * from already queued buffers and it will reset q->memory to
* VB2_MEMORY_UNKNOWN. * VB2_MEMORY_UNKNOWN.
*/ */
__vb2_queue_free(q, allocated_buffers); __vb2_queue_free(q, first_index, allocated_buffers);
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
return ret; return ret;
} }
@ -966,6 +1009,7 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
*/ */
*count = allocated_buffers; *count = allocated_buffers;
q->waiting_for_buffers = !q->is_output; q->waiting_for_buffers = !q->is_output;
q->is_busy = 1;
return 0; return 0;
@ -973,6 +1017,7 @@ error:
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
q->memory = VB2_MEMORY_UNKNOWN; q->memory = VB2_MEMORY_UNKNOWN;
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
vb2_core_free_buffers_storage(q);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(vb2_core_reqbufs); EXPORT_SYMBOL_GPL(vb2_core_reqbufs);
@ -980,7 +1025,8 @@ EXPORT_SYMBOL_GPL(vb2_core_reqbufs);
int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory, int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int flags, unsigned int *count, unsigned int flags, unsigned int *count,
unsigned int requested_planes, unsigned int requested_planes,
const unsigned int requested_sizes[]) const unsigned int requested_sizes[],
unsigned int *first_index)
{ {
unsigned int num_planes = 0, num_buffers, allocated_buffers; unsigned int num_planes = 0, num_buffers, allocated_buffers;
unsigned plane_sizes[VB2_MAX_PLANES] = { }; unsigned plane_sizes[VB2_MAX_PLANES] = { };
@ -1005,11 +1051,8 @@ int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
* value in the queue_setup op. * value in the queue_setup op.
*/ */
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
ret = vb2_core_allocated_buffers_storage(q);
q->memory = memory; q->memory = memory;
if (!q->bufs)
q->bufs = kcalloc(q->max_num_buffers, sizeof(*q->bufs), GFP_KERNEL);
if (!q->bufs)
ret = -ENOMEM;
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
if (ret) if (ret)
return ret; return ret;
@ -1042,7 +1085,7 @@ int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
/* Finally, allocate buffers and video memory */ /* Finally, allocate buffers and video memory */
allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers, allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers,
num_planes, plane_sizes); num_planes, plane_sizes, first_index);
if (allocated_buffers == 0) { if (allocated_buffers == 0) {
dprintk(q, 1, "memory allocation failed\n"); dprintk(q, 1, "memory allocation failed\n");
ret = -ENOMEM; ret = -ENOMEM;
@ -1072,7 +1115,6 @@ int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
} }
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
q->num_buffers += allocated_buffers;
if (ret < 0) { if (ret < 0) {
/* /*
@ -1080,7 +1122,7 @@ int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
* from already queued buffers and it will reset q->memory to * from already queued buffers and it will reset q->memory to
* VB2_MEMORY_UNKNOWN. * VB2_MEMORY_UNKNOWN.
*/ */
__vb2_queue_free(q, allocated_buffers); __vb2_queue_free(q, *first_index, allocated_buffers);
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
return -ENOMEM; return -ENOMEM;
} }
@ -1091,6 +1133,7 @@ int vb2_core_create_bufs(struct vb2_queue *q, enum vb2_memory memory,
* to the userspace. * to the userspace.
*/ */
*count = allocated_buffers; *count = allocated_buffers;
q->is_busy = 1;
return 0; return 0;
@ -1648,6 +1691,44 @@ int vb2_core_prepare_buf(struct vb2_queue *q, struct vb2_buffer *vb, void *pb)
} }
EXPORT_SYMBOL_GPL(vb2_core_prepare_buf); EXPORT_SYMBOL_GPL(vb2_core_prepare_buf);
int vb2_core_remove_bufs(struct vb2_queue *q, unsigned int start, unsigned int count)
{
unsigned int i, ret = 0;
unsigned int q_num_bufs = vb2_get_num_buffers(q);
if (count == 0)
return 0;
if (count > q_num_bufs)
return -EINVAL;
if (start > q->max_num_buffers - count)
return -EINVAL;
mutex_lock(&q->mmap_lock);
/* Check that all buffers in the range exist */
for (i = start; i < start + count; i++) {
struct vb2_buffer *vb = vb2_get_buffer(q, i);
if (!vb) {
ret = -EINVAL;
goto unlock;
}
if (vb->state != VB2_BUF_STATE_DEQUEUED) {
ret = -EBUSY;
goto unlock;
}
}
__vb2_queue_free(q, start, count);
dprintk(q, 2, "%u buffers removed\n", count);
unlock:
mutex_unlock(&q->mmap_lock);
return ret;
}
EXPORT_SYMBOL_GPL(vb2_core_remove_bufs);
/* /*
* vb2_start_streaming() - Attempt to start streaming. * vb2_start_streaming() - Attempt to start streaming.
* @q: videobuf2 queue * @q: videobuf2 queue
@ -1694,7 +1775,7 @@ static int vb2_start_streaming(struct vb2_queue *q)
* Forcefully reclaim buffers if the driver did not * Forcefully reclaim buffers if the driver did not
* correctly return them to vb2. * correctly return them to vb2.
*/ */
for (i = 0; i < vb2_get_num_buffers(q); ++i) { for (i = 0; i < q->max_num_buffers; ++i) {
vb = vb2_get_buffer(q, i); vb = vb2_get_buffer(q, i);
if (!vb) if (!vb)
@ -2100,7 +2181,7 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
* to vb2 in stop_streaming(). * to vb2 in stop_streaming().
*/ */
if (WARN_ON(atomic_read(&q->owned_by_drv_count))) { if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
for (i = 0; i < vb2_get_num_buffers(q); i++) { for (i = 0; i < q->max_num_buffers; i++) {
struct vb2_buffer *vb = vb2_get_buffer(q, i); struct vb2_buffer *vb = vb2_get_buffer(q, i);
if (!vb) if (!vb)
@ -2144,7 +2225,7 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
* call to __fill_user_buffer() after buf_finish(). That order can't * call to __fill_user_buffer() after buf_finish(). That order can't
* be changed, so we can't move the buf_finish() to __vb2_dqbuf(). * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
*/ */
for (i = 0; i < vb2_get_num_buffers(q); i++) { for (i = 0; i < q->max_num_buffers; i++) {
struct vb2_buffer *vb; struct vb2_buffer *vb;
struct media_request *req; struct media_request *req;
@ -2503,7 +2584,7 @@ int vb2_core_queue_init(struct vb2_queue *q)
WARN_ON(!q->ops->buf_queue)) WARN_ON(!q->ops->buf_queue))
return -EINVAL; return -EINVAL;
if (WARN_ON(q->max_num_buffers > MAX_BUFFER_INDEX) || if (WARN_ON(q->max_num_buffers < VB2_MAX_FRAME) ||
WARN_ON(q->min_queued_buffers > q->max_num_buffers)) WARN_ON(q->min_queued_buffers > q->max_num_buffers))
return -EINVAL; return -EINVAL;
@ -2521,6 +2602,25 @@ int vb2_core_queue_init(struct vb2_queue *q)
if (WARN_ON(q->supports_requests && q->min_queued_buffers)) if (WARN_ON(q->supports_requests && q->min_queued_buffers))
return -EINVAL; return -EINVAL;
/*
* The minimum requirement is 2: one buffer is used
* by the hardware while the other is being processed by userspace.
*/
if (q->min_reqbufs_allocation < 2)
q->min_reqbufs_allocation = 2;
/*
* If the driver needs 'min_queued_buffers' in the queue before
* calling start_streaming() then the minimum requirement is
* 'min_queued_buffers + 1' to keep at least one buffer available
* for userspace.
*/
if (q->min_reqbufs_allocation < q->min_queued_buffers + 1)
q->min_reqbufs_allocation = q->min_queued_buffers + 1;
if (WARN_ON(q->min_reqbufs_allocation > q->max_num_buffers))
return -EINVAL;
INIT_LIST_HEAD(&q->queued_list); INIT_LIST_HEAD(&q->queued_list);
INIT_LIST_HEAD(&q->done_list); INIT_LIST_HEAD(&q->done_list);
spin_lock_init(&q->done_lock); spin_lock_init(&q->done_lock);
@ -2552,9 +2652,9 @@ void vb2_core_queue_release(struct vb2_queue *q)
__vb2_cleanup_fileio(q); __vb2_cleanup_fileio(q);
__vb2_queue_cancel(q); __vb2_queue_cancel(q);
mutex_lock(&q->mmap_lock); mutex_lock(&q->mmap_lock);
__vb2_queue_free(q, vb2_get_num_buffers(q)); __vb2_queue_free(q, 0, q->max_num_buffers);
kfree(q->bufs); vb2_core_free_buffers_storage(q);
q->bufs = NULL; q->is_busy = 0;
mutex_unlock(&q->mmap_lock); mutex_unlock(&q->mmap_lock);
} }
EXPORT_SYMBOL_GPL(vb2_core_queue_release); EXPORT_SYMBOL_GPL(vb2_core_queue_release);
@ -2713,7 +2813,6 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
struct vb2_fileio_data *fileio; struct vb2_fileio_data *fileio;
struct vb2_buffer *vb; struct vb2_buffer *vb;
int i, ret; int i, ret;
unsigned int count = 0;
/* /*
* Sanity check * Sanity check
@ -2734,18 +2833,8 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
if (q->streaming || vb2_get_num_buffers(q) > 0) if (q->streaming || vb2_get_num_buffers(q) > 0)
return -EBUSY; return -EBUSY;
/*
* Start with q->min_queued_buffers + 1, driver can increase it in
* queue_setup()
*
* 'min_queued_buffers' buffers need to be queued up before you
* can start streaming, plus 1 for userspace (or in this case,
* kernelspace) processing.
*/
count = max(2, q->min_queued_buffers + 1);
dprintk(q, 3, "setting up file io: mode %s, count %d, read_once %d, write_immediately %d\n", dprintk(q, 3, "setting up file io: mode %s, count %d, read_once %d, write_immediately %d\n",
(read) ? "read" : "write", count, q->fileio_read_once, (read) ? "read" : "write", q->min_reqbufs_allocation, q->fileio_read_once,
q->fileio_write_immediately); q->fileio_write_immediately);
fileio = kzalloc(sizeof(*fileio), GFP_KERNEL); fileio = kzalloc(sizeof(*fileio), GFP_KERNEL);
@ -2759,13 +2848,19 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
* Request buffers and use MMAP type to force driver * Request buffers and use MMAP type to force driver
* to allocate buffers by itself. * to allocate buffers by itself.
*/ */
fileio->count = count; fileio->count = q->min_reqbufs_allocation;
fileio->memory = VB2_MEMORY_MMAP; fileio->memory = VB2_MEMORY_MMAP;
fileio->type = q->type; fileio->type = q->type;
q->fileio = fileio; q->fileio = fileio;
ret = vb2_core_reqbufs(q, fileio->memory, 0, &fileio->count); ret = vb2_core_reqbufs(q, fileio->memory, 0, &fileio->count);
if (ret) if (ret)
goto err_kfree; goto err_kfree;
/* vb2_fileio_data supports max VB2_MAX_FRAME buffers */
if (fileio->count > VB2_MAX_FRAME) {
dprintk(q, 1, "fileio: more than VB2_MAX_FRAME buffers requested\n");
ret = -ENOSPC;
goto err_reqbufs;
}
/* /*
* Userspace can never add or delete buffers later, so there * Userspace can never add or delete buffers later, so there

View File

@ -685,7 +685,7 @@ static void vb2_set_flags_and_caps(struct vb2_queue *q, u32 memory,
*flags &= V4L2_MEMORY_FLAG_NON_COHERENT; *flags &= V4L2_MEMORY_FLAG_NON_COHERENT;
} }
*caps = V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS; *caps |= V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS;
if (q->io_modes & VB2_MMAP) if (q->io_modes & VB2_MMAP)
*caps |= V4L2_BUF_CAP_SUPPORTS_MMAP; *caps |= V4L2_BUF_CAP_SUPPORTS_MMAP;
if (q->io_modes & VB2_USERPTR) if (q->io_modes & VB2_USERPTR)
@ -795,11 +795,15 @@ int vb2_create_bufs(struct vb2_queue *q, struct v4l2_create_buffers *create)
for (i = 0; i < requested_planes; i++) for (i = 0; i < requested_planes; i++)
if (requested_sizes[i] == 0) if (requested_sizes[i] == 0)
return -EINVAL; return -EINVAL;
return ret ? ret : vb2_core_create_bufs(q, create->memory, if (ret)
create->flags, return ret;
&create->count,
requested_planes, return vb2_core_create_bufs(q, create->memory,
requested_sizes); create->flags,
&create->count,
requested_planes,
requested_sizes,
&create->index);
} }
EXPORT_SYMBOL_GPL(vb2_create_bufs); EXPORT_SYMBOL_GPL(vb2_create_bufs);
@ -997,6 +1001,24 @@ EXPORT_SYMBOL_GPL(vb2_poll);
/* vb2 ioctl helpers */ /* vb2 ioctl helpers */
int vb2_ioctl_remove_bufs(struct file *file, void *priv,
struct v4l2_remove_buffers *d)
{
struct video_device *vdev = video_devdata(file);
if (vdev->queue->type != d->type)
return -EINVAL;
if (d->count == 0)
return 0;
if (vb2_queue_is_busy(vdev->queue, file))
return -EBUSY;
return vb2_core_remove_bufs(vdev->queue, d->index, d->count);
}
EXPORT_SYMBOL_GPL(vb2_ioctl_remove_bufs);
int vb2_ioctl_reqbufs(struct file *file, void *priv, int vb2_ioctl_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p) struct v4l2_requestbuffers *p)
{ {

View File

@ -956,7 +956,7 @@ int dvb_usercopy(struct file *file,
int (*func)(struct file *file, int (*func)(struct file *file,
unsigned int cmd, void *arg)) unsigned int cmd, void *arg))
{ {
char sbuf[128]; char sbuf[128] = {};
void *mbuf = NULL; void *mbuf = NULL;
void *parg = NULL; void *parg = NULL;
int err = -EINVAL; int err = -EINVAL;

View File

@ -174,6 +174,6 @@ struct as10x_register_addr {
uint32_t addr; uint32_t addr;
/* register mode access */ /* register mode access */
uint8_t mode; uint8_t mode;
}; } __packed;
#endif #endif

View File

@ -33,7 +33,6 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/firmware.h>
#include <linux/i2c.h> #include <linux/i2c.h>
/* /*
@ -1910,7 +1909,6 @@ struct drx_demod_instance {
/* generic demodulator data */ /* generic demodulator data */
struct i2c_adapter *i2c; struct i2c_adapter *i2c;
const struct firmware *firmware;
}; };
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------

View File

@ -56,6 +56,7 @@ INCLUDE FILES
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/module.h> #include <linux/module.h>
#include <linux/firmware.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/slab.h> #include <linux/slab.h>
@ -1444,8 +1445,7 @@ static int drxdap_fasi_read_block(struct i2c_device_addr *dev_addr,
/* Read block from I2C **************************************************** */ /* Read block from I2C **************************************************** */
do { do {
u16 todo = (datasize < DRXDAP_MAX_RCHUNKSIZE ? u16 todo = min(datasize, DRXDAP_MAX_RCHUNKSIZE);
datasize : DRXDAP_MAX_RCHUNKSIZE);
bufx = 0; bufx = 0;
@ -1659,7 +1659,7 @@ static int drxdap_fasi_write_block(struct i2c_device_addr *dev_addr,
Address must be rewritten because HI is reset after data transport and Address must be rewritten because HI is reset after data transport and
expects an address. expects an address.
*/ */
todo = (block_size < datasize ? block_size : datasize); todo = min(block_size, datasize);
if (todo == 0) { if (todo == 0) {
u16 overhead_size_i2c_addr = 0; u16 overhead_size_i2c_addr = 0;
u16 data_block_size = 0; u16 data_block_size = 0;
@ -1681,9 +1681,7 @@ static int drxdap_fasi_write_block(struct i2c_device_addr *dev_addr,
first_err = st; first_err = st;
} }
bufx = 0; bufx = 0;
todo = todo = min(data_block_size, datasize);
(data_block_size <
datasize ? data_block_size : datasize);
} }
memcpy(&buf[bufx], data, todo); memcpy(&buf[bufx], data, todo);
/* write (address if can do and) data */ /* write (address if can do and) data */
@ -11750,6 +11748,7 @@ static int drx_ctrl_u_code(struct drx_demod_instance *demod,
u8 *mc_data = NULL; u8 *mc_data = NULL;
unsigned size; unsigned size;
char *mc_file; char *mc_file;
const struct firmware *fw;
/* Check arguments */ /* Check arguments */
if (!mc_info || !mc_info->mc_file) if (!mc_info || !mc_info->mc_file)
@ -11757,28 +11756,22 @@ static int drx_ctrl_u_code(struct drx_demod_instance *demod,
mc_file = mc_info->mc_file; mc_file = mc_info->mc_file;
if (!demod->firmware) { rc = request_firmware(&fw, mc_file, demod->i2c->dev.parent);
const struct firmware *fw = NULL; if (rc < 0) {
pr_err("Couldn't read firmware %s\n", mc_file);
rc = request_firmware(&fw, mc_file, demod->i2c->dev.parent); return rc;
if (rc < 0) {
pr_err("Couldn't read firmware %s\n", mc_file);
return rc;
}
demod->firmware = fw;
if (demod->firmware->size < 2 * sizeof(u16)) {
rc = -EINVAL;
pr_err("Firmware is too short!\n");
goto release;
}
pr_info("Firmware %s, size %zu\n",
mc_file, demod->firmware->size);
} }
mc_data_init = demod->firmware->data; if (fw->size < 2 * sizeof(u16)) {
size = demod->firmware->size; rc = -EINVAL;
pr_err("Firmware is too short!\n");
goto release;
}
pr_info("Firmware %s, size %zu\n", mc_file, fw->size);
mc_data_init = fw->data;
size = fw->size;
mc_data = (void *)mc_data_init; mc_data = (void *)mc_data_init;
/* Check data */ /* Check data */
@ -11874,7 +11867,8 @@ static int drx_ctrl_u_code(struct drx_demod_instance *demod,
0x0000)) { 0x0000)) {
pr_err("error reading firmware at pos %zd\n", pr_err("error reading firmware at pos %zd\n",
mc_data - mc_data_init); mc_data - mc_data_init);
return -EIO; rc = -EIO;
goto release;
} }
result = memcmp(curr_ptr, mc_data_buffer, result = memcmp(curr_ptr, mc_data_buffer,
@ -11883,7 +11877,8 @@ static int drx_ctrl_u_code(struct drx_demod_instance *demod,
if (result) { if (result) {
pr_err("error verifying firmware at pos %zd\n", pr_err("error verifying firmware at pos %zd\n",
mc_data - mc_data_init); mc_data - mc_data_init);
return -EIO; rc = -EIO;
goto release;
} }
curr_addr += ((dr_xaddr_t)(bytes_to_comp / 2)); curr_addr += ((dr_xaddr_t)(bytes_to_comp / 2));
@ -11893,17 +11888,17 @@ static int drx_ctrl_u_code(struct drx_demod_instance *demod,
break; break;
} }
default: default:
return -EINVAL; rc = -EINVAL;
goto release;
} }
mc_data += mc_block_nr_bytes; mc_data += mc_block_nr_bytes;
} }
return 0; rc = 0;
release: release:
release_firmware(demod->firmware); release_firmware(fw);
demod->firmware = NULL;
return rc; return rc;
} }
@ -12271,7 +12266,6 @@ static void drx39xxj_release(struct dvb_frontend *fe)
kfree(demod->my_ext_attr); kfree(demod->my_ext_attr);
kfree(demod->my_common_attr); kfree(demod->my_common_attr);
kfree(demod->my_i2c_dev_addr); kfree(demod->my_i2c_dev_addr);
release_firmware(demod->firmware);
kfree(demod); kfree(demod);
kfree(state); kfree(state);
} }

View File

@ -2176,6 +2176,11 @@ static int lgdt3306a_probe(struct i2c_client *client)
struct dvb_frontend *fe; struct dvb_frontend *fe;
int ret; int ret;
if (!client->dev.platform_data) {
dev_err(&client->dev, "platform data is mandatory\n");
return -EINVAL;
}
config = kmemdup(client->dev.platform_data, config = kmemdup(client->dev.platform_data,
sizeof(struct lgdt3306a_config), GFP_KERNEL); sizeof(struct lgdt3306a_config), GFP_KERNEL);
if (config == NULL) { if (config == NULL) {

View File

@ -1000,6 +1000,13 @@ static int m88ds3103_set_frontend(struct dvb_frontend *fe)
if (ret) if (ret)
goto err; goto err;
if (dev->chiptype == M88DS3103_CHIPTYPE_3103B) {
/* to light up the LOCK led */
ret = m88ds3103_update_bits(dev, 0x11, 0x80, 0x00);
if (ret)
goto err;
}
dev->delivery_system = c->delivery_system; dev->delivery_system = c->delivery_system;
return 0; return 0;

View File

@ -1381,57 +1381,57 @@ static int config_ts(struct mxl *state, enum MXL_HYDRA_DEMOD_ID_E demod_id,
u32 nco_count_min = 0; u32 nco_count_min = 0;
u32 clk_type = 0; u32 clk_type = 0;
struct MXL_REG_FIELD_T xpt_sync_polarity[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_sync_polarity[MXL_HYDRA_DEMOD_MAX] = {
{0x90700010, 8, 1}, {0x90700010, 9, 1}, {0x90700010, 8, 1}, {0x90700010, 9, 1},
{0x90700010, 10, 1}, {0x90700010, 11, 1}, {0x90700010, 10, 1}, {0x90700010, 11, 1},
{0x90700010, 12, 1}, {0x90700010, 13, 1}, {0x90700010, 12, 1}, {0x90700010, 13, 1},
{0x90700010, 14, 1}, {0x90700010, 15, 1} }; {0x90700010, 14, 1}, {0x90700010, 15, 1} };
struct MXL_REG_FIELD_T xpt_clock_polarity[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_clock_polarity[MXL_HYDRA_DEMOD_MAX] = {
{0x90700010, 16, 1}, {0x90700010, 17, 1}, {0x90700010, 16, 1}, {0x90700010, 17, 1},
{0x90700010, 18, 1}, {0x90700010, 19, 1}, {0x90700010, 18, 1}, {0x90700010, 19, 1},
{0x90700010, 20, 1}, {0x90700010, 21, 1}, {0x90700010, 20, 1}, {0x90700010, 21, 1},
{0x90700010, 22, 1}, {0x90700010, 23, 1} }; {0x90700010, 22, 1}, {0x90700010, 23, 1} };
struct MXL_REG_FIELD_T xpt_valid_polarity[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_valid_polarity[MXL_HYDRA_DEMOD_MAX] = {
{0x90700014, 0, 1}, {0x90700014, 1, 1}, {0x90700014, 0, 1}, {0x90700014, 1, 1},
{0x90700014, 2, 1}, {0x90700014, 3, 1}, {0x90700014, 2, 1}, {0x90700014, 3, 1},
{0x90700014, 4, 1}, {0x90700014, 5, 1}, {0x90700014, 4, 1}, {0x90700014, 5, 1},
{0x90700014, 6, 1}, {0x90700014, 7, 1} }; {0x90700014, 6, 1}, {0x90700014, 7, 1} };
struct MXL_REG_FIELD_T xpt_ts_clock_phase[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_ts_clock_phase[MXL_HYDRA_DEMOD_MAX] = {
{0x90700018, 0, 3}, {0x90700018, 4, 3}, {0x90700018, 0, 3}, {0x90700018, 4, 3},
{0x90700018, 8, 3}, {0x90700018, 12, 3}, {0x90700018, 8, 3}, {0x90700018, 12, 3},
{0x90700018, 16, 3}, {0x90700018, 20, 3}, {0x90700018, 16, 3}, {0x90700018, 20, 3},
{0x90700018, 24, 3}, {0x90700018, 28, 3} }; {0x90700018, 24, 3}, {0x90700018, 28, 3} };
struct MXL_REG_FIELD_T xpt_lsb_first[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_lsb_first[MXL_HYDRA_DEMOD_MAX] = {
{0x9070000C, 16, 1}, {0x9070000C, 17, 1}, {0x9070000C, 16, 1}, {0x9070000C, 17, 1},
{0x9070000C, 18, 1}, {0x9070000C, 19, 1}, {0x9070000C, 18, 1}, {0x9070000C, 19, 1},
{0x9070000C, 20, 1}, {0x9070000C, 21, 1}, {0x9070000C, 20, 1}, {0x9070000C, 21, 1},
{0x9070000C, 22, 1}, {0x9070000C, 23, 1} }; {0x9070000C, 22, 1}, {0x9070000C, 23, 1} };
struct MXL_REG_FIELD_T xpt_sync_byte[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_sync_byte[MXL_HYDRA_DEMOD_MAX] = {
{0x90700010, 0, 1}, {0x90700010, 1, 1}, {0x90700010, 0, 1}, {0x90700010, 1, 1},
{0x90700010, 2, 1}, {0x90700010, 3, 1}, {0x90700010, 2, 1}, {0x90700010, 3, 1},
{0x90700010, 4, 1}, {0x90700010, 5, 1}, {0x90700010, 4, 1}, {0x90700010, 5, 1},
{0x90700010, 6, 1}, {0x90700010, 7, 1} }; {0x90700010, 6, 1}, {0x90700010, 7, 1} };
struct MXL_REG_FIELD_T xpt_enable_output[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_enable_output[MXL_HYDRA_DEMOD_MAX] = {
{0x9070000C, 0, 1}, {0x9070000C, 1, 1}, {0x9070000C, 0, 1}, {0x9070000C, 1, 1},
{0x9070000C, 2, 1}, {0x9070000C, 3, 1}, {0x9070000C, 2, 1}, {0x9070000C, 3, 1},
{0x9070000C, 4, 1}, {0x9070000C, 5, 1}, {0x9070000C, 4, 1}, {0x9070000C, 5, 1},
{0x9070000C, 6, 1}, {0x9070000C, 7, 1} }; {0x9070000C, 6, 1}, {0x9070000C, 7, 1} };
struct MXL_REG_FIELD_T xpt_err_replace_sync[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_err_replace_sync[MXL_HYDRA_DEMOD_MAX] = {
{0x9070000C, 24, 1}, {0x9070000C, 25, 1}, {0x9070000C, 24, 1}, {0x9070000C, 25, 1},
{0x9070000C, 26, 1}, {0x9070000C, 27, 1}, {0x9070000C, 26, 1}, {0x9070000C, 27, 1},
{0x9070000C, 28, 1}, {0x9070000C, 29, 1}, {0x9070000C, 28, 1}, {0x9070000C, 29, 1},
{0x9070000C, 30, 1}, {0x9070000C, 31, 1} }; {0x9070000C, 30, 1}, {0x9070000C, 31, 1} };
struct MXL_REG_FIELD_T xpt_err_replace_valid[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_err_replace_valid[MXL_HYDRA_DEMOD_MAX] = {
{0x90700014, 8, 1}, {0x90700014, 9, 1}, {0x90700014, 8, 1}, {0x90700014, 9, 1},
{0x90700014, 10, 1}, {0x90700014, 11, 1}, {0x90700014, 10, 1}, {0x90700014, 11, 1},
{0x90700014, 12, 1}, {0x90700014, 13, 1}, {0x90700014, 12, 1}, {0x90700014, 13, 1},
{0x90700014, 14, 1}, {0x90700014, 15, 1} }; {0x90700014, 14, 1}, {0x90700014, 15, 1} };
struct MXL_REG_FIELD_T xpt_continuous_clock[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_continuous_clock[MXL_HYDRA_DEMOD_MAX] = {
{0x907001D4, 0, 1}, {0x907001D4, 1, 1}, {0x907001D4, 0, 1}, {0x907001D4, 1, 1},
{0x907001D4, 2, 1}, {0x907001D4, 3, 1}, {0x907001D4, 2, 1}, {0x907001D4, 3, 1},
{0x907001D4, 4, 1}, {0x907001D4, 5, 1}, {0x907001D4, 4, 1}, {0x907001D4, 5, 1},
{0x907001D4, 6, 1}, {0x907001D4, 7, 1} }; {0x907001D4, 6, 1}, {0x907001D4, 7, 1} };
struct MXL_REG_FIELD_T xpt_nco_clock_rate[MXL_HYDRA_DEMOD_MAX] = { static const struct MXL_REG_FIELD_T xpt_nco_clock_rate[MXL_HYDRA_DEMOD_MAX] = {
{0x90700044, 16, 80}, {0x90700044, 16, 81}, {0x90700044, 16, 80}, {0x90700044, 16, 81},
{0x90700044, 16, 82}, {0x90700044, 16, 83}, {0x90700044, 16, 82}, {0x90700044, 16, 83},
{0x90700044, 16, 84}, {0x90700044, 16, 85}, {0x90700044, 16, 84}, {0x90700044, 16, 85},

View File

@ -513,10 +513,8 @@ static int si2165_upload_firmware(struct si2165_state *state)
ret = 0; ret = 0;
state->firmware_loaded = true; state->firmware_loaded = true;
error: error:
if (fw) { release_firmware(fw);
release_firmware(fw); fw = NULL;
fw = NULL;
}
return ret; return ret;
} }

View File

@ -1277,7 +1277,7 @@ static int stb0899_get_dev_id(struct stb0899_state *state)
dprintk(state->verbose, FE_ERROR, 1, "Demodulator Core ID=[%s], Version=[%d]", (char *) &demod_str, demod_ver); dprintk(state->verbose, FE_ERROR, 1, "Demodulator Core ID=[%s], Version=[%d]", (char *) &demod_str, demod_ver);
CONVERT32(STB0899_READ_S2REG(STB0899_S2FEC, FEC_CORE_ID_REG), (char *)&fec_str); CONVERT32(STB0899_READ_S2REG(STB0899_S2FEC, FEC_CORE_ID_REG), (char *)&fec_str);
fec_ver = STB0899_READ_S2REG(STB0899_S2FEC, FEC_VER_ID_REG); fec_ver = STB0899_READ_S2REG(STB0899_S2FEC, FEC_VER_ID_REG);
if (! (chip_id > 0)) { if (!chip_id) {
dprintk(state->verbose, FE_ERROR, 1, "couldn't find a STB 0899"); dprintk(state->verbose, FE_ERROR, 1, "couldn't find a STB 0899");
return -ENODEV; return -ENODEV;

View File

@ -410,6 +410,7 @@ static int tda10048_set_if(struct dvb_frontend *fe, u32 bw)
struct tda10048_config *config = &state->config; struct tda10048_config *config = &state->config;
int i; int i;
u32 if_freq_khz; u32 if_freq_khz;
u64 sample_freq;
dprintk(1, "%s(bw = %d)\n", __func__, bw); dprintk(1, "%s(bw = %d)\n", __func__, bw);
@ -451,9 +452,11 @@ static int tda10048_set_if(struct dvb_frontend *fe, u32 bw)
dprintk(1, "- pll_pfactor = %d\n", state->pll_pfactor); dprintk(1, "- pll_pfactor = %d\n", state->pll_pfactor);
/* Calculate the sample frequency */ /* Calculate the sample frequency */
state->sample_freq = state->xtal_hz * (state->pll_mfactor + 45); sample_freq = state->xtal_hz;
state->sample_freq /= (state->pll_nfactor + 1); sample_freq *= state->pll_mfactor + 45;
state->sample_freq /= (state->pll_pfactor + 4); do_div(sample_freq, state->pll_nfactor + 1);
do_div(sample_freq, state->pll_pfactor + 4);
state->sample_freq = sample_freq;
dprintk(1, "- sample_freq = %d\n", state->sample_freq); dprintk(1, "- sample_freq = %d\n", state->sample_freq);
/* Update the I/F */ /* Update the I/F */

View File

@ -328,7 +328,7 @@ static int CalcMainPLL(struct tda_state *state, u32 freq)
OscFreq = (u64) freq * (u64) Div; OscFreq = (u64) freq * (u64) Div;
OscFreq *= (u64) 16384; OscFreq *= (u64) 16384;
do_div(OscFreq, (u64)16000000); do_div(OscFreq, 16000000);
MainDiv = OscFreq; MainDiv = OscFreq;
state->m_Regs[MPD] = PostDiv & 0x77; state->m_Regs[MPD] = PostDiv & 0x77;
@ -352,7 +352,7 @@ static int CalcCalPLL(struct tda_state *state, u32 freq)
OscFreq = (u64)freq * (u64)Div; OscFreq = (u64)freq * (u64)Div;
/* CalDiv = u32( OscFreq * 16384 / 16000000 ); */ /* CalDiv = u32( OscFreq * 16384 / 16000000 ); */
OscFreq *= (u64)16384; OscFreq *= (u64)16384;
do_div(OscFreq, (u64)16000000); do_div(OscFreq, 16000000);
CalDiv = OscFreq; CalDiv = OscFreq;
state->m_Regs[CPD] = PostDiv; state->m_Regs[CPD] = PostDiv;

View File

@ -195,6 +195,7 @@ config VIDEO_IMX334
config VIDEO_IMX335 config VIDEO_IMX335
tristate "Sony IMX335 sensor support" tristate "Sony IMX335 sensor support"
depends on OF_GPIO depends on OF_GPIO
select V4L2_CCI_I2C
help help
This is a Video4Linux2 sensor driver for the Sony This is a Video4Linux2 sensor driver for the Sony
IMX335 camera. IMX335 camera.
@ -405,6 +406,7 @@ config VIDEO_OV2740
config VIDEO_OV4689 config VIDEO_OV4689
tristate "OmniVision OV4689 sensor support" tristate "OmniVision OV4689 sensor support"
depends on GPIOLIB depends on GPIOLIB
select V4L2_CCI_I2C
help help
This is a Video4Linux2 sensor-level driver for the OmniVision This is a Video4Linux2 sensor-level driver for the OmniVision
OV4689 camera. OV4689 camera.

View File

@ -1486,7 +1486,7 @@ static int adv7180_probe(struct i2c_client *client)
if (ret) if (ret)
goto err_media_entity_cleanup; goto err_media_entity_cleanup;
if (state->irq) { if (state->irq > 0) {
ret = request_threaded_irq(client->irq, NULL, adv7180_irq, ret = request_threaded_irq(client->irq, NULL, adv7180_irq,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING, IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
KBUILD_MODNAME, state); KBUILD_MODNAME, state);

View File

@ -214,7 +214,7 @@ static int adv748x_hdmi_set_video_timings(struct adv748x_state *state,
* v4l2_subdev_video_ops * v4l2_subdev_video_ops
*/ */
static int adv748x_hdmi_s_dv_timings(struct v4l2_subdev *sd, static int adv748x_hdmi_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
@ -254,7 +254,7 @@ error:
return ret; return ret;
} }
static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
@ -269,7 +269,7 @@ static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
@ -392,9 +392,6 @@ static int adv748x_hdmi_g_pixelaspect(struct v4l2_subdev *sd,
} }
static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = { static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = {
.s_dv_timings = adv748x_hdmi_s_dv_timings,
.g_dv_timings = adv748x_hdmi_g_dv_timings,
.query_dv_timings = adv748x_hdmi_query_dv_timings,
.g_input_status = adv748x_hdmi_g_input_status, .g_input_status = adv748x_hdmi_g_input_status,
.s_stream = adv748x_hdmi_s_stream, .s_stream = adv748x_hdmi_s_stream,
.g_pixelaspect = adv748x_hdmi_g_pixelaspect, .g_pixelaspect = adv748x_hdmi_g_pixelaspect,
@ -413,7 +410,7 @@ static int adv748x_hdmi_propagate_pixelrate(struct adv748x_hdmi *hdmi)
if (!tx) if (!tx)
return -ENOLINK; return -ENOLINK;
adv748x_hdmi_query_dv_timings(&hdmi->sd, &timings); adv748x_hdmi_query_dv_timings(&hdmi->sd, 0, &timings);
return adv748x_csi2_set_pixelrate(tx, timings.bt.pixelclock); return adv748x_csi2_set_pixelrate(tx, timings.bt.pixelclock);
} }
@ -610,6 +607,9 @@ static const struct v4l2_subdev_pad_ops adv748x_pad_ops_hdmi = {
.get_fmt = adv748x_hdmi_get_format, .get_fmt = adv748x_hdmi_get_format,
.get_edid = adv748x_hdmi_get_edid, .get_edid = adv748x_hdmi_get_edid,
.set_edid = adv748x_hdmi_set_edid, .set_edid = adv748x_hdmi_set_edid,
.s_dv_timings = adv748x_hdmi_s_dv_timings,
.g_dv_timings = adv748x_hdmi_g_dv_timings,
.query_dv_timings = adv748x_hdmi_query_dv_timings,
.dv_timings_cap = adv748x_hdmi_dv_timings_cap, .dv_timings_cap = adv748x_hdmi_dv_timings_cap,
.enum_dv_timings = adv748x_hdmi_enum_dv_timings, .enum_dv_timings = adv748x_hdmi_enum_dv_timings,
}; };
@ -734,7 +734,7 @@ int adv748x_hdmi_init(struct adv748x_hdmi *hdmi)
struct v4l2_dv_timings cea1280x720 = V4L2_DV_BT_CEA_1280X720P30; struct v4l2_dv_timings cea1280x720 = V4L2_DV_BT_CEA_1280X720P30;
int ret; int ret;
adv748x_hdmi_s_dv_timings(&hdmi->sd, &cea1280x720); adv748x_hdmi_s_dv_timings(&hdmi->sd, 0, &cea1280x720);
/* Initialise a default 16:9 aspect ratio */ /* Initialise a default 16:9 aspect ratio */
hdmi->aspect_ratio.numerator = 16; hdmi->aspect_ratio.numerator = 16;

View File

@ -995,8 +995,8 @@ static int adv7511_s_stream(struct v4l2_subdev *sd, int enable)
return 0; return 0;
} }
static int adv7511_s_dv_timings(struct v4l2_subdev *sd, static int adv7511_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state *state = get_adv7511_state(sd);
struct v4l2_bt_timings *bt = &timings->bt; struct v4l2_bt_timings *bt = &timings->bt;
@ -1004,6 +1004,9 @@ static int adv7511_s_dv_timings(struct v4l2_subdev *sd,
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
/* quick sanity check */ /* quick sanity check */
if (!v4l2_valid_dv_timings(timings, &adv7511_timings_cap, NULL, NULL)) if (!v4l2_valid_dv_timings(timings, &adv7511_timings_cap, NULL, NULL))
return -EINVAL; return -EINVAL;
@ -1042,13 +1045,16 @@ static int adv7511_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int adv7511_g_dv_timings(struct v4l2_subdev *sd, static int adv7511_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state *state = get_adv7511_state(sd);
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
if (!timings) if (!timings)
return -EINVAL; return -EINVAL;
@ -1078,8 +1084,6 @@ static int adv7511_dv_timings_cap(struct v4l2_subdev *sd,
static const struct v4l2_subdev_video_ops adv7511_video_ops = { static const struct v4l2_subdev_video_ops adv7511_video_ops = {
.s_stream = adv7511_s_stream, .s_stream = adv7511_s_stream,
.s_dv_timings = adv7511_s_dv_timings,
.g_dv_timings = adv7511_g_dv_timings,
}; };
/* ------------------------------ AUDIO OPS ------------------------------ */ /* ------------------------------ AUDIO OPS ------------------------------ */
@ -1403,6 +1407,8 @@ static const struct v4l2_subdev_pad_ops adv7511_pad_ops = {
.enum_mbus_code = adv7511_enum_mbus_code, .enum_mbus_code = adv7511_enum_mbus_code,
.get_fmt = adv7511_get_fmt, .get_fmt = adv7511_get_fmt,
.set_fmt = adv7511_set_fmt, .set_fmt = adv7511_set_fmt,
.s_dv_timings = adv7511_s_dv_timings,
.g_dv_timings = adv7511_g_dv_timings,
.enum_dv_timings = adv7511_enum_dv_timings, .enum_dv_timings = adv7511_enum_dv_timings,
.dv_timings_cap = adv7511_dv_timings_cap, .dv_timings_cap = adv7511_dv_timings_cap,
}; };

View File

@ -1557,8 +1557,8 @@ static unsigned int adv76xx_read_hdmi_pixelclock(struct v4l2_subdev *sd)
return freq; return freq;
} }
static int adv76xx_query_dv_timings(struct v4l2_subdev *sd, static int adv76xx_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv76xx_state *state = to_state(sd); struct adv76xx_state *state = to_state(sd);
const struct adv76xx_chip_info *info = state->info; const struct adv76xx_chip_info *info = state->info;
@ -1687,8 +1687,8 @@ found:
return 0; return 0;
} }
static int adv76xx_s_dv_timings(struct v4l2_subdev *sd, static int adv76xx_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv76xx_state *state = to_state(sd); struct adv76xx_state *state = to_state(sd);
struct v4l2_bt_timings *bt; struct v4l2_bt_timings *bt;
@ -1730,8 +1730,8 @@ static int adv76xx_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int adv76xx_g_dv_timings(struct v4l2_subdev *sd, static int adv76xx_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv76xx_state *state = to_state(sd); struct adv76xx_state *state = to_state(sd);
@ -2607,7 +2607,7 @@ static int adv76xx_log_status(struct v4l2_subdev *sd)
stdi.lcf, stdi.bl, stdi.lcvs, stdi.lcf, stdi.bl, stdi.lcvs,
stdi.interlaced ? "interlaced" : "progressive", stdi.interlaced ? "interlaced" : "progressive",
stdi.hs_pol, stdi.vs_pol); stdi.hs_pol, stdi.vs_pol);
if (adv76xx_query_dv_timings(sd, &timings)) if (adv76xx_query_dv_timings(sd, 0, &timings))
v4l2_info(sd, "No video detected\n"); v4l2_info(sd, "No video detected\n");
else else
v4l2_print_dv_timings(sd->name, "Detected format: ", v4l2_print_dv_timings(sd->name, "Detected format: ",
@ -2726,9 +2726,6 @@ static const struct v4l2_subdev_core_ops adv76xx_core_ops = {
static const struct v4l2_subdev_video_ops adv76xx_video_ops = { static const struct v4l2_subdev_video_ops adv76xx_video_ops = {
.s_routing = adv76xx_s_routing, .s_routing = adv76xx_s_routing,
.g_input_status = adv76xx_g_input_status, .g_input_status = adv76xx_g_input_status,
.s_dv_timings = adv76xx_s_dv_timings,
.g_dv_timings = adv76xx_g_dv_timings,
.query_dv_timings = adv76xx_query_dv_timings,
}; };
static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = { static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = {
@ -2738,6 +2735,9 @@ static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = {
.set_fmt = adv76xx_set_format, .set_fmt = adv76xx_set_format,
.get_edid = adv76xx_get_edid, .get_edid = adv76xx_get_edid,
.set_edid = adv76xx_set_edid, .set_edid = adv76xx_set_edid,
.s_dv_timings = adv76xx_s_dv_timings,
.g_dv_timings = adv76xx_g_dv_timings,
.query_dv_timings = adv76xx_query_dv_timings,
.dv_timings_cap = adv76xx_dv_timings_cap, .dv_timings_cap = adv76xx_dv_timings_cap,
.enum_dv_timings = adv76xx_enum_dv_timings, .enum_dv_timings = adv76xx_enum_dv_timings,
}; };

View File

@ -1518,7 +1518,7 @@ static void adv7842_fill_optional_dv_timings_fields(struct v4l2_subdev *sd,
timings->bt.flags |= V4L2_DV_FL_CAN_DETECT_REDUCED_FPS; timings->bt.flags |= V4L2_DV_FL_CAN_DETECT_REDUCED_FPS;
} }
static int adv7842_query_dv_timings(struct v4l2_subdev *sd, static int adv7842_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv7842_state *state = to_state(sd); struct adv7842_state *state = to_state(sd);
@ -1527,6 +1527,9 @@ static int adv7842_query_dv_timings(struct v4l2_subdev *sd,
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
memset(timings, 0, sizeof(struct v4l2_dv_timings)); memset(timings, 0, sizeof(struct v4l2_dv_timings));
/* SDP block */ /* SDP block */
@ -1643,7 +1646,7 @@ found:
return 0; return 0;
} }
static int adv7842_s_dv_timings(struct v4l2_subdev *sd, static int adv7842_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv7842_state *state = to_state(sd); struct adv7842_state *state = to_state(sd);
@ -1652,6 +1655,9 @@ static int adv7842_s_dv_timings(struct v4l2_subdev *sd,
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
if (state->mode == ADV7842_MODE_SDP) if (state->mode == ADV7842_MODE_SDP)
return -ENODATA; return -ENODATA;
@ -1689,11 +1695,14 @@ static int adv7842_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int adv7842_g_dv_timings(struct v4l2_subdev *sd, static int adv7842_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct adv7842_state *state = to_state(sd); struct adv7842_state *state = to_state(sd);
if (pad != 0)
return -EINVAL;
if (state->mode == ADV7842_MODE_SDP) if (state->mode == ADV7842_MODE_SDP)
return -ENODATA; return -ENODATA;
*timings = state->timings; *timings = state->timings;
@ -2780,7 +2789,7 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd)
"interlaced" : "progressive", "interlaced" : "progressive",
hs_pol, vs_pol); hs_pol, vs_pol);
} }
if (adv7842_query_dv_timings(sd, &timings)) if (adv7842_query_dv_timings(sd, 0, &timings))
v4l2_info(sd, "No video detected\n"); v4l2_info(sd, "No video detected\n");
else else
v4l2_print_dv_timings(sd->name, "Detected format: ", v4l2_print_dv_timings(sd->name, "Detected format: ",
@ -3226,7 +3235,7 @@ static int adv7842_command_ram_test(struct v4l2_subdev *sd)
memset(&state->timings, 0, sizeof(struct v4l2_dv_timings)); memset(&state->timings, 0, sizeof(struct v4l2_dv_timings));
adv7842_s_dv_timings(sd, &timings); adv7842_s_dv_timings(sd, 0, &timings);
return ret; return ret;
} }
@ -3298,9 +3307,6 @@ static const struct v4l2_subdev_video_ops adv7842_video_ops = {
.s_routing = adv7842_s_routing, .s_routing = adv7842_s_routing,
.querystd = adv7842_querystd, .querystd = adv7842_querystd,
.g_input_status = adv7842_g_input_status, .g_input_status = adv7842_g_input_status,
.s_dv_timings = adv7842_s_dv_timings,
.g_dv_timings = adv7842_g_dv_timings,
.query_dv_timings = adv7842_query_dv_timings,
}; };
static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { static const struct v4l2_subdev_pad_ops adv7842_pad_ops = {
@ -3309,6 +3315,9 @@ static const struct v4l2_subdev_pad_ops adv7842_pad_ops = {
.set_fmt = adv7842_set_format, .set_fmt = adv7842_set_format,
.get_edid = adv7842_get_edid, .get_edid = adv7842_get_edid,
.set_edid = adv7842_set_edid, .set_edid = adv7842_set_edid,
.s_dv_timings = adv7842_s_dv_timings,
.g_dv_timings = adv7842_g_dv_timings,
.query_dv_timings = adv7842_query_dv_timings,
.enum_dv_timings = adv7842_enum_dv_timings, .enum_dv_timings = adv7842_enum_dv_timings,
.dv_timings_cap = adv7842_dv_timings_cap, .dv_timings_cap = adv7842_dv_timings_cap,
}; };

View File

@ -310,8 +310,8 @@ module_i2c_driver(dw9714_i2c_driver);
MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
MODULE_AUTHOR("Jian Xu Zheng"); MODULE_AUTHOR("Jian Xu Zheng");
MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>"); MODULE_AUTHOR("Yuning Pu");
MODULE_AUTHOR("Jouni Ukkonen <jouni.ukkonen@intel.com>"); MODULE_AUTHOR("Jouni Ukkonen");
MODULE_AUTHOR("Tommi Franttila <tommi.franttila@intel.com>"); MODULE_AUTHOR("Tommi Franttila");
MODULE_DESCRIPTION("DW9714 VCM driver"); MODULE_DESCRIPTION("DW9714 VCM driver");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");

View File

@ -1475,7 +1475,7 @@ err_mutex:
return ret; return ret;
} }
static void __exit et8ek8_remove(struct i2c_client *client) static void et8ek8_remove(struct i2c_client *client)
{ {
struct v4l2_subdev *subdev = i2c_get_clientdata(client); struct v4l2_subdev *subdev = i2c_get_clientdata(client);
struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev);
@ -1517,7 +1517,7 @@ static struct i2c_driver et8ek8_i2c_driver = {
.of_match_table = et8ek8_of_table, .of_match_table = et8ek8_of_table,
}, },
.probe = et8ek8_probe, .probe = et8ek8_probe,
.remove = __exit_p(et8ek8_remove), .remove = et8ek8_remove,
.id_table = et8ek8_id_table, .id_table = et8ek8_id_table,
}; };

View File

@ -3,10 +3,13 @@
#include <asm/unaligned.h> #include <asm/unaligned.h>
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <media/v4l2-ctrls.h> #include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h> #include <media/v4l2-fwnode.h>
@ -633,6 +636,11 @@ struct hi556 {
struct v4l2_ctrl *hblank; struct v4l2_ctrl *hblank;
struct v4l2_ctrl *exposure; struct v4l2_ctrl *exposure;
/* GPIOs, clocks, etc. */
struct gpio_desc *reset_gpio;
struct clk *clk;
struct regulator *avdd;
/* Current mode */ /* Current mode */
const struct hi556_mode *cur_mode; const struct hi556_mode *cur_mode;
@ -1206,8 +1214,18 @@ static int hi556_check_hwcfg(struct device *dev)
int ret = 0; int ret = 0;
unsigned int i, j; 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 = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
fwnode_handle_put(ep);
if (ret)
return ret;
ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk);
if (ret) { if (ret) {
@ -1220,15 +1238,6 @@ static int hi556_check_hwcfg(struct device *dev)
return -EINVAL; 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)
return ret;
if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2) { if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2) {
dev_err(dev, "number of CSI2 data lanes %d is not supported", dev_err(dev, "number of CSI2 data lanes %d is not supported",
bus_cfg.bus.mipi_csi2.num_data_lanes); bus_cfg.bus.mipi_csi2.num_data_lanes);
@ -1275,6 +1284,47 @@ static void hi556_remove(struct i2c_client *client)
mutex_destroy(&hi556->mutex); 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);
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;
}
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;
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;
}
static int hi556_probe(struct i2c_client *client) static int hi556_probe(struct i2c_client *client)
{ {
struct hi556 *hi556; struct hi556 *hi556;
@ -1294,12 +1344,35 @@ static int hi556_probe(struct i2c_client *client)
v4l2_i2c_subdev_init(&hi556->sd, client, &hi556_subdev_ops); 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");
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");
/* 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); full_power = acpi_dev_state_d0(&client->dev);
if (full_power) { 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); ret = hi556_identify_module(hi556);
if (ret) { if (ret) {
dev_err(&client->dev, "failed to find sensor: %d", ret); dev_err(&client->dev, "failed to find sensor: %d", ret);
return ret; goto probe_error_power_off;
} }
} }
@ -1344,9 +1417,16 @@ probe_error_v4l2_ctrl_handler_free:
v4l2_ctrl_handler_free(hi556->sd.ctrl_handler); v4l2_ctrl_handler_free(hi556->sd.ctrl_handler);
mutex_destroy(&hi556->mutex); mutex_destroy(&hi556->mutex);
probe_error_power_off:
if (full_power)
hi556_suspend(&client->dev);
return ret; return ret;
} }
static DEFINE_RUNTIME_DEV_PM_OPS(hi556_pm_ops, hi556_suspend, hi556_resume,
NULL);
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
static const struct acpi_device_id hi556_acpi_ids[] = { static const struct acpi_device_id hi556_acpi_ids[] = {
{"INT3537"}, {"INT3537"},
@ -1360,6 +1440,7 @@ static struct i2c_driver hi556_i2c_driver = {
.driver = { .driver = {
.name = "hi556", .name = "hi556",
.acpi_match_table = ACPI_PTR(hi556_acpi_ids), .acpi_match_table = ACPI_PTR(hi556_acpi_ids),
.pm = pm_sleep_ptr(&hi556_pm_ops),
}, },
.probe = hi556_probe, .probe = hi556_probe,
.remove = hi556_remove, .remove = hi556_remove,

View File

@ -1114,6 +1114,7 @@ free_ctrl:
v4l2_ctrl_handler_free(&imx214->ctrls); v4l2_ctrl_handler_free(&imx214->ctrls);
error_power_off: error_power_off:
pm_runtime_disable(imx214->dev); pm_runtime_disable(imx214->dev);
regulator_bulk_disable(IMX214_NUM_SUPPLIES, imx214->supplies);
return ret; return ret;
} }

View File

@ -551,8 +551,7 @@ static int imx219_init_controls(struct imx219 *imx219)
if (ctrl_hdlr->error) { if (ctrl_hdlr->error) {
ret = ctrl_hdlr->error; ret = ctrl_hdlr->error;
dev_err(&client->dev, "%s control init failed (%d)\n", dev_err_probe(&client->dev, ret, "Control init failed\n");
__func__, ret);
goto error; goto error;
} }
@ -1024,17 +1023,15 @@ static int imx219_identify_module(struct imx219 *imx219)
u64 val; u64 val;
ret = cci_read(imx219->regmap, IMX219_REG_CHIP_ID, &val, NULL); ret = cci_read(imx219->regmap, IMX219_REG_CHIP_ID, &val, NULL);
if (ret) { if (ret)
dev_err(&client->dev, "failed to read chip id %x\n", return dev_err_probe(&client->dev, ret,
IMX219_CHIP_ID); "failed to read chip id %x\n",
return ret; IMX219_CHIP_ID);
}
if (val != IMX219_CHIP_ID) { if (val != IMX219_CHIP_ID)
dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", return dev_err_probe(&client->dev, -EIO,
IMX219_CHIP_ID, val); "chip id mismatch: %x!=%llx\n",
return -EIO; IMX219_CHIP_ID, val);
}
return 0; return 0;
} }
@ -1048,35 +1045,36 @@ static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219)
int ret = -EINVAL; int ret = -EINVAL;
endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
if (!endpoint) { if (!endpoint)
dev_err(dev, "endpoint node not found\n"); return dev_err_probe(dev, -EINVAL, "endpoint node not found\n");
return -EINVAL;
}
if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
dev_err(dev, "could not parse endpoint\n"); dev_err_probe(dev, -EINVAL, "could not parse endpoint\n");
goto error_out; goto error_out;
} }
/* Check the number of MIPI CSI2 data lanes */ /* Check the number of MIPI CSI2 data lanes */
if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2 && if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2 &&
ep_cfg.bus.mipi_csi2.num_data_lanes != 4) { ep_cfg.bus.mipi_csi2.num_data_lanes != 4) {
dev_err(dev, "only 2 or 4 data lanes are currently supported\n"); dev_err_probe(dev, -EINVAL,
"only 2 or 4 data lanes are currently supported\n");
goto error_out; goto error_out;
} }
imx219->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes; imx219->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes;
/* Check the link frequency set in device tree */ /* Check the link frequency set in device tree */
if (!ep_cfg.nr_of_link_frequencies) { if (!ep_cfg.nr_of_link_frequencies) {
dev_err(dev, "link-frequency property not found in DT\n"); dev_err_probe(dev, -EINVAL,
"link-frequency property not found in DT\n");
goto error_out; goto error_out;
} }
if (ep_cfg.nr_of_link_frequencies != 1 || if (ep_cfg.nr_of_link_frequencies != 1 ||
(ep_cfg.link_frequencies[0] != ((imx219->lanes == 2) ? (ep_cfg.link_frequencies[0] != ((imx219->lanes == 2) ?
IMX219_DEFAULT_LINK_FREQ : IMX219_DEFAULT_LINK_FREQ_4LANE))) { IMX219_DEFAULT_LINK_FREQ : IMX219_DEFAULT_LINK_FREQ_4LANE))) {
dev_err(dev, "Link frequency not supported: %lld\n", dev_err_probe(dev, -EINVAL,
ep_cfg.link_frequencies[0]); "Link frequency not supported: %lld\n",
ep_cfg.link_frequencies[0]);
goto error_out; goto error_out;
} }
@ -1107,31 +1105,25 @@ static int imx219_probe(struct i2c_client *client)
return -EINVAL; return -EINVAL;
imx219->regmap = devm_cci_regmap_init_i2c(client, 16); imx219->regmap = devm_cci_regmap_init_i2c(client, 16);
if (IS_ERR(imx219->regmap)) { if (IS_ERR(imx219->regmap))
ret = PTR_ERR(imx219->regmap); return dev_err_probe(dev, PTR_ERR(imx219->regmap),
dev_err(dev, "failed to initialize CCI: %d\n", ret); "failed to initialize CCI\n");
return ret;
}
/* Get system clock (xclk) */ /* Get system clock (xclk) */
imx219->xclk = devm_clk_get(dev, NULL); imx219->xclk = devm_clk_get(dev, NULL);
if (IS_ERR(imx219->xclk)) { if (IS_ERR(imx219->xclk))
dev_err(dev, "failed to get xclk\n"); return dev_err_probe(dev, PTR_ERR(imx219->xclk),
return PTR_ERR(imx219->xclk); "failed to get xclk\n");
}
imx219->xclk_freq = clk_get_rate(imx219->xclk); imx219->xclk_freq = clk_get_rate(imx219->xclk);
if (imx219->xclk_freq != IMX219_XCLK_FREQ) { if (imx219->xclk_freq != IMX219_XCLK_FREQ)
dev_err(dev, "xclk frequency not supported: %d Hz\n", return dev_err_probe(dev, -EINVAL,
imx219->xclk_freq); "xclk frequency not supported: %d Hz\n",
return -EINVAL; imx219->xclk_freq);
}
ret = imx219_get_regulators(imx219); ret = imx219_get_regulators(imx219);
if (ret) { if (ret)
dev_err(dev, "failed to get regulators\n"); return dev_err_probe(dev, ret, "failed to get regulators\n");
return ret;
}
/* Request optional enable pin */ /* Request optional enable pin */
imx219->reset_gpio = devm_gpiod_get_optional(dev, "reset", imx219->reset_gpio = devm_gpiod_get_optional(dev, "reset",
@ -1183,20 +1175,21 @@ static int imx219_probe(struct i2c_client *client)
ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad); ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad);
if (ret) { if (ret) {
dev_err(dev, "failed to init entity pads: %d\n", ret); dev_err_probe(dev, ret, "failed to init entity pads\n");
goto error_handler_free; goto error_handler_free;
} }
imx219->sd.state_lock = imx219->ctrl_handler.lock; imx219->sd.state_lock = imx219->ctrl_handler.lock;
ret = v4l2_subdev_init_finalize(&imx219->sd); ret = v4l2_subdev_init_finalize(&imx219->sd);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "subdev init error: %d\n", ret); dev_err_probe(dev, ret, "subdev init error\n");
goto error_media_entity; goto error_media_entity;
} }
ret = v4l2_async_register_subdev_sensor(&imx219->sd); ret = v4l2_async_register_subdev_sensor(&imx219->sd);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to register sensor sub-device: %d\n", ret); dev_err_probe(dev, ret,
"failed to register sensor sub-device\n");
goto error_subdev_cleanup; goto error_subdev_cleanup;
} }

View File

@ -11,57 +11,108 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <media/v4l2-cci.h>
#include <media/v4l2-ctrls.h> #include <media/v4l2-ctrls.h>
#include <media/v4l2-fwnode.h> #include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h> #include <media/v4l2-subdev.h>
/* Streaming Mode */ /* Streaming Mode */
#define IMX335_REG_MODE_SELECT 0x3000 #define IMX335_REG_MODE_SELECT CCI_REG8(0x3000)
#define IMX335_MODE_STANDBY 0x01 #define IMX335_MODE_STANDBY 0x01
#define IMX335_MODE_STREAMING 0x00 #define IMX335_MODE_STREAMING 0x00
/* Lines per frame */
#define IMX335_REG_LPFR 0x3030
/* Chip ID */
#define IMX335_REG_ID 0x3912
#define IMX335_ID 0x00
/* Exposure control */
#define IMX335_REG_SHUTTER 0x3058
#define IMX335_EXPOSURE_MIN 1
#define IMX335_EXPOSURE_OFFSET 9
#define IMX335_EXPOSURE_STEP 1
#define IMX335_EXPOSURE_DEFAULT 0x0648
/* Analog gain control */
#define IMX335_REG_AGAIN 0x30e8
#define IMX335_AGAIN_MIN 0
#define IMX335_AGAIN_MAX 240
#define IMX335_AGAIN_STEP 1
#define IMX335_AGAIN_DEFAULT 0
/* Group hold register */ /* Group hold register */
#define IMX335_REG_HOLD 0x3001 #define IMX335_REG_HOLD CCI_REG8(0x3001)
#define IMX335_REG_MASTER_MODE CCI_REG8(0x3002)
#define IMX335_REG_BCWAIT_TIME CCI_REG8(0x300c)
#define IMX335_REG_CPWAIT_TIME CCI_REG8(0x300d)
#define IMX335_REG_WINMODE CCI_REG8(0x3018)
#define IMX335_REG_HTRIMMING_START CCI_REG16_LE(0x302c)
#define IMX335_REG_HNUM CCI_REG8(0x302e)
/* Lines per frame */
#define IMX335_REG_VMAX CCI_REG24_LE(0x3030)
#define IMX335_REG_OPB_SIZE_V CCI_REG8(0x304c)
#define IMX335_REG_ADBIT CCI_REG8(0x3050)
#define IMX335_REG_Y_OUT_SIZE CCI_REG16_LE(0x3056)
#define IMX335_REG_SHUTTER CCI_REG24_LE(0x3058)
#define IMX335_EXPOSURE_MIN 1
#define IMX335_EXPOSURE_OFFSET 9
#define IMX335_EXPOSURE_STEP 1
#define IMX335_EXPOSURE_DEFAULT 0x0648
#define IMX335_REG_AREA3_ST_ADR_1 CCI_REG16_LE(0x3074)
#define IMX335_REG_AREA3_WIDTH_1 CCI_REG16_LE(0x3076)
/* Analog and Digital gain control */
#define IMX335_REG_GAIN CCI_REG8(0x30e8)
#define IMX335_AGAIN_MIN 0
#define IMX335_AGAIN_MAX 100
#define IMX335_AGAIN_STEP 1
#define IMX335_AGAIN_DEFAULT 0
#define IMX335_REG_TPG_TESTCLKEN CCI_REG8(0x3148)
#define IMX335_REG_INCLKSEL1 CCI_REG16_LE(0x314c)
#define IMX335_REG_INCLKSEL2 CCI_REG8(0x315a)
#define IMX335_REG_INCLKSEL3 CCI_REG8(0x3168)
#define IMX335_REG_INCLKSEL4 CCI_REG8(0x316a)
#define IMX335_REG_MDBIT CCI_REG8(0x319d)
#define IMX335_REG_SYSMODE CCI_REG8(0x319e)
#define IMX335_REG_XVS_XHS_DRV CCI_REG8(0x31a1)
/* Test pattern generator */ /* Test pattern generator */
#define IMX335_REG_TPG 0x329e #define IMX335_REG_TPG_DIG_CLP_MODE CCI_REG8(0x3280)
#define IMX335_TPG_ALL_000 0 #define IMX335_REG_TPG_EN_DUOUT CCI_REG8(0x329c)
#define IMX335_TPG_ALL_FFF 1 #define IMX335_REG_TPG CCI_REG8(0x329e)
#define IMX335_TPG_ALL_555 2 #define IMX335_TPG_ALL_000 0
#define IMX335_TPG_ALL_AAA 3 #define IMX335_TPG_ALL_FFF 1
#define IMX335_TPG_TOG_555_AAA 4 #define IMX335_TPG_ALL_555 2
#define IMX335_TPG_TOG_AAA_555 5 #define IMX335_TPG_ALL_AAA 3
#define IMX335_TPG_TOG_000_555 6 #define IMX335_TPG_TOG_555_AAA 4
#define IMX335_TPG_TOG_555_000 7 #define IMX335_TPG_TOG_AAA_555 5
#define IMX335_TPG_TOG_000_FFF 8 #define IMX335_TPG_TOG_000_555 6
#define IMX335_TPG_TOG_FFF_000 9 #define IMX335_TPG_TOG_555_000 7
#define IMX335_TPG_H_COLOR_BARS 10 #define IMX335_TPG_TOG_000_FFF 8
#define IMX335_TPG_V_COLOR_BARS 11 #define IMX335_TPG_TOG_FFF_000 9
#define IMX335_TPG_H_COLOR_BARS 10
#define IMX335_TPG_V_COLOR_BARS 11
#define IMX335_REG_TPG_COLORWIDTH CCI_REG8(0x32a0)
#define IMX335_REG_BLKLEVEL CCI_REG16_LE(0x3302)
#define IMX335_REG_WRJ_OPEN CCI_REG8(0x336c)
#define IMX335_REG_ADBIT1 CCI_REG16_LE(0x341c)
/* Chip ID */
#define IMX335_REG_ID CCI_REG8(0x3912)
#define IMX335_ID 0x00
/* Data Lanes */
#define IMX335_REG_LANEMODE CCI_REG8(0x3a01)
#define IMX335_2LANE 1
#define IMX335_4LANE 3
#define IMX335_REG_TCLKPOST CCI_REG16_LE(0x3a18)
#define IMX335_REG_TCLKPREPARE CCI_REG16_LE(0x3a1a)
#define IMX335_REG_TCLK_TRAIL CCI_REG16_LE(0x3a1c)
#define IMX335_REG_TCLK_ZERO CCI_REG16_LE(0x3a1e)
#define IMX335_REG_THS_PREPARE CCI_REG16_LE(0x3a20)
#define IMX335_REG_THS_ZERO CCI_REG16_LE(0x3a22)
#define IMX335_REG_THS_TRAIL CCI_REG16_LE(0x3a24)
#define IMX335_REG_THS_EXIT CCI_REG16_LE(0x3a26)
#define IMX335_REG_TPLX CCI_REG16_LE(0x3a28)
/* Input clock rate */ /* Input clock rate */
#define IMX335_INCLK_RATE 24000000 #define IMX335_INCLK_RATE 24000000
/* CSI2 HW configuration */ /* CSI2 HW configuration */
#define IMX335_LINK_FREQ_594MHz 594000000LL #define IMX335_LINK_FREQ_594MHz 594000000LL
@ -69,9 +120,6 @@
#define IMX335_NUM_DATA_LANES 4 #define IMX335_NUM_DATA_LANES 4
#define IMX335_REG_MIN 0x00
#define IMX335_REG_MAX 0xfffff
/* IMX335 native and active pixel array size. */ /* IMX335 native and active pixel array size. */
#define IMX335_NATIVE_WIDTH 2616U #define IMX335_NATIVE_WIDTH 2616U
#define IMX335_NATIVE_HEIGHT 1964U #define IMX335_NATIVE_HEIGHT 1964U
@ -80,16 +128,6 @@
#define IMX335_PIXEL_ARRAY_WIDTH 2592U #define IMX335_PIXEL_ARRAY_WIDTH 2592U
#define IMX335_PIXEL_ARRAY_HEIGHT 1944U #define IMX335_PIXEL_ARRAY_HEIGHT 1944U
/**
* struct imx335_reg - imx335 sensor register
* @address: Register address
* @val: Register value
*/
struct imx335_reg {
u16 address;
u8 val;
};
/** /**
* struct imx335_reg_list - imx335 sensor register list * struct imx335_reg_list - imx335 sensor register list
* @num_of_regs: Number of registers in the list * @num_of_regs: Number of registers in the list
@ -97,7 +135,7 @@ struct imx335_reg {
*/ */
struct imx335_reg_list { struct imx335_reg_list {
u32 num_of_regs; u32 num_of_regs;
const struct imx335_reg *regs; const struct cci_reg_sequence *regs;
}; };
static const char * const imx335_supply_name[] = { static const char * const imx335_supply_name[] = {
@ -138,6 +176,7 @@ struct imx335_mode {
* @pad: Media pad. Only one pad supported * @pad: Media pad. Only one pad supported
* @reset_gpio: Sensor reset gpio * @reset_gpio: Sensor reset gpio
* @supplies: Regulator supplies to handle power control * @supplies: Regulator supplies to handle power control
* @cci: CCI register map
* @inclk: Sensor input clock * @inclk: Sensor input clock
* @ctrl_handler: V4L2 control handler * @ctrl_handler: V4L2 control handler
* @link_freq_ctrl: Pointer to link frequency control * @link_freq_ctrl: Pointer to link frequency control
@ -147,6 +186,7 @@ struct imx335_mode {
* @exp_ctrl: Pointer to exposure control * @exp_ctrl: Pointer to exposure control
* @again_ctrl: Pointer to analog gain control * @again_ctrl: Pointer to analog gain control
* @vblank: Vertical blanking in lines * @vblank: Vertical blanking in lines
* @lane_mode: Mode for number of connected data lanes
* @cur_mode: Pointer to current selected sensor mode * @cur_mode: Pointer to current selected sensor mode
* @mutex: Mutex for serializing sensor controls * @mutex: Mutex for serializing sensor controls
* @link_freq_bitmap: Menu bitmap for link_freq_ctrl * @link_freq_bitmap: Menu bitmap for link_freq_ctrl
@ -159,6 +199,7 @@ struct imx335 {
struct media_pad pad; struct media_pad pad;
struct gpio_desc *reset_gpio; struct gpio_desc *reset_gpio;
struct regulator_bulk_data supplies[ARRAY_SIZE(imx335_supply_name)]; struct regulator_bulk_data supplies[ARRAY_SIZE(imx335_supply_name)];
struct regmap *cci;
struct clk *inclk; struct clk *inclk;
struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl_handler ctrl_handler;
@ -171,6 +212,7 @@ struct imx335 {
struct v4l2_ctrl *again_ctrl; struct v4l2_ctrl *again_ctrl;
}; };
u32 vblank; u32 vblank;
u32 lane_mode;
const struct imx335_mode *cur_mode; const struct imx335_mode *cur_mode;
struct mutex mutex; struct mutex mutex;
unsigned long link_freq_bitmap; unsigned long link_freq_bitmap;
@ -210,140 +252,135 @@ static const int imx335_tpg_val[] = {
}; };
/* Sensor mode registers */ /* Sensor mode registers */
static const struct imx335_reg mode_2592x1940_regs[] = { static const struct cci_reg_sequence mode_2592x1940_regs[] = {
{0x3000, 0x01}, { IMX335_REG_MODE_SELECT, IMX335_MODE_STANDBY },
{0x3002, 0x00}, { IMX335_REG_MASTER_MODE, 0x00 },
{0x3018, 0x04}, { IMX335_REG_WINMODE, 0x04 },
{0x302c, 0x3c}, { IMX335_REG_HTRIMMING_START, 48 },
{0x302e, 0x20}, { IMX335_REG_HNUM, 2592 },
{0x3056, 0x94}, { IMX335_REG_Y_OUT_SIZE, 1944 },
{0x3074, 0xc8}, { IMX335_REG_AREA3_ST_ADR_1, 176 },
{0x3076, 0x28}, { IMX335_REG_AREA3_WIDTH_1, 3928 },
{0x304c, 0x00}, { IMX335_REG_OPB_SIZE_V, 0 },
{0x31a1, 0x00}, { IMX335_REG_XVS_XHS_DRV, 0x00 },
{0x3288, 0x21}, { CCI_REG8(0x3288), 0x21 },
{0x328a, 0x02}, { CCI_REG8(0x328a), 0x02 },
{0x3414, 0x05}, { CCI_REG8(0x3414), 0x05 },
{0x3416, 0x18}, { CCI_REG8(0x3416), 0x18 },
{0x3648, 0x01}, { CCI_REG8(0x3648), 0x01 },
{0x364a, 0x04}, { CCI_REG8(0x364a), 0x04 },
{0x364c, 0x04}, { CCI_REG8(0x364c), 0x04 },
{0x3678, 0x01}, { CCI_REG8(0x3678), 0x01 },
{0x367c, 0x31}, { CCI_REG8(0x367c), 0x31 },
{0x367e, 0x31}, { CCI_REG8(0x367e), 0x31 },
{0x3706, 0x10}, { CCI_REG8(0x3706), 0x10 },
{0x3708, 0x03}, { CCI_REG8(0x3708), 0x03 },
{0x3714, 0x02}, { CCI_REG8(0x3714), 0x02 },
{0x3715, 0x02}, { CCI_REG8(0x3715), 0x02 },
{0x3716, 0x01}, { CCI_REG8(0x3716), 0x01 },
{0x3717, 0x03}, { CCI_REG8(0x3717), 0x03 },
{0x371c, 0x3d}, { CCI_REG8(0x371c), 0x3d },
{0x371d, 0x3f}, { CCI_REG8(0x371d), 0x3f },
{0x372c, 0x00}, { CCI_REG8(0x372c), 0x00 },
{0x372d, 0x00}, { CCI_REG8(0x372d), 0x00 },
{0x372e, 0x46}, { CCI_REG8(0x372e), 0x46 },
{0x372f, 0x00}, { CCI_REG8(0x372f), 0x00 },
{0x3730, 0x89}, { CCI_REG8(0x3730), 0x89 },
{0x3731, 0x00}, { CCI_REG8(0x3731), 0x00 },
{0x3732, 0x08}, { CCI_REG8(0x3732), 0x08 },
{0x3733, 0x01}, { CCI_REG8(0x3733), 0x01 },
{0x3734, 0xfe}, { CCI_REG8(0x3734), 0xfe },
{0x3735, 0x05}, { CCI_REG8(0x3735), 0x05 },
{0x3740, 0x02}, { CCI_REG8(0x3740), 0x02 },
{0x375d, 0x00}, { CCI_REG8(0x375d), 0x00 },
{0x375e, 0x00}, { CCI_REG8(0x375e), 0x00 },
{0x375f, 0x11}, { CCI_REG8(0x375f), 0x11 },
{0x3760, 0x01}, { CCI_REG8(0x3760), 0x01 },
{0x3768, 0x1b}, { CCI_REG8(0x3768), 0x1b },
{0x3769, 0x1b}, { CCI_REG8(0x3769), 0x1b },
{0x376a, 0x1b}, { CCI_REG8(0x376a), 0x1b },
{0x376b, 0x1b}, { CCI_REG8(0x376b), 0x1b },
{0x376c, 0x1a}, { CCI_REG8(0x376c), 0x1a },
{0x376d, 0x17}, { CCI_REG8(0x376d), 0x17 },
{0x376e, 0x0f}, { CCI_REG8(0x376e), 0x0f },
{0x3776, 0x00}, { CCI_REG8(0x3776), 0x00 },
{0x3777, 0x00}, { CCI_REG8(0x3777), 0x00 },
{0x3778, 0x46}, { CCI_REG8(0x3778), 0x46 },
{0x3779, 0x00}, { CCI_REG8(0x3779), 0x00 },
{0x377a, 0x89}, { CCI_REG8(0x377a), 0x89 },
{0x377b, 0x00}, { CCI_REG8(0x377b), 0x00 },
{0x377c, 0x08}, { CCI_REG8(0x377c), 0x08 },
{0x377d, 0x01}, { CCI_REG8(0x377d), 0x01 },
{0x377e, 0x23}, { CCI_REG8(0x377e), 0x23 },
{0x377f, 0x02}, { CCI_REG8(0x377f), 0x02 },
{0x3780, 0xd9}, { CCI_REG8(0x3780), 0xd9 },
{0x3781, 0x03}, { CCI_REG8(0x3781), 0x03 },
{0x3782, 0xf5}, { CCI_REG8(0x3782), 0xf5 },
{0x3783, 0x06}, { CCI_REG8(0x3783), 0x06 },
{0x3784, 0xa5}, { CCI_REG8(0x3784), 0xa5 },
{0x3788, 0x0f}, { CCI_REG8(0x3788), 0x0f },
{0x378a, 0xd9}, { CCI_REG8(0x378a), 0xd9 },
{0x378b, 0x03}, { CCI_REG8(0x378b), 0x03 },
{0x378c, 0xeb}, { CCI_REG8(0x378c), 0xeb },
{0x378d, 0x05}, { CCI_REG8(0x378d), 0x05 },
{0x378e, 0x87}, { CCI_REG8(0x378e), 0x87 },
{0x378f, 0x06}, { CCI_REG8(0x378f), 0x06 },
{0x3790, 0xf5}, { CCI_REG8(0x3790), 0xf5 },
{0x3792, 0x43}, { CCI_REG8(0x3792), 0x43 },
{0x3794, 0x7a}, { CCI_REG8(0x3794), 0x7a },
{0x3796, 0xa1}, { CCI_REG8(0x3796), 0xa1 },
{0x37b0, 0x36}, { CCI_REG8(0x37b0), 0x36 },
{0x3a00, 0x00}, { CCI_REG8(0x3a00), 0x00 },
}; };
static const struct imx335_reg raw10_framefmt_regs[] = { static const struct cci_reg_sequence raw10_framefmt_regs[] = {
{0x3050, 0x00}, { IMX335_REG_ADBIT, 0x00 },
{0x319d, 0x00}, { IMX335_REG_MDBIT, 0x00 },
{0x341c, 0xff}, { IMX335_REG_ADBIT1, 0x1ff },
{0x341d, 0x01},
}; };
static const struct imx335_reg raw12_framefmt_regs[] = { static const struct cci_reg_sequence raw12_framefmt_regs[] = {
{0x3050, 0x01}, { IMX335_REG_ADBIT, 0x01 },
{0x319d, 0x01}, { IMX335_REG_MDBIT, 0x01 },
{0x341c, 0x47}, { IMX335_REG_ADBIT1, 0x47 },
{0x341d, 0x00},
}; };
static const struct imx335_reg mipi_data_rate_1188Mbps[] = { static const struct cci_reg_sequence mipi_data_rate_1188Mbps[] = {
{0x300c, 0x3b}, { IMX335_REG_BCWAIT_TIME, 0x3b },
{0x300d, 0x2a}, { IMX335_REG_CPWAIT_TIME, 0x2a },
{0x314c, 0xc6}, { IMX335_REG_INCLKSEL1, 0x00c6 },
{0x314d, 0x00}, { IMX335_REG_INCLKSEL2, 0x02 },
{0x315a, 0x02}, { IMX335_REG_INCLKSEL3, 0xa0 },
{0x3168, 0xa0}, { IMX335_REG_INCLKSEL4, 0x7e },
{0x316a, 0x7e}, { IMX335_REG_SYSMODE, 0x01 },
{0x319e, 0x01}, { IMX335_REG_TCLKPOST, 0x8f },
{0x3a18, 0x8f}, { IMX335_REG_TCLKPREPARE, 0x4f },
{0x3a1a, 0x4f}, { IMX335_REG_TCLK_TRAIL, 0x47 },
{0x3a1c, 0x47}, { IMX335_REG_TCLK_ZERO, 0x0137 },
{0x3a1e, 0x37}, { IMX335_REG_THS_PREPARE, 0x4f },
{0x3a1f, 0x01}, { IMX335_REG_THS_ZERO, 0x87 },
{0x3a20, 0x4f}, { IMX335_REG_THS_TRAIL, 0x4f },
{0x3a22, 0x87}, { IMX335_REG_THS_EXIT, 0x7f },
{0x3a24, 0x4f}, { IMX335_REG_TPLX, 0x3f },
{0x3a26, 0x7f},
{0x3a28, 0x3f},
}; };
static const struct imx335_reg mipi_data_rate_891Mbps[] = { static const struct cci_reg_sequence mipi_data_rate_891Mbps[] = {
{0x300c, 0x3b}, { IMX335_REG_BCWAIT_TIME, 0x3b },
{0x300d, 0x2a}, { IMX335_REG_CPWAIT_TIME, 0x2a },
{0x314c, 0x29}, { IMX335_REG_INCLKSEL1, 0x0129 },
{0x314d, 0x01}, { IMX335_REG_INCLKSEL2, 0x06 },
{0x315a, 0x06}, { IMX335_REG_INCLKSEL3, 0xa0 },
{0x3168, 0xa0}, { IMX335_REG_INCLKSEL4, 0x7e },
{0x316a, 0x7e}, { IMX335_REG_SYSMODE, 0x02 },
{0x319e, 0x02}, { IMX335_REG_TCLKPOST, 0x7f },
{0x3a18, 0x7f}, { IMX335_REG_TCLKPREPARE, 0x37 },
{0x3a1a, 0x37}, { IMX335_REG_TCLK_TRAIL, 0x37 },
{0x3a1c, 0x37}, { IMX335_REG_TCLK_ZERO, 0xf7 },
{0x3a1e, 0xf7}, { IMX335_REG_THS_PREPARE, 0x3f },
{0x3a20, 0x3f}, { IMX335_REG_THS_ZERO, 0x6f },
{0x3a22, 0x6f}, { IMX335_REG_THS_TRAIL, 0x3f },
{0x3a24, 0x3f}, { IMX335_REG_THS_EXIT, 0x5f },
{0x3a26, 0x5f}, { IMX335_REG_TPLX, 0x2f },
{0x3a28, 0x2f},
}; };
static const s64 link_freq[] = { static const s64 link_freq[] = {
@ -372,10 +409,10 @@ static const u32 imx335_mbus_codes[] = {
/* Supported sensor mode configurations */ /* Supported sensor mode configurations */
static const struct imx335_mode supported_mode = { static const struct imx335_mode supported_mode = {
.width = 2592, .width = 2592,
.height = 1940, .height = 1944,
.hblank = 342, .hblank = 342,
.vblank = 2560, .vblank = 2556,
.vblank_min = 2560, .vblank_min = 2556,
.vblank_max = 133060, .vblank_max = 133060,
.pclk = 396000000, .pclk = 396000000,
.reg_list = { .reg_list = {
@ -395,101 +432,6 @@ static inline struct imx335 *to_imx335(struct v4l2_subdev *subdev)
return container_of(subdev, struct imx335, sd); return container_of(subdev, struct imx335, sd);
} }
/**
* imx335_read_reg() - Read registers.
* @imx335: pointer to imx335 device
* @reg: register address
* @len: length of bytes to read. Max supported bytes is 4
* @val: pointer to register value to be filled.
*
* Big endian register addresses with little endian values.
*
* Return: 0 if successful, error code otherwise.
*/
static int imx335_read_reg(struct imx335 *imx335, u16 reg, u32 len, u32 *val)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx335->sd);
struct i2c_msg msgs[2] = {0};
u8 addr_buf[2] = {0};
u8 data_buf[4] = {0};
int ret;
if (WARN_ON(len > 4))
return -EINVAL;
put_unaligned_be16(reg, addr_buf);
/* Write register address */
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = ARRAY_SIZE(addr_buf);
msgs[0].buf = addr_buf;
/* Read data from register */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = data_buf;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return -EIO;
*val = get_unaligned_le32(data_buf);
return 0;
}
/**
* imx335_write_reg() - Write register
* @imx335: pointer to imx335 device
* @reg: register address
* @len: length of bytes. Max supported bytes is 4
* @val: register value
*
* Big endian register addresses with little endian values.
*
* Return: 0 if successful, error code otherwise.
*/
static int imx335_write_reg(struct imx335 *imx335, u16 reg, u32 len, u32 val)
{
struct i2c_client *client = v4l2_get_subdevdata(&imx335->sd);
u8 buf[6] = {0};
if (WARN_ON(len > 4))
return -EINVAL;
put_unaligned_be16(reg, buf);
put_unaligned_le32(val, buf + 2);
if (i2c_master_send(client, buf, len + 2) != len + 2)
return -EIO;
return 0;
}
/**
* imx335_write_regs() - Write a list of registers
* @imx335: pointer to imx335 device
* @regs: list of registers to be written
* @len: length of registers array
*
* Return: 0 if successful. error code otherwise.
*/
static int imx335_write_regs(struct imx335 *imx335,
const struct imx335_reg *regs, u32 len)
{
unsigned int i;
int ret;
for (i = 0; i < len; i++) {
ret = imx335_write_reg(imx335, regs[i].address, 1, regs[i].val);
if (ret)
return ret;
}
return 0;
}
/** /**
* imx335_update_controls() - Update control ranges based on streaming mode * imx335_update_controls() - Update control ranges based on streaming mode
* @imx335: pointer to imx335 device * @imx335: pointer to imx335 device
@ -526,7 +468,8 @@ static int imx335_update_controls(struct imx335 *imx335,
static int imx335_update_exp_gain(struct imx335 *imx335, u32 exposure, u32 gain) static int imx335_update_exp_gain(struct imx335 *imx335, u32 exposure, u32 gain)
{ {
u32 lpfr, shutter; u32 lpfr, shutter;
int ret; int ret_hold;
int ret = 0;
lpfr = imx335->vblank + imx335->cur_mode->height; lpfr = imx335->vblank + imx335->cur_mode->height;
shutter = lpfr - exposure; shutter = lpfr - exposure;
@ -534,64 +477,55 @@ static int imx335_update_exp_gain(struct imx335 *imx335, u32 exposure, u32 gain)
dev_dbg(imx335->dev, "Set exp %u, analog gain %u, shutter %u, lpfr %u\n", dev_dbg(imx335->dev, "Set exp %u, analog gain %u, shutter %u, lpfr %u\n",
exposure, gain, shutter, lpfr); exposure, gain, shutter, lpfr);
ret = imx335_write_reg(imx335, IMX335_REG_HOLD, 1, 1); cci_write(imx335->cci, IMX335_REG_HOLD, 1, &ret);
if (ret) cci_write(imx335->cci, IMX335_REG_VMAX, lpfr, &ret);
return ret; cci_write(imx335->cci, IMX335_REG_SHUTTER, shutter, &ret);
cci_write(imx335->cci, IMX335_REG_GAIN, gain, &ret);
ret = imx335_write_reg(imx335, IMX335_REG_LPFR, 3, lpfr); /*
if (ret) * Unconditionally attempt to release the hold, but track the
goto error_release_group_hold; * error if the unhold itself fails.
*/
ret = imx335_write_reg(imx335, IMX335_REG_SHUTTER, 3, shutter); ret_hold = cci_write(imx335->cci, IMX335_REG_HOLD, 0, NULL);
if (ret) if (ret_hold)
goto error_release_group_hold; ret = ret_hold;
ret = imx335_write_reg(imx335, IMX335_REG_AGAIN, 2, gain);
error_release_group_hold:
imx335_write_reg(imx335, IMX335_REG_HOLD, 1, 0);
return ret; return ret;
} }
static int imx335_update_test_pattern(struct imx335 *imx335, u32 pattern_index) static int imx335_update_test_pattern(struct imx335 *imx335, u32 pattern_index)
{ {
int ret; int ret = 0;
if (pattern_index >= ARRAY_SIZE(imx335_tpg_val)) if (pattern_index >= ARRAY_SIZE(imx335_tpg_val))
return -EINVAL; return -EINVAL;
if (pattern_index) { if (pattern_index) {
const struct imx335_reg tpg_enable_regs[] = { const struct cci_reg_sequence tpg_enable_regs[] = {
{ 0x3148, 0x10 }, { IMX335_REG_TPG_TESTCLKEN, 0x10 },
{ 0x3280, 0x00 }, { IMX335_REG_TPG_DIG_CLP_MODE, 0x00 },
{ 0x329c, 0x01 }, { IMX335_REG_TPG_EN_DUOUT, 0x01 },
{ 0x32a0, 0x11 }, { IMX335_REG_TPG_COLORWIDTH, 0x11 },
{ 0x3302, 0x00 }, { IMX335_REG_BLKLEVEL, 0x00 },
{ 0x3303, 0x00 }, { IMX335_REG_WRJ_OPEN, 0x00 },
{ 0x336c, 0x00 },
}; };
ret = imx335_write_reg(imx335, IMX335_REG_TPG, 1, cci_write(imx335->cci, IMX335_REG_TPG,
imx335_tpg_val[pattern_index]); imx335_tpg_val[pattern_index], &ret);
if (ret)
return ret;
ret = imx335_write_regs(imx335, tpg_enable_regs, cci_multi_reg_write(imx335->cci, tpg_enable_regs,
ARRAY_SIZE(tpg_enable_regs)); ARRAY_SIZE(tpg_enable_regs), &ret);
} else { } else {
const struct imx335_reg tpg_disable_regs[] = { const struct cci_reg_sequence tpg_disable_regs[] = {
{ 0x3148, 0x00 }, { IMX335_REG_TPG_TESTCLKEN, 0x00 },
{ 0x3280, 0x01 }, { IMX335_REG_TPG_DIG_CLP_MODE, 0x01 },
{ 0x329c, 0x00 }, { IMX335_REG_TPG_EN_DUOUT, 0x00 },
{ 0x32a0, 0x10 }, { IMX335_REG_TPG_COLORWIDTH, 0x10 },
{ 0x3302, 0x32 }, { IMX335_REG_BLKLEVEL, 0x32 },
{ 0x3303, 0x00 }, { IMX335_REG_WRJ_OPEN, 0x01 },
{ 0x336c, 0x01 },
}; };
ret = imx335_write_regs(imx335, tpg_disable_regs, cci_multi_reg_write(imx335->cci, tpg_disable_regs,
ARRAY_SIZE(tpg_disable_regs)); ARRAY_SIZE(tpg_disable_regs), &ret);
} }
return ret; return ret;
@ -890,12 +824,14 @@ static int imx335_set_framefmt(struct imx335 *imx335)
{ {
switch (imx335->cur_mbus_code) { switch (imx335->cur_mbus_code) {
case MEDIA_BUS_FMT_SRGGB10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10:
return imx335_write_regs(imx335, raw10_framefmt_regs, return cci_multi_reg_write(imx335->cci, raw10_framefmt_regs,
ARRAY_SIZE(raw10_framefmt_regs)); ARRAY_SIZE(raw10_framefmt_regs),
NULL);
case MEDIA_BUS_FMT_SRGGB12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12:
return imx335_write_regs(imx335, raw12_framefmt_regs, return cci_multi_reg_write(imx335->cci, raw12_framefmt_regs,
ARRAY_SIZE(raw12_framefmt_regs)); ARRAY_SIZE(raw12_framefmt_regs),
NULL);
} }
return -EINVAL; return -EINVAL;
@ -914,7 +850,8 @@ static int imx335_start_streaming(struct imx335 *imx335)
/* Setup PLL */ /* Setup PLL */
reg_list = &link_freq_reglist[__ffs(imx335->link_freq_bitmap)]; reg_list = &link_freq_reglist[__ffs(imx335->link_freq_bitmap)];
ret = imx335_write_regs(imx335, reg_list->regs, reg_list->num_of_regs); ret = cci_multi_reg_write(imx335->cci, reg_list->regs,
reg_list->num_of_regs, NULL);
if (ret) { if (ret) {
dev_err(imx335->dev, "%s failed to set plls\n", __func__); dev_err(imx335->dev, "%s failed to set plls\n", __func__);
return ret; return ret;
@ -922,8 +859,8 @@ static int imx335_start_streaming(struct imx335 *imx335)
/* Write sensor mode registers */ /* Write sensor mode registers */
reg_list = &imx335->cur_mode->reg_list; reg_list = &imx335->cur_mode->reg_list;
ret = imx335_write_regs(imx335, reg_list->regs, ret = cci_multi_reg_write(imx335->cci, reg_list->regs,
reg_list->num_of_regs); reg_list->num_of_regs, NULL);
if (ret) { if (ret) {
dev_err(imx335->dev, "fail to write initial registers\n"); dev_err(imx335->dev, "fail to write initial registers\n");
return ret; return ret;
@ -936,6 +873,12 @@ static int imx335_start_streaming(struct imx335 *imx335)
return ret; return ret;
} }
/* Configure lanes */
ret = cci_write(imx335->cci, IMX335_REG_LANEMODE,
imx335->lane_mode, NULL);
if (ret)
return ret;
/* Setup handler will write actual exposure and gain */ /* Setup handler will write actual exposure and gain */
ret = __v4l2_ctrl_handler_setup(imx335->sd.ctrl_handler); ret = __v4l2_ctrl_handler_setup(imx335->sd.ctrl_handler);
if (ret) { if (ret) {
@ -944,8 +887,8 @@ static int imx335_start_streaming(struct imx335 *imx335)
} }
/* Start streaming */ /* Start streaming */
ret = imx335_write_reg(imx335, IMX335_REG_MODE_SELECT, ret = cci_write(imx335->cci, IMX335_REG_MODE_SELECT,
1, IMX335_MODE_STREAMING); IMX335_MODE_STREAMING, NULL);
if (ret) { if (ret) {
dev_err(imx335->dev, "fail to start streaming\n"); dev_err(imx335->dev, "fail to start streaming\n");
return ret; return ret;
@ -965,8 +908,8 @@ static int imx335_start_streaming(struct imx335 *imx335)
*/ */
static int imx335_stop_streaming(struct imx335 *imx335) static int imx335_stop_streaming(struct imx335 *imx335)
{ {
return imx335_write_reg(imx335, IMX335_REG_MODE_SELECT, return cci_write(imx335->cci, IMX335_REG_MODE_SELECT,
1, IMX335_MODE_STANDBY); IMX335_MODE_STANDBY, NULL);
} }
/** /**
@ -1017,14 +960,14 @@ error_unlock:
static int imx335_detect(struct imx335 *imx335) static int imx335_detect(struct imx335 *imx335)
{ {
int ret; int ret;
u32 val; u64 val;
ret = imx335_read_reg(imx335, IMX335_REG_ID, 2, &val); ret = cci_read(imx335->cci, IMX335_REG_ID, &val, NULL);
if (ret) if (ret)
return ret; return ret;
if (val != IMX335_ID) { if (val != IMX335_ID) {
dev_err(imx335->dev, "chip id mismatch: %x!=%x\n", dev_err(imx335->dev, "chip id mismatch: %x!=%llx\n",
IMX335_ID, val); IMX335_ID, val);
return -ENXIO; return -ENXIO;
} }
@ -1096,7 +1039,14 @@ static int imx335_parse_hw_config(struct imx335 *imx335)
if (ret) if (ret)
return ret; return ret;
if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX335_NUM_DATA_LANES) { switch (bus_cfg.bus.mipi_csi2.num_data_lanes) {
case 2:
imx335->lane_mode = IMX335_2LANE;
break;
case 4:
imx335->lane_mode = IMX335_4LANE;
break;
default:
dev_err(imx335->dev, dev_err(imx335->dev,
"number of CSI2 data lanes %d is not supported\n", "number of CSI2 data lanes %d is not supported\n",
bus_cfg.bus.mipi_csi2.num_data_lanes); bus_cfg.bus.mipi_csi2.num_data_lanes);
@ -1208,10 +1158,16 @@ static int imx335_init_controls(struct imx335 *imx335)
{ {
struct v4l2_ctrl_handler *ctrl_hdlr = &imx335->ctrl_handler; struct v4l2_ctrl_handler *ctrl_hdlr = &imx335->ctrl_handler;
const struct imx335_mode *mode = imx335->cur_mode; const struct imx335_mode *mode = imx335->cur_mode;
struct v4l2_fwnode_device_properties props;
u32 lpfr; u32 lpfr;
int ret; int ret;
ret = v4l2_ctrl_handler_init(ctrl_hdlr, 7); ret = v4l2_fwnode_device_parse(imx335->dev, &props);
if (ret)
return ret;
/* v4l2_fwnode_device_properties can add two more controls */
ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9);
if (ret) if (ret)
return ret; return ret;
@ -1228,6 +1184,14 @@ static int imx335_init_controls(struct imx335 *imx335)
IMX335_EXPOSURE_STEP, IMX335_EXPOSURE_STEP,
IMX335_EXPOSURE_DEFAULT); IMX335_EXPOSURE_DEFAULT);
/*
* The sensor has an analog gain and a digital gain, both controlled
* through a single gain value, expressed in 0.3dB increments. Values
* from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values
* up to 72.0dB (240) add further digital gain. Limit the range to
* analog gain only, support for digital gain can be added separately
* if needed.
*/
imx335->again_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, imx335->again_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
&imx335_ctrl_ops, &imx335_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN,
@ -1276,6 +1240,8 @@ static int imx335_init_controls(struct imx335 *imx335)
if (imx335->hblank_ctrl) if (imx335->hblank_ctrl)
imx335->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; imx335->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx335_ctrl_ops, &props);
if (ctrl_hdlr->error) { if (ctrl_hdlr->error) {
dev_err(imx335->dev, "control init failed: %d\n", dev_err(imx335->dev, "control init failed: %d\n",
ctrl_hdlr->error); ctrl_hdlr->error);
@ -1304,6 +1270,11 @@ static int imx335_probe(struct i2c_client *client)
return -ENOMEM; return -ENOMEM;
imx335->dev = &client->dev; imx335->dev = &client->dev;
imx335->cci = devm_cci_regmap_init_i2c(client, 16);
if (IS_ERR(imx335->cci)) {
dev_err(imx335->dev, "Unable to initialize I2C\n");
return -ENODEV;
}
/* Initialize subdev */ /* Initialize subdev */
v4l2_i2c_subdev_init(&imx335->sd, client, &imx335_subdev_ops); v4l2_i2c_subdev_init(&imx335->sd, client, &imx335_subdev_ops);

View File

@ -8,6 +8,9 @@
* Copyright (C) 2015 Cogent Embedded, Inc. * Copyright (C) 2015 Cogent Embedded, Inc.
*/ */
#ifndef __MEDIA_I2C_MAX9271_H__
#define __MEDIA_I2C_MAX9271_H__
#include <linux/i2c.h> #include <linux/i2c.h>
#define MAX9271_DEFAULT_ADDR 0x40 #define MAX9271_DEFAULT_ADDR 0x40
@ -231,3 +234,5 @@ int max9271_set_deserializer_address(struct max9271_device *dev, u8 addr);
* Return 0 on success or a negative error code on failure * Return 0 on success or a negative error code on failure
*/ */
int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest); int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest);
#endif /* __MEDIA_I2C_MAX9271_H__ */

View File

@ -75,6 +75,8 @@
#define OV2680_ACTIVE_START_TOP 8 #define OV2680_ACTIVE_START_TOP 8
#define OV2680_MIN_CROP_WIDTH 2 #define OV2680_MIN_CROP_WIDTH 2
#define OV2680_MIN_CROP_HEIGHT 2 #define OV2680_MIN_CROP_HEIGHT 2
#define OV2680_MIN_VBLANK 4
#define OV2680_MAX_VBLANK 0xffff
/* Fixed pre-div of 1/2 */ /* Fixed pre-div of 1/2 */
#define OV2680_PLL_PREDIV0 2 #define OV2680_PLL_PREDIV0 2
@ -84,10 +86,7 @@
/* 66MHz pixel clock: 66MHz / 1704 * 1294 = 30fps */ /* 66MHz pixel clock: 66MHz / 1704 * 1294 = 30fps */
#define OV2680_PIXELS_PER_LINE 1704 #define OV2680_PIXELS_PER_LINE 1704
#define OV2680_LINES_PER_FRAME 1294 #define OV2680_LINES_PER_FRAME_30FPS 1294
/* If possible send 16 extra rows / lines to the ISP as padding */
#define OV2680_END_MARGIN 16
/* Max exposure time is VTS - 8 */ /* Max exposure time is VTS - 8 */
#define OV2680_INTEGRATION_TIME_MARGIN 8 #define OV2680_INTEGRATION_TIME_MARGIN 8
@ -130,6 +129,8 @@ struct ov2680_ctrls {
struct v4l2_ctrl *test_pattern; struct v4l2_ctrl *test_pattern;
struct v4l2_ctrl *link_freq; struct v4l2_ctrl *link_freq;
struct v4l2_ctrl *pixel_rate; struct v4l2_ctrl *pixel_rate;
struct v4l2_ctrl *vblank;
struct v4l2_ctrl *hblank;
}; };
struct ov2680_mode { struct ov2680_mode {
@ -143,8 +144,6 @@ struct ov2680_mode {
u16 v_end; u16 v_end;
u16 h_output_size; u16 h_output_size;
u16 v_output_size; u16 v_output_size;
u16 hts;
u16 vts;
}; };
struct ov2680_dev { struct ov2680_dev {
@ -359,15 +358,11 @@ static void ov2680_calc_mode(struct ov2680_dev *sensor)
sensor->mode.v_start = (sensor->mode.crop.top + sensor->mode.v_start = (sensor->mode.crop.top +
(sensor->mode.crop.height - height) / 2) & ~1; (sensor->mode.crop.height - height) / 2) & ~1;
sensor->mode.h_end = sensor->mode.h_end =
min(sensor->mode.h_start + width + OV2680_END_MARGIN - 1, min(sensor->mode.h_start + width - 1, OV2680_NATIVE_WIDTH - 1);
OV2680_NATIVE_WIDTH - 1);
sensor->mode.v_end = sensor->mode.v_end =
min(sensor->mode.v_start + height + OV2680_END_MARGIN - 1, min(sensor->mode.v_start + height - 1, OV2680_NATIVE_HEIGHT - 1);
OV2680_NATIVE_HEIGHT - 1);
sensor->mode.h_output_size = orig_width; sensor->mode.h_output_size = orig_width;
sensor->mode.v_output_size = orig_height; sensor->mode.v_output_size = orig_height;
sensor->mode.hts = OV2680_PIXELS_PER_LINE;
sensor->mode.vts = OV2680_LINES_PER_FRAME;
} }
static int ov2680_set_mode(struct ov2680_dev *sensor) static int ov2680_set_mode(struct ov2680_dev *sensor)
@ -402,9 +397,8 @@ static int ov2680_set_mode(struct ov2680_dev *sensor)
cci_write(sensor->regmap, OV2680_REG_VERTICAL_OUTPUT_SIZE, cci_write(sensor->regmap, OV2680_REG_VERTICAL_OUTPUT_SIZE,
sensor->mode.v_output_size, &ret); sensor->mode.v_output_size, &ret);
cci_write(sensor->regmap, OV2680_REG_TIMING_HTS, cci_write(sensor->regmap, OV2680_REG_TIMING_HTS,
sensor->mode.hts, &ret); OV2680_PIXELS_PER_LINE, &ret);
cci_write(sensor->regmap, OV2680_REG_TIMING_VTS, /* VTS gets set by the vblank ctrl */
sensor->mode.vts, &ret);
cci_write(sensor->regmap, OV2680_REG_ISP_X_WIN, 0, &ret); cci_write(sensor->regmap, OV2680_REG_ISP_X_WIN, 0, &ret);
cci_write(sensor->regmap, OV2680_REG_ISP_Y_WIN, 0, &ret); cci_write(sensor->regmap, OV2680_REG_ISP_Y_WIN, 0, &ret);
cci_write(sensor->regmap, OV2680_REG_X_INC, inc, &ret); cci_write(sensor->regmap, OV2680_REG_X_INC, inc, &ret);
@ -478,6 +472,15 @@ static int ov2680_exposure_set(struct ov2680_dev *sensor, u32 exp)
NULL); NULL);
} }
static int ov2680_exposure_update_range(struct ov2680_dev *sensor)
{
int exp_max = sensor->mode.fmt.height + sensor->ctrls.vblank->val -
OV2680_INTEGRATION_TIME_MARGIN;
return __v4l2_ctrl_modify_range(sensor->ctrls.exposure, 0, exp_max,
1, exp_max);
}
static int ov2680_stream_enable(struct ov2680_dev *sensor) static int ov2680_stream_enable(struct ov2680_dev *sensor)
{ {
int ret; int ret;
@ -644,7 +647,7 @@ static int ov2680_set_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *try_fmt; struct v4l2_mbus_framefmt *try_fmt;
const struct v4l2_rect *crop; const struct v4l2_rect *crop;
unsigned int width, height; unsigned int width, height;
int ret = 0; int def, max, ret = 0;
crop = __ov2680_get_pad_crop(sensor, sd_state, format->pad, crop = __ov2680_get_pad_crop(sensor, sd_state, format->pad,
format->which); format->which);
@ -673,6 +676,27 @@ static int ov2680_set_fmt(struct v4l2_subdev *sd,
sensor->mode.fmt = format->format; sensor->mode.fmt = format->format;
ov2680_calc_mode(sensor); ov2680_calc_mode(sensor);
/* vblank range is height dependent adjust and reset to default */
max = OV2680_MAX_VBLANK - height;
def = OV2680_LINES_PER_FRAME_30FPS - height;
ret = __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV2680_MIN_VBLANK,
max, 1, def);
if (ret)
goto unlock;
ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.vblank, def);
if (ret)
goto unlock;
/* exposure range depends on vts which may have changed */
ret = ov2680_exposure_update_range(sensor);
if (ret)
goto unlock;
/* adjust hblank value for new width */
def = OV2680_PIXELS_PER_LINE - width;
ret = __v4l2_ctrl_modify_range(sensor->ctrls.hblank, def, def, 1, def);
unlock: unlock:
mutex_unlock(&sensor->lock); mutex_unlock(&sensor->lock);
@ -842,6 +866,13 @@ static int ov2680_s_ctrl(struct v4l2_ctrl *ctrl)
struct ov2680_dev *sensor = to_ov2680_dev(sd); struct ov2680_dev *sensor = to_ov2680_dev(sd);
int ret; int ret;
/* Update exposure range on vblank changes */
if (ctrl->id == V4L2_CID_VBLANK) {
ret = ov2680_exposure_update_range(sensor);
if (ret)
return ret;
}
/* Only apply changes to the controls if the device is powered up */ /* Only apply changes to the controls if the device is powered up */
if (!pm_runtime_get_if_in_use(sensor->sd.dev)) { if (!pm_runtime_get_if_in_use(sensor->sd.dev)) {
ov2680_set_bayer_order(sensor, &sensor->mode.fmt); ov2680_set_bayer_order(sensor, &sensor->mode.fmt);
@ -864,6 +895,10 @@ static int ov2680_s_ctrl(struct v4l2_ctrl *ctrl)
case V4L2_CID_TEST_PATTERN: case V4L2_CID_TEST_PATTERN:
ret = ov2680_test_pattern_set(sensor, ctrl->val); ret = ov2680_test_pattern_set(sensor, ctrl->val);
break; break;
case V4L2_CID_VBLANK:
ret = cci_write(sensor->regmap, OV2680_REG_TIMING_VTS,
sensor->mode.fmt.height + ctrl->val, NULL);
break;
default: default:
ret = -EINVAL; ret = -EINVAL;
break; break;
@ -922,8 +957,8 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor)
const struct v4l2_ctrl_ops *ops = &ov2680_ctrl_ops; const struct v4l2_ctrl_ops *ops = &ov2680_ctrl_ops;
struct ov2680_ctrls *ctrls = &sensor->ctrls; struct ov2680_ctrls *ctrls = &sensor->ctrls;
struct v4l2_ctrl_handler *hdl = &ctrls->handler; struct v4l2_ctrl_handler *hdl = &ctrls->handler;
int exp_max = OV2680_LINES_PER_FRAME - OV2680_INTEGRATION_TIME_MARGIN; struct v4l2_fwnode_device_properties props;
int ret = 0; int def, max, ret = 0;
v4l2_i2c_subdev_init(&sensor->sd, client, &ov2680_subdev_ops); v4l2_i2c_subdev_init(&sensor->sd, client, &ov2680_subdev_ops);
sensor->sd.internal_ops = &ov2680_internal_ops; sensor->sd.internal_ops = &ov2680_internal_ops;
@ -948,8 +983,9 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor)
ARRAY_SIZE(test_pattern_menu) - 1, ARRAY_SIZE(test_pattern_menu) - 1,
0, 0, test_pattern_menu); 0, 0, test_pattern_menu);
max = OV2680_LINES_PER_FRAME_30FPS - OV2680_INTEGRATION_TIME_MARGIN;
ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
0, exp_max, 1, exp_max); 0, max, 1, max);
ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN,
0, 1023, 1, 250); 0, 1023, 1, 250);
@ -960,6 +996,21 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor)
0, sensor->pixel_rate, 0, sensor->pixel_rate,
1, sensor->pixel_rate); 1, sensor->pixel_rate);
max = OV2680_MAX_VBLANK - OV2680_DEFAULT_HEIGHT;
def = OV2680_LINES_PER_FRAME_30FPS - OV2680_DEFAULT_HEIGHT;
ctrls->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK,
OV2680_MIN_VBLANK, max, 1, def);
def = OV2680_PIXELS_PER_LINE - OV2680_DEFAULT_WIDTH;
ctrls->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK,
def, def, 1, def);
ret = v4l2_fwnode_device_parse(sensor->dev, &props);
if (ret)
goto cleanup_entity;
v4l2_ctrl_new_fwnode_properties(hdl, ops, &props);
if (hdl->error) { if (hdl->error) {
ret = hdl->error; ret = hdl->error;
goto cleanup_entity; goto cleanup_entity;
@ -968,6 +1019,7 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor)
ctrls->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ctrls->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
ctrls->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ctrls->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
sensor->sd.ctrl_handler = hdl; sensor->sd.ctrl_handler = hdl;
@ -1116,25 +1168,24 @@ static int ov2680_parse_dt(struct ov2680_dev *sensor)
sensor->pixel_rate = sensor->link_freq[0] * 2; sensor->pixel_rate = sensor->link_freq[0] * 2;
do_div(sensor->pixel_rate, 10); do_div(sensor->pixel_rate, 10);
/* Verify bus cfg */ if (!bus_cfg.nr_of_link_frequencies) {
if (bus_cfg.bus.mipi_csi2.num_data_lanes != 1) { dev_warn(dev, "Consider passing 'link-frequencies' in DT\n");
ret = dev_err_probe(dev, -EINVAL, goto skip_link_freq_validation;
"only a 1-lane CSI2 config is supported");
goto out_free_bus_cfg;
} }
for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++)
if (bus_cfg.link_frequencies[i] == sensor->link_freq[0]) if (bus_cfg.link_frequencies[i] == sensor->link_freq[0])
break; break;
if (bus_cfg.nr_of_link_frequencies == 0 || if (bus_cfg.nr_of_link_frequencies == i) {
bus_cfg.nr_of_link_frequencies == i) {
ret = dev_err_probe(dev, -EINVAL, ret = dev_err_probe(dev, -EINVAL,
"supported link freq %lld not found\n", "supported link freq %lld not found\n",
sensor->link_freq[0]); sensor->link_freq[0]);
goto out_free_bus_cfg; goto out_free_bus_cfg;
} }
skip_link_freq_validation:
ret = 0;
out_free_bus_cfg: out_free_bus_cfg:
v4l2_fwnode_endpoint_free(&bus_cfg); v4l2_fwnode_endpoint_free(&bus_cfg);
return ret; return ret;

View File

@ -768,14 +768,15 @@ static int ov2740_init_controls(struct ov2740 *ov2740)
cur_mode = ov2740->cur_mode; cur_mode = ov2740->cur_mode;
size = ARRAY_SIZE(link_freq_menu_items); size = ARRAY_SIZE(link_freq_menu_items);
ov2740->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov2740_ctrl_ops, ov2740->link_freq =
V4L2_CID_LINK_FREQ, v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov2740_ctrl_ops,
size - 1, 0, V4L2_CID_LINK_FREQ, size - 1,
link_freq_menu_items); ov2740->supported_modes->link_freq_index,
link_freq_menu_items);
if (ov2740->link_freq) if (ov2740->link_freq)
ov2740->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ov2740->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
pixel_rate = to_pixel_rate(OV2740_LINK_FREQ_360MHZ_INDEX); pixel_rate = to_pixel_rate(ov2740->supported_modes->link_freq_index);
ov2740->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, ov2740->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops,
V4L2_CID_PIXEL_RATE, 0, V4L2_CID_PIXEL_RATE, 0,
pixel_rate, 1, pixel_rate); pixel_rate, 1, pixel_rate);
@ -1332,9 +1333,16 @@ static int ov2740_probe(struct i2c_client *client)
return dev_err_probe(dev, ret, "failed to check HW configuration\n"); return dev_err_probe(dev, ret, "failed to check HW configuration\n");
ov2740->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); ov2740->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(ov2740->reset_gpio)) if (IS_ERR(ov2740->reset_gpio)) {
return dev_err_probe(dev, PTR_ERR(ov2740->reset_gpio), return dev_err_probe(dev, PTR_ERR(ov2740->reset_gpio),
"failed to get reset GPIO\n"); "failed to get reset GPIO\n");
} else if (ov2740->reset_gpio) {
/*
* Ensure reset is asserted for at least 20 ms before
* ov2740_resume() deasserts it.
*/
msleep(20);
}
ov2740->clk = devm_clk_get_optional(dev, "clk"); ov2740->clk = devm_clk_get_optional(dev, "clk");
if (IS_ERR(ov2740->clk)) if (IS_ERR(ov2740->clk))

View File

@ -3,7 +3,7 @@
* ov4689 driver * ov4689 driver
* *
* Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
* Copyright (C) 2022 Mikhail Rudenko * Copyright (C) 2022, 2024 Mikhail Rudenko
*/ */
#include <linux/clk.h> #include <linux/clk.h>
@ -15,45 +15,86 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <media/media-entity.h> #include <media/media-entity.h>
#include <media/v4l2-async.h> #include <media/v4l2-async.h>
#include <media/v4l2-cci.h>
#include <media/v4l2-ctrls.h> #include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h> #include <media/v4l2-subdev.h>
#include <media/v4l2-fwnode.h> #include <media/v4l2-fwnode.h>
#define CHIP_ID 0x004688 #define OV4689_REG_CTRL_MODE CCI_REG8(0x0100)
#define OV4689_REG_CHIP_ID 0x300a
#define OV4689_XVCLK_FREQ 24000000
#define OV4689_REG_CTRL_MODE 0x0100
#define OV4689_MODE_SW_STANDBY 0x0 #define OV4689_MODE_SW_STANDBY 0x0
#define OV4689_MODE_STREAMING BIT(0) #define OV4689_MODE_STREAMING BIT(0)
#define OV4689_REG_EXPOSURE 0x3500 #define OV4689_REG_CHIP_ID CCI_REG16(0x300a)
#define CHIP_ID 0x004688
#define OV4689_REG_EXPOSURE CCI_REG24(0x3500)
#define OV4689_EXPOSURE_MIN 4 #define OV4689_EXPOSURE_MIN 4
#define OV4689_EXPOSURE_STEP 1 #define OV4689_EXPOSURE_STEP 1
#define OV4689_VTS_MAX 0x7fff
#define OV4689_REG_GAIN_H 0x3508 #define OV4689_REG_GAIN CCI_REG16(0x3508)
#define OV4689_REG_GAIN_L 0x3509
#define OV4689_GAIN_H_MASK 0x07
#define OV4689_GAIN_H_SHIFT 8
#define OV4689_GAIN_L_MASK 0xff
#define OV4689_GAIN_STEP 1 #define OV4689_GAIN_STEP 1
#define OV4689_GAIN_DEFAULT 0x80 #define OV4689_GAIN_DEFAULT 0x80
#define OV4689_REG_TEST_PATTERN 0x5040 #define OV4689_REG_DIG_GAIN CCI_REG16(0x352a)
#define OV4689_DIG_GAIN_MIN 1
#define OV4689_DIG_GAIN_MAX 0x7fff
#define OV4689_DIG_GAIN_STEP 1
#define OV4689_DIG_GAIN_DEFAULT 0x800
#define OV4689_REG_H_CROP_START CCI_REG16(0x3800)
#define OV4689_REG_V_CROP_START CCI_REG16(0x3802)
#define OV4689_REG_H_CROP_END CCI_REG16(0x3804)
#define OV4689_REG_V_CROP_END CCI_REG16(0x3806)
#define OV4689_REG_H_OUTPUT_SIZE CCI_REG16(0x3808)
#define OV4689_REG_V_OUTPUT_SIZE CCI_REG16(0x380a)
#define OV4689_REG_HTS CCI_REG16(0x380c)
#define OV4689_HTS_DIVIDER 4
#define OV4689_HTS_MAX 0x7fff
#define OV4689_REG_VTS CCI_REG16(0x380e)
#define OV4689_VTS_MAX 0x7fff
#define OV4689_REG_H_WIN_OFF CCI_REG16(0x3810)
#define OV4689_REG_V_WIN_OFF CCI_REG16(0x3812)
#define OV4689_REG_TIMING_FORMAT1 CCI_REG8(0x3820) /* Vertical */
#define OV4689_REG_TIMING_FORMAT2 CCI_REG8(0x3821) /* Horizontal */
#define OV4689_TIMING_FLIP_MASK GENMASK(2, 1)
#define OV4689_TIMING_FLIP_ARRAY BIT(1)
#define OV4689_TIMING_FLIP_DIGITAL BIT(2)
#define OV4689_TIMING_FLIP_BOTH (OV4689_TIMING_FLIP_ARRAY |\
OV4689_TIMING_FLIP_DIGITAL)
#define OV4689_REG_ANCHOR_LEFT_START CCI_REG16(0x4020)
#define OV4689_ANCHOR_LEFT_START_DEF 576
#define OV4689_REG_ANCHOR_LEFT_END CCI_REG16(0x4022)
#define OV4689_ANCHOR_LEFT_END_DEF 831
#define OV4689_REG_ANCHOR_RIGHT_START CCI_REG16(0x4024)
#define OV4689_ANCHOR_RIGHT_START_DEF 1984
#define OV4689_REG_ANCHOR_RIGHT_END CCI_REG16(0x4026)
#define OV4689_ANCHOR_RIGHT_END_DEF 2239
#define OV4689_REG_VFIFO_CTRL_01 CCI_REG8(0x4601)
#define OV4689_REG_WB_GAIN_RED CCI_REG16(0x500c)
#define OV4689_REG_WB_GAIN_BLUE CCI_REG16(0x5010)
#define OV4689_WB_GAIN_MIN 1
#define OV4689_WB_GAIN_MAX 0xfff
#define OV4689_WB_GAIN_STEP 1
#define OV4689_WB_GAIN_DEFAULT 0x400
#define OV4689_REG_TEST_PATTERN CCI_REG8(0x5040)
#define OV4689_TEST_PATTERN_ENABLE 0x80 #define OV4689_TEST_PATTERN_ENABLE 0x80
#define OV4689_TEST_PATTERN_DISABLE 0x0 #define OV4689_TEST_PATTERN_DISABLE 0x0
#define OV4689_REG_VTS 0x380e
#define REG_NULL 0xFFFF
#define OV4689_REG_VALUE_08BIT 1
#define OV4689_REG_VALUE_16BIT 2
#define OV4689_REG_VALUE_24BIT 3
#define OV4689_LANES 4 #define OV4689_LANES 4
#define OV4689_XVCLK_FREQ 24000000
#define OV4689_PIXEL_ARRAY_WIDTH 2720
#define OV4689_PIXEL_ARRAY_HEIGHT 1536
#define OV4689_DUMMY_ROWS 8 /* 8 dummy rows on each side */
#define OV4689_DUMMY_COLUMNS 16 /* 16 dummy columns on each side */
static const char *const ov4689_supply_names[] = { static const char *const ov4689_supply_names[] = {
"avdd", /* Analog power */ "avdd", /* Analog power */
@ -61,11 +102,6 @@ static const char *const ov4689_supply_names[] = {
"dvdd", /* Digital core power */ "dvdd", /* Digital core power */
}; };
struct regval {
u16 addr;
u8 val;
};
enum ov4689_mode_id { enum ov4689_mode_id {
OV4689_MODE_2688_1520 = 0, OV4689_MODE_2688_1520 = 0,
OV4689_NUM_MODES, OV4689_NUM_MODES,
@ -75,20 +111,18 @@ struct ov4689_mode {
enum ov4689_mode_id id; enum ov4689_mode_id id;
u32 width; u32 width;
u32 height; u32 height;
u32 max_fps;
u32 hts_def; u32 hts_def;
u32 hts_min;
u32 vts_def; u32 vts_def;
u32 exp_def; u32 exp_def;
u32 pixel_rate; u32 pixel_rate;
u32 sensor_width; const struct cci_reg_sequence *reg_list;
u32 sensor_height; unsigned int num_regs;
u32 crop_top;
u32 crop_left;
const struct regval *reg_list;
}; };
struct ov4689 { struct ov4689 {
struct i2c_client *client; struct device *dev;
struct regmap *regmap;
struct clk *xvclk; struct clk *xvclk;
struct gpio_desc *reset_gpio; struct gpio_desc *reset_gpio;
struct gpio_desc *pwdn_gpio; struct gpio_desc *pwdn_gpio;
@ -99,7 +133,6 @@ struct ov4689 {
u32 clock_rate; u32 clock_rate;
struct mutex mutex; /* lock to protect ctrls and cur_mode */
struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_ctrl *exposure; struct v4l2_ctrl *exposure;
@ -119,95 +152,108 @@ struct ov4689_gain_range {
/* /*
* Xclk 24Mhz * Xclk 24Mhz
* max_framerate 30fps * max_framerate 90fps
* mipi_datarate per lane 1008Mbps * mipi_datarate per lane 1008Mbps
*/ */
static const struct regval ov4689_2688x1520_regs[] = { static const struct cci_reg_sequence ov4689_2688x1520_regs[] = {
{0x0103, 0x01}, {0x3638, 0x00}, {0x0300, 0x00}, /* System control*/
{0x0302, 0x2a}, {0x0303, 0x00}, {0x0304, 0x03}, { CCI_REG8(0x0103), 0x01 }, /* SC_CTRL0103 software_reset = 1 */
{0x030b, 0x00}, {0x030d, 0x1e}, {0x030e, 0x04}, { CCI_REG8(0x3000), 0x20 }, /* SC_CMMN_PAD_OEN0 FSIN_output_enable = 1 */
{0x030f, 0x01}, {0x0312, 0x01}, {0x031e, 0x00}, { CCI_REG8(0x3021), 0x03 }, /*
{0x3000, 0x20}, {0x3002, 0x00}, {0x3018, 0x72}, * SC_CMMN_MISC_CTRL fst_stby_ctr = 0,
{0x3020, 0x93}, {0x3021, 0x03}, {0x3022, 0x01}, * sleep_no_latch_enable = 0
{0x3031, 0x0a}, {0x303f, 0x0c}, {0x3305, 0xf1}, */
{0x3307, 0x04}, {0x3309, 0x29}, {0x3500, 0x00},
{0x3501, 0x60}, {0x3502, 0x00}, {0x3503, 0x04}, /* AEC PK */
{0x3504, 0x00}, {0x3505, 0x00}, {0x3506, 0x00}, { CCI_REG8(0x3503), 0x04 }, /* AEC_MANUAL gain_input_as_sensor_gain_format = 1 */
{0x3507, 0x00}, {0x3508, 0x00}, {0x3509, 0x80},
{0x350a, 0x00}, {0x350b, 0x00}, {0x350c, 0x00}, /* ADC and analog control*/
{0x350d, 0x00}, {0x350e, 0x00}, {0x350f, 0x80}, { CCI_REG8(0x3603), 0x40 },
{0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x00}, { CCI_REG8(0x3604), 0x02 },
{0x3513, 0x00}, {0x3514, 0x00}, {0x3515, 0x80}, { CCI_REG8(0x3609), 0x12 },
{0x3516, 0x00}, {0x3517, 0x00}, {0x3518, 0x00}, { CCI_REG8(0x360c), 0x08 },
{0x3519, 0x00}, {0x351a, 0x00}, {0x351b, 0x80}, { CCI_REG8(0x360f), 0xe5 },
{0x351c, 0x00}, {0x351d, 0x00}, {0x351e, 0x00}, { CCI_REG8(0x3608), 0x8f },
{0x351f, 0x00}, {0x3520, 0x00}, {0x3521, 0x80}, { CCI_REG8(0x3611), 0x00 },
{0x3522, 0x08}, {0x3524, 0x08}, {0x3526, 0x08}, { CCI_REG8(0x3613), 0xf7 },
{0x3528, 0x08}, {0x352a, 0x08}, {0x3602, 0x00}, { CCI_REG8(0x3616), 0x58 },
{0x3603, 0x40}, {0x3604, 0x02}, {0x3605, 0x00}, { CCI_REG8(0x3619), 0x99 },
{0x3606, 0x00}, {0x3607, 0x00}, {0x3609, 0x12}, { CCI_REG8(0x361b), 0x60 },
{0x360a, 0x40}, {0x360c, 0x08}, {0x360f, 0xe5}, { CCI_REG8(0x361e), 0x79 },
{0x3608, 0x8f}, {0x3611, 0x00}, {0x3613, 0xf7}, { CCI_REG8(0x3634), 0x10 },
{0x3616, 0x58}, {0x3619, 0x99}, {0x361b, 0x60}, { CCI_REG8(0x3635), 0x10 },
{0x361c, 0x7a}, {0x361e, 0x79}, {0x361f, 0x02}, { CCI_REG8(0x3636), 0x15 },
{0x3632, 0x00}, {0x3633, 0x10}, {0x3634, 0x10}, { CCI_REG8(0x3646), 0x86 },
{0x3635, 0x10}, {0x3636, 0x15}, {0x3646, 0x86}, { CCI_REG8(0x364a), 0x0b },
{0x364a, 0x0b}, {0x3700, 0x17}, {0x3701, 0x22},
{0x3703, 0x10}, {0x370a, 0x37}, {0x3705, 0x00}, /* Sensor control */
{0x3706, 0x63}, {0x3709, 0x3c}, {0x370b, 0x01}, { CCI_REG8(0x3700), 0x17 },
{0x370c, 0x30}, {0x3710, 0x24}, {0x3711, 0x0c}, { CCI_REG8(0x3701), 0x22 },
{0x3716, 0x00}, {0x3720, 0x28}, {0x3729, 0x7b}, { CCI_REG8(0x3703), 0x10 },
{0x372a, 0x84}, {0x372b, 0xbd}, {0x372c, 0xbc}, { CCI_REG8(0x370a), 0x37 },
{0x372e, 0x52}, {0x373c, 0x0e}, {0x373e, 0x33}, { CCI_REG8(0x3706), 0x63 },
{0x3743, 0x10}, {0x3744, 0x88}, {0x3745, 0xc0}, { CCI_REG8(0x3709), 0x3c },
{0x374a, 0x43}, {0x374c, 0x00}, {0x374e, 0x23}, { CCI_REG8(0x370c), 0x30 },
{0x3751, 0x7b}, {0x3752, 0x84}, {0x3753, 0xbd}, { CCI_REG8(0x3710), 0x24 },
{0x3754, 0xbc}, {0x3756, 0x52}, {0x375c, 0x00}, { CCI_REG8(0x3720), 0x28 },
{0x3760, 0x00}, {0x3761, 0x00}, {0x3762, 0x00}, { CCI_REG8(0x3729), 0x7b },
{0x3763, 0x00}, {0x3764, 0x00}, {0x3767, 0x04}, { CCI_REG8(0x372b), 0xbd },
{0x3768, 0x04}, {0x3769, 0x08}, {0x376a, 0x08}, { CCI_REG8(0x372c), 0xbc },
{0x376b, 0x20}, {0x376c, 0x00}, {0x376d, 0x00}, { CCI_REG8(0x372e), 0x52 },
{0x376e, 0x00}, {0x3773, 0x00}, {0x3774, 0x51}, { CCI_REG8(0x373c), 0x0e },
{0x3776, 0xbd}, {0x3777, 0xbd}, {0x3781, 0x18}, { CCI_REG8(0x373e), 0x33 },
{0x3783, 0x25}, {0x3798, 0x1b}, {0x3800, 0x00}, { CCI_REG8(0x3743), 0x10 },
{0x3801, 0x08}, {0x3802, 0x00}, {0x3803, 0x04}, { CCI_REG8(0x3744), 0x88 },
{0x3804, 0x0a}, {0x3805, 0x97}, {0x3806, 0x05}, { CCI_REG8(0x3745), 0xc0 },
{0x3807, 0xfb}, {0x3808, 0x0a}, {0x3809, 0x80}, { CCI_REG8(0x374c), 0x00 },
{0x380a, 0x05}, {0x380b, 0xf0}, {0x380c, 0x0a}, { CCI_REG8(0x374e), 0x23 },
{0x380d, 0x0e}, {0x380e, 0x06}, {0x380f, 0x12}, { CCI_REG8(0x3751), 0x7b },
{0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, { CCI_REG8(0x3753), 0xbd },
{0x3813, 0x04}, {0x3814, 0x01}, {0x3815, 0x01}, { CCI_REG8(0x3754), 0xbc },
{0x3819, 0x01}, {0x3820, 0x00}, {0x3821, 0x06}, { CCI_REG8(0x3756), 0x52 },
{0x3829, 0x00}, {0x382a, 0x01}, {0x382b, 0x01}, { CCI_REG8(0x376b), 0x20 },
{0x382d, 0x7f}, {0x3830, 0x04}, {0x3836, 0x01}, { CCI_REG8(0x3774), 0x51 },
{0x3837, 0x00}, {0x3841, 0x02}, {0x3846, 0x08}, { CCI_REG8(0x3776), 0xbd },
{0x3847, 0x07}, {0x3d85, 0x36}, {0x3d8c, 0x71}, { CCI_REG8(0x3777), 0xbd },
{0x3d8d, 0xcb}, {0x3f0a, 0x00}, {0x4000, 0xf1}, { CCI_REG8(0x3781), 0x18 },
{0x4001, 0x40}, {0x4002, 0x04}, {0x4003, 0x14}, { CCI_REG8(0x3783), 0x25 },
{0x400e, 0x00}, {0x4011, 0x00}, {0x401a, 0x00}, { CCI_REG8(0x3798), 0x1b },
{0x401b, 0x00}, {0x401c, 0x00}, {0x401d, 0x00},
{0x401f, 0x00}, {0x4020, 0x00}, {0x4021, 0x10}, /* Timing control */
{0x4022, 0x07}, {0x4023, 0xcf}, {0x4024, 0x09}, { CCI_REG8(0x3819), 0x01 }, /* VSYNC_END_L vsync_end_point[7:0] = 0x01 */
{0x4025, 0x60}, {0x4026, 0x09}, {0x4027, 0x6f},
{0x4028, 0x00}, {0x4029, 0x02}, {0x402a, 0x06}, /* OTP control */
{0x402b, 0x04}, {0x402c, 0x02}, {0x402d, 0x02}, { CCI_REG8(0x3d85), 0x36 }, /* OTP_REG85 OTP_power_up_load_setting_enable = 1,
{0x402e, 0x0e}, {0x402f, 0x04}, {0x4302, 0xff}, * OTP_power_up_load_data_enable = 1,
{0x4303, 0xff}, {0x4304, 0x00}, {0x4305, 0x00}, * OTP_bist_select = 1 (compare with zero)
{0x4306, 0x00}, {0x4308, 0x02}, {0x4500, 0x6c}, */
{0x4501, 0xc4}, {0x4502, 0x40}, {0x4503, 0x01}, { CCI_REG8(0x3d8c), 0x71 }, /* OTP_SETTING_STT_ADDRESS_H */
{0x4601, 0xa7}, {0x4800, 0x04}, {0x4813, 0x08}, { CCI_REG8(0x3d8d), 0xcb }, /* OTP_SETTING_STT_ADDRESS_L */
{0x481f, 0x40}, {0x4829, 0x78}, {0x4837, 0x10},
{0x4b00, 0x2a}, {0x4b0d, 0x00}, {0x4d00, 0x04}, /* BLC registers*/
{0x4d01, 0x42}, {0x4d02, 0xd1}, {0x4d03, 0x93}, { CCI_REG8(0x4001), 0x40 }, /* DEBUG_MODE */
{0x4d04, 0xf5}, {0x4d05, 0xc1}, {0x5000, 0xf3}, { CCI_REG8(0x401b), 0x00 }, /* DEBUG_MODE */
{0x5001, 0x11}, {0x5004, 0x00}, {0x500a, 0x00}, { CCI_REG8(0x401d), 0x00 }, /* DEBUG_MODE */
{0x500b, 0x00}, {0x5032, 0x00}, {0x5040, 0x00}, { CCI_REG8(0x401f), 0x00 }, /* DEBUG_MODE */
{0x5050, 0x0c}, {0x5500, 0x00}, {0x5501, 0x10},
{0x5502, 0x01}, {0x5503, 0x0f}, {0x8000, 0x00}, /* ADC sync control */
{0x8001, 0x00}, {0x8002, 0x00}, {0x8003, 0x00}, { CCI_REG8(0x4500), 0x6c }, /* ADC_SYNC_CTRL */
{0x8004, 0x00}, {0x8005, 0x00}, {0x8006, 0x00}, { CCI_REG8(0x4503), 0x01 }, /* ADC_SYNC_CTRL */
{0x8007, 0x00}, {0x8008, 0x00}, {0x3638, 0x00},
{REG_NULL, 0x00}, /* Temperature monitor */
{ CCI_REG8(0x4d00), 0x04 }, /* TPM_CTRL_00 tmp_slope[15:8] = 0x04 */
{ CCI_REG8(0x4d01), 0x42 }, /* TPM_CTRL_01 tmp_slope[7:0] = 0x42 */
{ CCI_REG8(0x4d02), 0xd1 }, /* TPM_CTRL_02 tpm_offset[31:24] = 0xd1 */
{ CCI_REG8(0x4d03), 0x93 }, /* TPM_CTRL_03 tpm_offset[23:16] = 0x93 */
{ CCI_REG8(0x4d04), 0xf5 }, /* TPM_CTRL_04 tpm_offset[15:8] = 0xf5 */
{ CCI_REG8(0x4d05), 0xc1 }, /* TPM_CTRL_05 tpm_offset[7:0] = 0xc1 */
/* pre-ISP control */
{ CCI_REG8(0x5050), 0x0c }, /* DEBUG_MODE */
/* OTP-DPC control */
{ CCI_REG8(0x5501), 0x10 }, /* OTP_DPC_START_L otp_start_address[7:0] = 0x10 */
{ CCI_REG8(0x5503), 0x0f }, /* OTP_DPC_END_L otp_end_address[7:0] = 0x0f */
}; };
static const struct ov4689_mode supported_modes[] = { static const struct ov4689_mode supported_modes[] = {
@ -215,16 +261,13 @@ static const struct ov4689_mode supported_modes[] = {
.id = OV4689_MODE_2688_1520, .id = OV4689_MODE_2688_1520,
.width = 2688, .width = 2688,
.height = 1520, .height = 1520,
.sensor_width = 2720,
.sensor_height = 1536,
.crop_top = 8,
.crop_left = 16,
.max_fps = 30,
.exp_def = 1536, .exp_def = 1536,
.hts_def = 4 * 2574, .hts_def = 10296,
.hts_min = 3432,
.vts_def = 1554, .vts_def = 1554,
.pixel_rate = 480000000, .pixel_rate = 480000000,
.reg_list = ov4689_2688x1520_regs, .reg_list = ov4689_2688x1520_regs,
.num_regs = ARRAY_SIZE(ov4689_2688x1520_regs),
}, },
}; };
@ -277,83 +320,6 @@ static const struct ov4689_gain_range ov4689_gain_ranges[] = {
}, },
}; };
/* Write registers up to 4 at a time */
static int ov4689_write_reg(struct i2c_client *client, u16 reg, u32 len,
u32 val)
{
u32 buf_i, val_i;
__be32 val_be;
u8 *val_p;
u8 buf[6];
if (len > 4)
return -EINVAL;
buf[0] = reg >> 8;
buf[1] = reg & 0xff;
val_be = cpu_to_be32(val);
val_p = (u8 *)&val_be;
buf_i = 2;
val_i = 4 - len;
while (val_i < 4)
buf[buf_i++] = val_p[val_i++];
if (i2c_master_send(client, buf, len + 2) != len + 2)
return -EIO;
return 0;
}
static int ov4689_write_array(struct i2c_client *client,
const struct regval *regs)
{
int ret = 0;
u32 i;
for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++)
ret = ov4689_write_reg(client, regs[i].addr,
OV4689_REG_VALUE_08BIT, regs[i].val);
return ret;
}
/* Read registers up to 4 at a time */
static int ov4689_read_reg(struct i2c_client *client, u16 reg, unsigned int len,
u32 *val)
{
__be16 reg_addr_be = cpu_to_be16(reg);
struct i2c_msg msgs[2];
__be32 data_be = 0;
u8 *data_be_p;
int ret;
if (len > 4 || !len)
return -EINVAL;
data_be_p = (u8 *)&data_be;
/* Write register address */
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = (u8 *)&reg_addr_be;
/* Read data from register */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = &data_be_p[4 - len];
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return -EIO;
*val = be32_to_cpu(data_be);
return 0;
}
static void ov4689_fill_fmt(const struct ov4689_mode *mode, static void ov4689_fill_fmt(const struct ov4689_mode *mode,
struct v4l2_mbus_framefmt *fmt) struct v4l2_mbus_framefmt *fmt)
{ {
@ -376,19 +342,6 @@ static int ov4689_set_fmt(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int ov4689_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;
struct ov4689 *ov4689 = to_ov4689(sd);
/* only one mode supported for now */
ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt);
return 0;
}
static int ov4689_enum_mbus_code(struct v4l2_subdev *sd, static int ov4689_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state, struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code) struct v4l2_subdev_mbus_code_enum *code)
@ -427,16 +380,14 @@ static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern)
else else
val = OV4689_TEST_PATTERN_DISABLE; val = OV4689_TEST_PATTERN_DISABLE;
return ov4689_write_reg(ov4689->client, OV4689_REG_TEST_PATTERN, return cci_write(ov4689->regmap, OV4689_REG_TEST_PATTERN,
OV4689_REG_VALUE_08BIT, val); val, NULL);
} }
static int ov4689_get_selection(struct v4l2_subdev *sd, static int ov4689_get_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state, struct v4l2_subdev_state *state,
struct v4l2_subdev_selection *sel) struct v4l2_subdev_selection *sel)
{ {
const struct ov4689_mode *mode = to_ov4689(sd)->cur_mode;
if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL; return -EINVAL;
@ -444,63 +395,114 @@ static int ov4689_get_selection(struct v4l2_subdev *sd,
case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_BOUNDS:
sel->r.top = 0; sel->r.top = 0;
sel->r.left = 0; sel->r.left = 0;
sel->r.width = mode->sensor_width; sel->r.width = OV4689_PIXEL_ARRAY_WIDTH;
sel->r.height = mode->sensor_height; sel->r.height = OV4689_PIXEL_ARRAY_HEIGHT;
return 0; return 0;
case V4L2_SEL_TGT_CROP: case V4L2_SEL_TGT_CROP:
case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP_DEFAULT:
sel->r.top = mode->crop_top; sel->r.top = OV4689_DUMMY_ROWS;
sel->r.left = mode->crop_left; sel->r.left = OV4689_DUMMY_COLUMNS;
sel->r.width = mode->width; sel->r.width =
sel->r.height = mode->height; OV4689_PIXEL_ARRAY_WIDTH - 2 * OV4689_DUMMY_COLUMNS;
sel->r.height =
OV4689_PIXEL_ARRAY_HEIGHT - 2 * OV4689_DUMMY_ROWS;
return 0; return 0;
} }
return -EINVAL; return -EINVAL;
} }
static int ov4689_setup_timings(struct ov4689 *ov4689)
{
const struct ov4689_mode *mode = ov4689->cur_mode;
struct regmap *rm = ov4689->regmap;
int ret = 0;
cci_write(rm, OV4689_REG_H_CROP_START, 8, &ret);
cci_write(rm, OV4689_REG_V_CROP_START, 8, &ret);
cci_write(rm, OV4689_REG_H_CROP_END, 2711, &ret);
cci_write(rm, OV4689_REG_V_CROP_END, 1531, &ret);
cci_write(rm, OV4689_REG_H_OUTPUT_SIZE, mode->width, &ret);
cci_write(rm, OV4689_REG_V_OUTPUT_SIZE, mode->height, &ret);
cci_write(rm, OV4689_REG_H_WIN_OFF, 8, &ret);
cci_write(rm, OV4689_REG_V_WIN_OFF, 4, &ret);
cci_write(rm, OV4689_REG_VFIFO_CTRL_01, 167, &ret);
return ret;
}
static int ov4689_setup_blc_anchors(struct ov4689 *ov4689)
{
struct regmap *rm = ov4689->regmap;
int ret = 0;
cci_write(rm, OV4689_REG_ANCHOR_LEFT_START, 16, &ret);
cci_write(rm, OV4689_REG_ANCHOR_LEFT_END, 1999, &ret);
cci_write(rm, OV4689_REG_ANCHOR_RIGHT_START, 2400, &ret);
cci_write(rm, OV4689_REG_ANCHOR_RIGHT_END, 2415, &ret);
return ret;
}
static int ov4689_s_stream(struct v4l2_subdev *sd, int on) static int ov4689_s_stream(struct v4l2_subdev *sd, int on)
{ {
struct ov4689 *ov4689 = to_ov4689(sd); struct ov4689 *ov4689 = to_ov4689(sd);
struct i2c_client *client = ov4689->client; struct v4l2_subdev_state *sd_state;
struct device *dev = ov4689->dev;
int ret = 0; int ret = 0;
mutex_lock(&ov4689->mutex); sd_state = v4l2_subdev_lock_and_get_active_state(&ov4689->subdev);
if (on) { if (on) {
ret = pm_runtime_resume_and_get(&client->dev); ret = pm_runtime_resume_and_get(dev);
if (ret < 0) if (ret < 0)
goto unlock_and_return; goto unlock_and_return;
ret = ov4689_write_array(ov4689->client, ret = cci_multi_reg_write(ov4689->regmap,
ov4689->cur_mode->reg_list); ov4689->cur_mode->reg_list,
ov4689->cur_mode->num_regs,
NULL);
if (ret) { if (ret) {
pm_runtime_put(&client->dev); pm_runtime_put(dev);
goto unlock_and_return;
}
ret = ov4689_setup_timings(ov4689);
if (ret) {
pm_runtime_put(dev);
goto unlock_and_return;
}
ret = ov4689_setup_blc_anchors(ov4689);
if (ret) {
pm_runtime_put(dev);
goto unlock_and_return; goto unlock_and_return;
} }
ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler); ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler);
if (ret) { if (ret) {
pm_runtime_put(&client->dev); pm_runtime_put(dev);
goto unlock_and_return; goto unlock_and_return;
} }
ret = ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, ret = cci_write(ov4689->regmap, OV4689_REG_CTRL_MODE,
OV4689_REG_VALUE_08BIT, OV4689_MODE_STREAMING, NULL);
OV4689_MODE_STREAMING);
if (ret) { if (ret) {
pm_runtime_put(&client->dev); pm_runtime_put(dev);
goto unlock_and_return; goto unlock_and_return;
} }
} else { } else {
ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, cci_write(ov4689->regmap, OV4689_REG_CTRL_MODE,
OV4689_REG_VALUE_08BIT, OV4689_MODE_SW_STANDBY, NULL);
OV4689_MODE_SW_STANDBY); pm_runtime_mark_last_busy(dev);
pm_runtime_put(&client->dev); pm_runtime_put_autosuspend(dev);
} }
unlock_and_return: unlock_and_return:
mutex_unlock(&ov4689->mutex); v4l2_subdev_unlock_state(sd_state);
return ret; return ret;
} }
@ -563,18 +565,13 @@ static int __maybe_unused ov4689_power_off(struct device *dev)
return 0; return 0;
} }
static int ov4689_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) static int ov4689_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state)
{ {
struct ov4689 *ov4689 = to_ov4689(sd); struct v4l2_mbus_framefmt *fmt =
struct v4l2_mbus_framefmt *try_fmt; v4l2_subdev_state_get_format(sd_state, 0);
mutex_lock(&ov4689->mutex); ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], fmt);
try_fmt = v4l2_subdev_state_get_format(fh->state, 0);
/* Initialize try_fmt */
ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], try_fmt);
mutex_unlock(&ov4689->mutex);
return 0; return 0;
} }
@ -583,10 +580,6 @@ static const struct dev_pm_ops ov4689_pm_ops = {
SET_RUNTIME_PM_OPS(ov4689_power_off, ov4689_power_on, NULL) SET_RUNTIME_PM_OPS(ov4689_power_off, ov4689_power_on, NULL)
}; };
static const struct v4l2_subdev_internal_ops ov4689_internal_ops = {
.open = ov4689_open,
};
static const struct v4l2_subdev_video_ops ov4689_video_ops = { static const struct v4l2_subdev_video_ops ov4689_video_ops = {
.s_stream = ov4689_s_stream, .s_stream = ov4689_s_stream,
}; };
@ -594,11 +587,15 @@ static const struct v4l2_subdev_video_ops ov4689_video_ops = {
static const struct v4l2_subdev_pad_ops ov4689_pad_ops = { static const struct v4l2_subdev_pad_ops ov4689_pad_ops = {
.enum_mbus_code = ov4689_enum_mbus_code, .enum_mbus_code = ov4689_enum_mbus_code,
.enum_frame_size = ov4689_enum_frame_sizes, .enum_frame_size = ov4689_enum_frame_sizes,
.get_fmt = ov4689_get_fmt, .get_fmt = v4l2_subdev_get_fmt,
.set_fmt = ov4689_set_fmt, .set_fmt = ov4689_set_fmt,
.get_selection = ov4689_get_selection, .get_selection = ov4689_get_selection,
}; };
static const struct v4l2_subdev_internal_ops ov4689_internal_ops = {
.init_state = ov4689_init_state,
};
static const struct v4l2_subdev_ops ov4689_subdev_ops = { static const struct v4l2_subdev_ops ov4689_subdev_ops = {
.video = &ov4689_video_ops, .video = &ov4689_video_ops,
.pad = &ov4689_pad_ops, .pad = &ov4689_pad_ops,
@ -610,7 +607,6 @@ static const struct v4l2_subdev_ops ov4689_subdev_ops = {
*/ */
static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result) static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result)
{ {
const struct device *dev = &ov4689->client->dev;
const struct ov4689_gain_range *range; const struct ov4689_gain_range *range;
unsigned int n; unsigned int n;
@ -621,7 +617,8 @@ static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result)
} }
if (n == ARRAY_SIZE(ov4689_gain_ranges)) { if (n == ARRAY_SIZE(ov4689_gain_ranges)) {
dev_warn_ratelimited(dev, "no mapping found for gain %d\n", dev_warn_ratelimited(ov4689->dev,
"no mapping found for gain %d\n",
logical_gain); logical_gain);
return -EINVAL; return -EINVAL;
} }
@ -637,10 +634,11 @@ static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl)
{ {
struct ov4689 *ov4689 = struct ov4689 *ov4689 =
container_of(ctrl->handler, struct ov4689, ctrl_handler); container_of(ctrl->handler, struct ov4689, ctrl_handler);
struct i2c_client *client = ov4689->client; struct regmap *regmap = ov4689->regmap;
int sensor_gain; struct device *dev = ov4689->dev;
int sensor_gain = 0;
s64 max_expo; s64 max_expo;
int ret; int ret = 0;
/* Propagate change of current control to all related controls */ /* Propagate change of current control to all related controls */
switch (ctrl->id) { switch (ctrl->id) {
@ -654,44 +652,58 @@ static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl)
break; break;
} }
if (!pm_runtime_get_if_in_use(&client->dev)) if (!pm_runtime_get_if_in_use(dev))
return 0; return 0;
switch (ctrl->id) { switch (ctrl->id) {
case V4L2_CID_EXPOSURE: case V4L2_CID_EXPOSURE:
/* 4 least significant bits of expsoure are fractional part */ /* 4 least significant bits of exposure are fractional part */
ret = ov4689_write_reg(ov4689->client, OV4689_REG_EXPOSURE, cci_write(regmap, OV4689_REG_EXPOSURE, ctrl->val << 4, &ret);
OV4689_REG_VALUE_24BIT, ctrl->val << 4);
break; break;
case V4L2_CID_ANALOGUE_GAIN: case V4L2_CID_ANALOGUE_GAIN:
ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain); ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain);
cci_write(regmap, OV4689_REG_GAIN, sensor_gain, &ret);
ret = ret ?:
ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_H,
OV4689_REG_VALUE_08BIT,
(sensor_gain >> OV4689_GAIN_H_SHIFT) &
OV4689_GAIN_H_MASK);
ret = ret ?:
ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_L,
OV4689_REG_VALUE_08BIT,
sensor_gain & OV4689_GAIN_L_MASK);
break; break;
case V4L2_CID_VBLANK: case V4L2_CID_VBLANK:
ret = ov4689_write_reg(ov4689->client, OV4689_REG_VTS, cci_write(regmap, OV4689_REG_VTS,
OV4689_REG_VALUE_16BIT, ctrl->val + ov4689->cur_mode->height, &ret);
ctrl->val + ov4689->cur_mode->height);
break; break;
case V4L2_CID_TEST_PATTERN: case V4L2_CID_TEST_PATTERN:
ret = ov4689_enable_test_pattern(ov4689, ctrl->val); ret = ov4689_enable_test_pattern(ov4689, ctrl->val);
break; break;
case V4L2_CID_HBLANK:
cci_write(regmap, OV4689_REG_HTS,
(ctrl->val + ov4689->cur_mode->width) /
OV4689_HTS_DIVIDER, &ret);
break;
case V4L2_CID_VFLIP:
cci_update_bits(regmap, OV4689_REG_TIMING_FORMAT1,
OV4689_TIMING_FLIP_MASK,
ctrl->val ? OV4689_TIMING_FLIP_BOTH : 0, &ret);
break;
case V4L2_CID_HFLIP:
cci_update_bits(regmap, OV4689_REG_TIMING_FORMAT2,
OV4689_TIMING_FLIP_MASK,
ctrl->val ? 0 : OV4689_TIMING_FLIP_BOTH, &ret);
break;
case V4L2_CID_DIGITAL_GAIN:
cci_write(regmap, OV4689_REG_DIG_GAIN, ctrl->val, &ret);
break;
case V4L2_CID_RED_BALANCE:
cci_write(regmap, OV4689_REG_WB_GAIN_RED, ctrl->val, &ret);
break;
case V4L2_CID_BLUE_BALANCE:
cci_write(regmap, OV4689_REG_WB_GAIN_BLUE, ctrl->val, &ret);
break;
default: default:
dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", dev_warn(dev, "%s Unhandled id:0x%x, val:0x%x\n",
__func__, ctrl->id, ctrl->val); __func__, ctrl->id, ctrl->val);
ret = -EINVAL; ret = -EINVAL;
break; break;
} }
pm_runtime_put(&client->dev); pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret; return ret;
} }
@ -707,16 +719,15 @@ static int ov4689_initialize_controls(struct ov4689 *ov4689)
struct v4l2_ctrl_handler *handler; struct v4l2_ctrl_handler *handler;
const struct ov4689_mode *mode; const struct ov4689_mode *mode;
s64 exposure_max, vblank_def; s64 exposure_max, vblank_def;
s64 hblank_def, hblank_min;
struct v4l2_ctrl *ctrl; struct v4l2_ctrl *ctrl;
s64 h_blank_def;
int ret; int ret;
handler = &ov4689->ctrl_handler; handler = &ov4689->ctrl_handler;
mode = ov4689->cur_mode; mode = ov4689->cur_mode;
ret = v4l2_ctrl_handler_init(handler, 10); ret = v4l2_ctrl_handler_init(handler, 15);
if (ret) if (ret)
return ret; return ret;
handler->lock = &ov4689->mutex;
ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0, ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0,
link_freq_menu_items); link_freq_menu_items);
@ -726,11 +737,11 @@ static int ov4689_initialize_controls(struct ov4689 *ov4689)
v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0,
mode->pixel_rate, 1, mode->pixel_rate); mode->pixel_rate, 1, mode->pixel_rate);
h_blank_def = mode->hts_def - mode->width; hblank_def = mode->hts_def - mode->width;
ctrl = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, h_blank_def, hblank_min = mode->hts_min - mode->width;
h_blank_def, 1, h_blank_def); v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_HBLANK,
if (ctrl) hblank_min, OV4689_HTS_MAX - mode->width,
ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; OV4689_HTS_DIVIDER, hblank_def);
vblank_def = mode->vts_def - mode->height; vblank_def = mode->vts_def - mode->height;
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK, v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK,
@ -754,10 +765,24 @@ static int ov4689_initialize_controls(struct ov4689 *ov4689)
ARRAY_SIZE(ov4689_test_pattern_menu) - 1, ARRAY_SIZE(ov4689_test_pattern_menu) - 1,
0, 0, ov4689_test_pattern_menu); 0, 0, ov4689_test_pattern_menu);
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
OV4689_DIG_GAIN_MIN, OV4689_DIG_GAIN_MAX,
OV4689_DIG_GAIN_STEP, OV4689_DIG_GAIN_DEFAULT);
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_RED_BALANCE,
OV4689_WB_GAIN_MIN, OV4689_WB_GAIN_MAX,
OV4689_WB_GAIN_STEP, OV4689_WB_GAIN_DEFAULT);
v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_BLUE_BALANCE,
OV4689_WB_GAIN_MIN, OV4689_WB_GAIN_MAX,
OV4689_WB_GAIN_STEP, OV4689_WB_GAIN_DEFAULT);
if (handler->error) { if (handler->error) {
ret = handler->error; ret = handler->error;
dev_err(&ov4689->client->dev, "Failed to init controls(%d)\n", dev_err(ov4689->dev, "Failed to init controls(%d)\n", ret);
ret);
goto err_free_handler; goto err_free_handler;
} }
@ -783,19 +808,18 @@ err_free_handler:
static int ov4689_check_sensor_id(struct ov4689 *ov4689, static int ov4689_check_sensor_id(struct ov4689 *ov4689,
struct i2c_client *client) struct i2c_client *client)
{ {
struct device *dev = &ov4689->client->dev; struct device *dev = ov4689->dev;
u32 id = 0; u64 id = 0;
int ret; int ret;
ret = ov4689_read_reg(client, OV4689_REG_CHIP_ID, ret = cci_read(ov4689->regmap, OV4689_REG_CHIP_ID, &id, NULL);
OV4689_REG_VALUE_16BIT, &id);
if (ret) { if (ret) {
dev_err(dev, "Cannot read sensor ID\n"); dev_err(dev, "Cannot read sensor ID\n");
return ret; return ret;
} }
if (id != CHIP_ID) { if (id != CHIP_ID) {
dev_err(dev, "Unexpected sensor ID %06x, expected %06x\n", dev_err(dev, "Unexpected sensor ID %06llx, expected %06x\n",
id, CHIP_ID); id, CHIP_ID);
return -ENODEV; return -ENODEV;
} }
@ -812,7 +836,7 @@ static int ov4689_configure_regulators(struct ov4689 *ov4689)
for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++) for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++)
ov4689->supplies[i].supply = ov4689_supply_names[i]; ov4689->supplies[i].supply = ov4689_supply_names[i];
return devm_regulator_bulk_get(&ov4689->client->dev, return devm_regulator_bulk_get(ov4689->dev,
ARRAY_SIZE(ov4689_supply_names), ARRAY_SIZE(ov4689_supply_names),
ov4689->supplies); ov4689->supplies);
} }
@ -881,7 +905,8 @@ static int ov4689_probe(struct i2c_client *client)
if (!ov4689) if (!ov4689)
return -ENOMEM; return -ENOMEM;
ov4689->client = client; ov4689->dev = dev;
ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520]; ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520];
ov4689->xvclk = devm_clk_get_optional(dev, NULL); ov4689->xvclk = devm_clk_get_optional(dev, NULL);
@ -905,6 +930,13 @@ static int ov4689_probe(struct i2c_client *client)
return -EINVAL; return -EINVAL;
} }
ov4689->regmap = devm_cci_regmap_init_i2c(client, 16);
if (IS_ERR(ov4689->regmap)) {
ret = PTR_ERR(ov4689->regmap);
dev_err(dev, "failed to initialize CCI: %d\n", ret);
return ret;
}
ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset", ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_LOW); GPIOD_OUT_LOW);
if (IS_ERR(ov4689->reset_gpio)) { if (IS_ERR(ov4689->reset_gpio)) {
@ -923,13 +955,15 @@ static int ov4689_probe(struct i2c_client *client)
return dev_err_probe(dev, ret, return dev_err_probe(dev, ret,
"Failed to get power regulators\n"); "Failed to get power regulators\n");
mutex_init(&ov4689->mutex);
sd = &ov4689->subdev; sd = &ov4689->subdev;
v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops); v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops);
sd->internal_ops = &ov4689_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
ret = ov4689_initialize_controls(ov4689); ret = ov4689_initialize_controls(ov4689);
if (ret) if (ret) {
goto err_destroy_mutex; dev_err(dev, "Failed to initialize controls\n");
return ret;
}
ret = ov4689_power_on(dev); ret = ov4689_power_on(dev);
if (ret) if (ret)
@ -939,35 +973,47 @@ static int ov4689_probe(struct i2c_client *client)
if (ret) if (ret)
goto err_power_off; goto err_power_off;
sd->internal_ops = &ov4689_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
ov4689->pad.flags = MEDIA_PAD_FL_SOURCE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
ov4689->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad); ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad);
if (ret < 0) if (ret < 0)
goto err_power_off; goto err_power_off;
ret = v4l2_async_register_subdev_sensor(sd); sd->state_lock = ov4689->ctrl_handler.lock;
ret = v4l2_subdev_init_finalize(sd);
if (ret) { if (ret) {
dev_err(dev, "v4l2 async register subdev failed\n"); dev_err(dev, "Could not register v4l2 device\n");
goto err_clean_entity; goto err_clean_entity;
} }
pm_runtime_set_active(dev); pm_runtime_set_active(dev);
pm_runtime_get_noresume(dev);
pm_runtime_enable(dev); pm_runtime_enable(dev);
pm_runtime_idle(dev); pm_runtime_set_autosuspend_delay(dev, 1000);
pm_runtime_use_autosuspend(dev);
ret = v4l2_async_register_subdev_sensor(sd);
if (ret) {
dev_err(dev, "v4l2 async register subdev failed\n");
goto err_clean_subdev_pm;
}
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0; return 0;
err_clean_subdev_pm:
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
v4l2_subdev_cleanup(sd);
err_clean_entity: err_clean_entity:
media_entity_cleanup(&sd->entity); media_entity_cleanup(&sd->entity);
err_power_off: err_power_off:
ov4689_power_off(dev); ov4689_power_off(dev);
err_free_handler: err_free_handler:
v4l2_ctrl_handler_free(&ov4689->ctrl_handler); v4l2_ctrl_handler_free(&ov4689->ctrl_handler);
err_destroy_mutex:
mutex_destroy(&ov4689->mutex);
return ret; return ret;
} }
@ -979,9 +1025,8 @@ static void ov4689_remove(struct i2c_client *client)
v4l2_async_unregister_subdev(sd); v4l2_async_unregister_subdev(sd);
media_entity_cleanup(&sd->entity); media_entity_cleanup(&sd->entity);
v4l2_subdev_cleanup(sd);
v4l2_ctrl_handler_free(&ov4689->ctrl_handler); v4l2_ctrl_handler_free(&ov4689->ctrl_handler);
mutex_destroy(&ov4689->mutex);
pm_runtime_disable(&client->dev); pm_runtime_disable(&client->dev);
if (!pm_runtime_status_suspended(&client->dev)) if (!pm_runtime_status_suspended(&client->dev))

View File

@ -463,8 +463,8 @@ static int rdacm20_initialize(struct rdacm20_device *dev)
return ret; return ret;
/* /*
* Ensure that we have a good link configuration before attempting to * Ensure that we have a good link configuration before attempting to
* identify the device. * identify the device.
*/ */
ret = max9271_configure_i2c(&dev->serializer, ret = max9271_configure_i2c(&dev->serializer,
MAX9271_I2CSLVSH_469NS_234NS | MAX9271_I2CSLVSH_469NS_234NS |

View File

@ -326,7 +326,7 @@ static int mipid02_configure_from_rx_speed(struct mipid02_dev *bridge,
} }
dev_dbg(&client->dev, "detect link_freq = %lld Hz", link_freq); dev_dbg(&client->dev, "detect link_freq = %lld Hz", link_freq);
do_div(ui_4, link_freq); ui_4 = div64_u64(ui_4, link_freq);
bridge->r.clk_lane_reg1 |= ui_4 << 2; bridge->r.clk_lane_reg1 |= ui_4 << 2;
return 0; return 0;

View File

@ -1521,11 +1521,14 @@ static int tc358743_g_input_status(struct v4l2_subdev *sd, u32 *status)
return 0; return 0;
} }
static int tc358743_s_dv_timings(struct v4l2_subdev *sd, static int tc358743_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct tc358743_state *state = to_state(sd); struct tc358743_state *state = to_state(sd);
if (pad != 0)
return -EINVAL;
if (!timings) if (!timings)
return -EINVAL; return -EINVAL;
@ -1553,11 +1556,14 @@ static int tc358743_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int tc358743_g_dv_timings(struct v4l2_subdev *sd, static int tc358743_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct tc358743_state *state = to_state(sd); struct tc358743_state *state = to_state(sd);
if (pad != 0)
return -EINVAL;
*timings = state->timings; *timings = state->timings;
return 0; return 0;
@ -1573,11 +1579,14 @@ static int tc358743_enum_dv_timings(struct v4l2_subdev *sd,
&tc358743_timings_cap, NULL, NULL); &tc358743_timings_cap, NULL, NULL);
} }
static int tc358743_query_dv_timings(struct v4l2_subdev *sd, static int tc358743_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
int ret; int ret;
if (pad != 0)
return -EINVAL;
ret = tc358743_get_detected_timings(sd, timings); ret = tc358743_get_detected_timings(sd, timings);
if (ret) if (ret)
return ret; return ret;
@ -1822,9 +1831,6 @@ static const struct v4l2_subdev_core_ops tc358743_core_ops = {
static const struct v4l2_subdev_video_ops tc358743_video_ops = { static const struct v4l2_subdev_video_ops tc358743_video_ops = {
.g_input_status = tc358743_g_input_status, .g_input_status = tc358743_g_input_status,
.s_dv_timings = tc358743_s_dv_timings,
.g_dv_timings = tc358743_g_dv_timings,
.query_dv_timings = tc358743_query_dv_timings,
.s_stream = tc358743_s_stream, .s_stream = tc358743_s_stream,
}; };
@ -1834,6 +1840,9 @@ static const struct v4l2_subdev_pad_ops tc358743_pad_ops = {
.get_fmt = tc358743_get_fmt, .get_fmt = tc358743_get_fmt,
.get_edid = tc358743_g_edid, .get_edid = tc358743_g_edid,
.set_edid = tc358743_s_edid, .set_edid = tc358743_s_edid,
.s_dv_timings = tc358743_s_dv_timings,
.g_dv_timings = tc358743_g_dv_timings,
.query_dv_timings = tc358743_query_dv_timings,
.enum_dv_timings = tc358743_enum_dv_timings, .enum_dv_timings = tc358743_enum_dv_timings,
.dv_timings_cap = tc358743_dv_timings_cap, .dv_timings_cap = tc358743_dv_timings_cap,
.get_mbus_config = tc358743_get_mbus_config, .get_mbus_config = tc358743_get_mbus_config,
@ -2110,7 +2119,7 @@ static int tc358743_probe(struct i2c_client *client)
tc358743_initial_setup(sd); tc358743_initial_setup(sd);
tc358743_s_dv_timings(sd, &default_timing); tc358743_s_dv_timings(sd, 0, &default_timing);
tc358743_set_csi_color_space(sd); tc358743_set_csi_color_space(sd);

View File

@ -844,8 +844,7 @@ static unsigned long tc358746_find_pll_settings(struct tc358746 *tc358746,
continue; continue;
tmp = fout * postdiv; tmp = fout * postdiv;
do_div(tmp, fin); mul = div64_ul(tmp, fin);
mul = tmp;
if (mul > 511) if (mul > 511)
continue; continue;

View File

@ -1669,8 +1669,8 @@ tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status)
return 0; return 0;
}; };
static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct tda1997x_state *state = to_state(sd); struct tda1997x_state *state = to_state(sd);
@ -1694,7 +1694,7 @@ static int tda1997x_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct tda1997x_state *state = to_state(sd); struct tda1997x_state *state = to_state(sd);
@ -1707,7 +1707,7 @@ static int tda1997x_g_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct tda1997x_state *state = to_state(sd); struct tda1997x_state *state = to_state(sd);
@ -1724,9 +1724,6 @@ static int tda1997x_query_dv_timings(struct v4l2_subdev *sd,
static const struct v4l2_subdev_video_ops tda1997x_video_ops = { static const struct v4l2_subdev_video_ops tda1997x_video_ops = {
.g_input_status = tda1997x_g_input_status, .g_input_status = tda1997x_g_input_status,
.s_dv_timings = tda1997x_s_dv_timings,
.g_dv_timings = tda1997x_g_dv_timings,
.query_dv_timings = tda1997x_query_dv_timings,
}; };
@ -1930,6 +1927,9 @@ static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = {
.set_fmt = tda1997x_set_format, .set_fmt = tda1997x_set_format,
.get_edid = tda1997x_get_edid, .get_edid = tda1997x_get_edid,
.set_edid = tda1997x_set_edid, .set_edid = tda1997x_set_edid,
.s_dv_timings = tda1997x_s_dv_timings,
.g_dv_timings = tda1997x_g_dv_timings,
.query_dv_timings = tda1997x_query_dv_timings,
.dv_timings_cap = tda1997x_get_dv_timings_cap, .dv_timings_cap = tda1997x_get_dv_timings_cap,
.enum_dv_timings = tda1997x_enum_dv_timings, .enum_dv_timings = tda1997x_enum_dv_timings,
}; };

View File

@ -193,8 +193,8 @@ static int ths7303_s_stream(struct v4l2_subdev *sd, int enable)
} }
/* for setting filter for HD output */ /* for setting filter for HD output */
static int ths7303_s_dv_timings(struct v4l2_subdev *sd, static int ths7303_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *dv_timings) struct v4l2_dv_timings *dv_timings)
{ {
struct ths7303_state *state = to_state(sd); struct ths7303_state *state = to_state(sd);
@ -210,7 +210,6 @@ static int ths7303_s_dv_timings(struct v4l2_subdev *sd,
static const struct v4l2_subdev_video_ops ths7303_video_ops = { static const struct v4l2_subdev_video_ops ths7303_video_ops = {
.s_stream = ths7303_s_stream, .s_stream = ths7303_s_stream,
.s_std_output = ths7303_s_std_output, .s_std_output = ths7303_s_std_output,
.s_dv_timings = ths7303_s_dv_timings,
}; };
#ifdef CONFIG_VIDEO_ADV_DEBUG #ifdef CONFIG_VIDEO_ADV_DEBUG
@ -317,9 +316,14 @@ static const struct v4l2_subdev_core_ops ths7303_core_ops = {
#endif #endif
}; };
static const struct v4l2_subdev_pad_ops ths7303_pad_ops = {
.s_dv_timings = ths7303_s_dv_timings,
};
static const struct v4l2_subdev_ops ths7303_ops = { static const struct v4l2_subdev_ops ths7303_ops = {
.core = &ths7303_core_ops, .core = &ths7303_core_ops,
.video = &ths7303_video_ops, .video = &ths7303_video_ops,
.pad = &ths7303_pad_ops,
}; };
static int ths7303_probe(struct i2c_client *client) static int ths7303_probe(struct i2c_client *client)

View File

@ -358,13 +358,16 @@ static void ths8200_setup(struct v4l2_subdev *sd, struct v4l2_bt_timings *bt)
bt->hsync, bt->vsync); bt->hsync, bt->vsync);
} }
static int ths8200_s_dv_timings(struct v4l2_subdev *sd, static int ths8200_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct ths8200_state *state = to_state(sd); struct ths8200_state *state = to_state(sd);
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
if (!v4l2_valid_dv_timings(timings, &ths8200_timings_cap, if (!v4l2_valid_dv_timings(timings, &ths8200_timings_cap,
NULL, NULL)) NULL, NULL))
return -EINVAL; return -EINVAL;
@ -385,13 +388,16 @@ static int ths8200_s_dv_timings(struct v4l2_subdev *sd,
return 0; return 0;
} }
static int ths8200_g_dv_timings(struct v4l2_subdev *sd, static int ths8200_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
struct ths8200_state *state = to_state(sd); struct ths8200_state *state = to_state(sd);
v4l2_dbg(1, debug, sd, "%s:\n", __func__); v4l2_dbg(1, debug, sd, "%s:\n", __func__);
if (pad != 0)
return -EINVAL;
*timings = state->dv_timings; *timings = state->dv_timings;
return 0; return 0;
@ -420,11 +426,11 @@ static int ths8200_dv_timings_cap(struct v4l2_subdev *sd,
/* Specific video subsystem operation handlers */ /* Specific video subsystem operation handlers */
static const struct v4l2_subdev_video_ops ths8200_video_ops = { static const struct v4l2_subdev_video_ops ths8200_video_ops = {
.s_stream = ths8200_s_stream, .s_stream = ths8200_s_stream,
.s_dv_timings = ths8200_s_dv_timings,
.g_dv_timings = ths8200_g_dv_timings,
}; };
static const struct v4l2_subdev_pad_ops ths8200_pad_ops = { static const struct v4l2_subdev_pad_ops ths8200_pad_ops = {
.s_dv_timings = ths8200_s_dv_timings,
.g_dv_timings = ths8200_g_dv_timings,
.enum_dv_timings = ths8200_enum_dv_timings, .enum_dv_timings = ths8200_enum_dv_timings,
.dv_timings_cap = ths8200_dv_timings_cap, .dv_timings_cap = ths8200_dv_timings_cap,
}; };

View File

@ -546,13 +546,16 @@ static int tvp7002_write_inittab(struct v4l2_subdev *sd,
return error; return error;
} }
static int tvp7002_s_dv_timings(struct v4l2_subdev *sd, static int tvp7002_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *dv_timings) struct v4l2_dv_timings *dv_timings)
{ {
struct tvp7002 *device = to_tvp7002(sd); struct tvp7002 *device = to_tvp7002(sd);
const struct v4l2_bt_timings *bt = &dv_timings->bt; const struct v4l2_bt_timings *bt = &dv_timings->bt;
int i; int i;
if (pad != 0)
return -EINVAL;
if (dv_timings->type != V4L2_DV_BT_656_1120) if (dv_timings->type != V4L2_DV_BT_656_1120)
return -EINVAL; return -EINVAL;
for (i = 0; i < NUM_TIMINGS; i++) { for (i = 0; i < NUM_TIMINGS; i++) {
@ -566,11 +569,14 @@ static int tvp7002_s_dv_timings(struct v4l2_subdev *sd,
return -EINVAL; return -EINVAL;
} }
static int tvp7002_g_dv_timings(struct v4l2_subdev *sd, static int tvp7002_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *dv_timings) struct v4l2_dv_timings *dv_timings)
{ {
struct tvp7002 *device = to_tvp7002(sd); struct tvp7002 *device = to_tvp7002(sd);
if (pad != 0)
return -EINVAL;
*dv_timings = device->current_timings->timings; *dv_timings = device->current_timings->timings;
return 0; return 0;
} }
@ -659,12 +665,16 @@ static int tvp7002_query_dv(struct v4l2_subdev *sd, int *index)
return 0; return 0;
} }
static int tvp7002_query_dv_timings(struct v4l2_subdev *sd, static int tvp7002_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings) struct v4l2_dv_timings *timings)
{ {
int index; int index;
int err = tvp7002_query_dv(sd, &index); int err;
if (pad != 0)
return -EINVAL;
err = tvp7002_query_dv(sd, &index);
if (err) if (err)
return err; return err;
*timings = tvp7002_timings[index].timings; *timings = tvp7002_timings[index].timings;
@ -861,9 +871,6 @@ static const struct v4l2_subdev_core_ops tvp7002_core_ops = {
/* Specific video subsystem operation handlers */ /* Specific video subsystem operation handlers */
static const struct v4l2_subdev_video_ops tvp7002_video_ops = { static const struct v4l2_subdev_video_ops tvp7002_video_ops = {
.g_dv_timings = tvp7002_g_dv_timings,
.s_dv_timings = tvp7002_s_dv_timings,
.query_dv_timings = tvp7002_query_dv_timings,
.s_stream = tvp7002_s_stream, .s_stream = tvp7002_s_stream,
}; };
@ -872,6 +879,9 @@ static const struct v4l2_subdev_pad_ops tvp7002_pad_ops = {
.enum_mbus_code = tvp7002_enum_mbus_code, .enum_mbus_code = tvp7002_enum_mbus_code,
.get_fmt = tvp7002_get_pad_format, .get_fmt = tvp7002_get_pad_format,
.set_fmt = tvp7002_set_pad_format, .set_fmt = tvp7002_set_pad_format,
.g_dv_timings = tvp7002_g_dv_timings,
.s_dv_timings = tvp7002_s_dv_timings,
.query_dv_timings = tvp7002_query_dv_timings,
.enum_dv_timings = tvp7002_enum_dv_timings, .enum_dv_timings = tvp7002_enum_dv_timings,
}; };
@ -1001,7 +1011,7 @@ static int tvp7002_probe(struct i2c_client *c)
/* Set registers according to default video mode */ /* Set registers according to default video mode */
timings = device->current_timings->timings; timings = device->current_timings->timings;
error = tvp7002_s_dv_timings(sd, &timings); error = tvp7002_s_dv_timings(sd, 0, &timings);
#if defined(CONFIG_MEDIA_CONTROLLER) #if defined(CONFIG_MEDIA_CONTROLLER)
device->pad.flags = MEDIA_PAD_FL_SOURCE; device->pad.flags = MEDIA_PAD_FL_SOURCE;

View File

@ -245,15 +245,14 @@ int __must_check media_devnode_register(struct media_device *mdev,
kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor); kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor);
/* Part 3: Add the media and char device */ /* Part 3: Add the media and char device */
set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
ret = cdev_device_add(&devnode->cdev, &devnode->dev); ret = cdev_device_add(&devnode->cdev, &devnode->dev);
if (ret < 0) { if (ret < 0) {
clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
pr_err("%s: cdev_device_add failed\n", __func__); pr_err("%s: cdev_device_add failed\n", __func__);
goto cdev_add_error; goto cdev_add_error;
} }
/* Part 4: Activate this minor. The char device can now be used. */
set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
return 0; return 0;
cdev_add_error: cdev_add_error:

View File

@ -619,6 +619,12 @@ static int media_pipeline_explore_next_link(struct media_pipeline *pipe,
link = list_entry(entry->links, typeof(*link), list); link = list_entry(entry->links, typeof(*link), list);
last_link = media_pipeline_walk_pop(walk); last_link = media_pipeline_walk_pop(walk);
if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) {
dev_dbg(walk->mdev->dev,
"media pipeline: skipping link (not data-link)\n");
return 0;
}
dev_dbg(walk->mdev->dev, dev_dbg(walk->mdev->dev,
"media pipeline: exploring link '%s':%u -> '%s':%u\n", "media pipeline: exploring link '%s':%u -> '%s':%u\n",
link->source->entity->name, link->source->index, link->source->entity->name, link->source->index,

View File

@ -344,30 +344,7 @@ static struct sdio_driver smssdio_driver = {
.probe = smssdio_probe, .probe = smssdio_probe,
.remove = smssdio_remove, .remove = smssdio_remove,
}; };
module_sdio_driver(smssdio_driver);
/*******************************************************************/
/* Module functions */
/*******************************************************************/
static int __init smssdio_module_init(void)
{
int ret = 0;
printk(KERN_INFO "smssdio: Siano SMS1xxx SDIO driver\n");
printk(KERN_INFO "smssdio: Copyright Pierre Ossman\n");
ret = sdio_register_driver(&smssdio_driver);
return ret;
}
static void __exit smssdio_module_exit(void)
{
sdio_unregister_driver(&smssdio_driver);
}
module_init(smssdio_module_init);
module_exit(smssdio_module_exit);
MODULE_DESCRIPTION("Siano SMS1xxx SDIO driver"); MODULE_DESCRIPTION("Siano SMS1xxx SDIO driver");
MODULE_AUTHOR("Pierre Ossman"); MODULE_AUTHOR("Pierre Ossman");

View File

@ -633,7 +633,7 @@ static int cobalt_s_dv_timings(struct file *file, void *priv_fh,
return -EBUSY; return -EBUSY;
err = v4l2_subdev_call(s->sd, err = v4l2_subdev_call(s->sd,
video, s_dv_timings, timings); pad, s_dv_timings, 0, timings);
if (!err) { if (!err) {
s->timings = *timings; s->timings = *timings;
s->width = timings->bt.width; s->width = timings->bt.width;
@ -653,7 +653,7 @@ static int cobalt_g_dv_timings(struct file *file, void *priv_fh,
return 0; return 0;
} }
return v4l2_subdev_call(s->sd, return v4l2_subdev_call(s->sd,
video, g_dv_timings, timings); pad, g_dv_timings, 0, timings);
} }
static int cobalt_query_dv_timings(struct file *file, void *priv_fh, static int cobalt_query_dv_timings(struct file *file, void *priv_fh,
@ -666,7 +666,7 @@ static int cobalt_query_dv_timings(struct file *file, void *priv_fh,
return 0; return 0;
} }
return v4l2_subdev_call(s->sd, return v4l2_subdev_call(s->sd,
video, query_dv_timings, timings); pad, query_dv_timings, 0, timings);
} }
static int cobalt_dv_timings_cap(struct file *file, void *priv_fh, static int cobalt_dv_timings_cap(struct file *file, void *priv_fh,
@ -1080,7 +1080,7 @@ static int cobalt_g_pixelaspect(struct file *file, void *fh,
if (s->input == 1) if (s->input == 1)
timings = cea1080p60; timings = cea1080p60;
else else
err = v4l2_subdev_call(s->sd, video, g_dv_timings, &timings); err = v4l2_subdev_call(s->sd, pad, g_dv_timings, 0, &timings);
if (!err) if (!err)
*f = v4l2_dv_timings_aspect_ratio(&timings); *f = v4l2_dv_timings_aspect_ratio(&timings);
return err; return err;
@ -1099,7 +1099,7 @@ static int cobalt_g_selection(struct file *file, void *fh,
if (s->input == 1) if (s->input == 1)
timings = cea1080p60; timings = cea1080p60;
else else
err = v4l2_subdev_call(s->sd, video, g_dv_timings, &timings); err = v4l2_subdev_call(s->sd, pad, g_dv_timings, 0, &timings);
if (err) if (err)
return err; return err;
@ -1243,7 +1243,7 @@ static int cobalt_node_register(struct cobalt *cobalt, int node)
if (s->sd) if (s->sd)
vdev->ctrl_handler = s->sd->ctrl_handler; vdev->ctrl_handler = s->sd->ctrl_handler;
s->timings = dv1080p60; s->timings = dv1080p60;
v4l2_subdev_call(s->sd, video, s_dv_timings, &s->timings); v4l2_subdev_call(s->sd, pad, s_dv_timings, 0, &s->timings);
if (!s->is_output && s->sd) if (!s->is_output && s->sd)
cobalt_enable_input(s); cobalt_enable_input(s);
vdev->ioctl_ops = s->is_dummy ? &cobalt_ioctl_empty_ops : vdev->ioctl_ops = s->is_dummy ? &cobalt_ioctl_empty_ops :

View File

@ -1,11 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
source "drivers/media/pci/intel/ipu3/Kconfig" source "drivers/media/pci/intel/ipu3/Kconfig"
source "drivers/media/pci/intel/ipu6/Kconfig"
source "drivers/media/pci/intel/ivsc/Kconfig" source "drivers/media/pci/intel/ivsc/Kconfig"
config IPU_BRIDGE config IPU_BRIDGE
tristate "Intel IPU Bridge" tristate "Intel IPU Bridge"
depends on I2C && ACPI depends on ACPI || COMPILE_TEST
depends on I2C
help help
The IPU bridge is a helper library for Intel IPU drivers to The IPU bridge is a helper library for Intel IPU drivers to
function on systems shipped with Windows. function on systems shipped with Windows.

View File

@ -5,3 +5,4 @@
obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o
obj-y += ipu3/ obj-y += ipu3/
obj-y += ivsc/ obj-y += ivsc/
obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/

View File

@ -15,6 +15,8 @@
#include <media/ipu-bridge.h> #include <media/ipu-bridge.h>
#include <media/v4l2-fwnode.h> #include <media/v4l2-fwnode.h>
#define ADEV_DEV(adev) ACPI_PTR(&((adev)->dev))
/* /*
* 92335fcf-3203-4472-af93-7b4453ac29da * 92335fcf-3203-4472-af93-7b4453ac29da
* *
@ -87,6 +89,7 @@ static const char * const ipu_vcm_types[] = {
"lc898212axb", "lc898212axb",
}; };
#if IS_ENABLED(CONFIG_ACPI)
/* /*
* Used to figure out IVSC acpi device by ipu_bridge_get_ivsc_acpi_dev() * Used to figure out IVSC acpi device by ipu_bridge_get_ivsc_acpi_dev()
* instead of device and driver match to probe IVSC device. * instead of device and driver match to probe IVSC device.
@ -100,13 +103,13 @@ static const struct acpi_device_id ivsc_acpi_ids[] = {
static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev)
{ {
acpi_handle handle = acpi_device_handle(adev);
struct acpi_device *consumer, *ivsc_adev;
unsigned int i; unsigned int i;
for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) {
const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i];
struct acpi_device *consumer, *ivsc_adev;
acpi_handle handle = acpi_device_handle(adev);
for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1)
/* camera sensor depends on IVSC in DSDT if exist */ /* camera sensor depends on IVSC in DSDT if exist */
for_each_acpi_consumer_dev(ivsc_adev, consumer) for_each_acpi_consumer_dev(ivsc_adev, consumer)
@ -118,6 +121,12 @@ static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev
return NULL; return NULL;
} }
#else
static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev)
{
return NULL;
}
#endif
static int ipu_bridge_match_ivsc_dev(struct device *dev, const void *adev) static int ipu_bridge_match_ivsc_dev(struct device *dev, const void *adev)
{ {
@ -163,7 +172,7 @@ static int ipu_bridge_check_ivsc_dev(struct ipu_sensor *sensor,
csi_dev = ipu_bridge_get_ivsc_csi_dev(adev); csi_dev = ipu_bridge_get_ivsc_csi_dev(adev);
if (!csi_dev) { if (!csi_dev) {
acpi_dev_put(adev); acpi_dev_put(adev);
dev_err(&adev->dev, "Failed to find MEI CSI dev\n"); dev_err(ADEV_DEV(adev), "Failed to find MEI CSI dev\n");
return -ENODEV; return -ENODEV;
} }
@ -182,24 +191,25 @@ static int ipu_bridge_read_acpi_buffer(struct acpi_device *adev, char *id,
acpi_status status; acpi_status status;
int ret = 0; int ret = 0;
status = acpi_evaluate_object(adev->handle, id, NULL, &buffer); status = acpi_evaluate_object(ACPI_PTR(adev->handle),
id, NULL, &buffer);
if (ACPI_FAILURE(status)) if (ACPI_FAILURE(status))
return -ENODEV; return -ENODEV;
obj = buffer.pointer; obj = buffer.pointer;
if (!obj) { if (!obj) {
dev_err(&adev->dev, "Couldn't locate ACPI buffer\n"); dev_err(ADEV_DEV(adev), "Couldn't locate ACPI buffer\n");
return -ENODEV; return -ENODEV;
} }
if (obj->type != ACPI_TYPE_BUFFER) { if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(&adev->dev, "Not an ACPI buffer\n"); dev_err(ADEV_DEV(adev), "Not an ACPI buffer\n");
ret = -ENODEV; ret = -ENODEV;
goto out_free_buff; goto out_free_buff;
} }
if (obj->buffer.length > size) { if (obj->buffer.length > size) {
dev_err(&adev->dev, "Given buffer is too small\n"); dev_err(ADEV_DEV(adev), "Given buffer is too small\n");
ret = -EINVAL; ret = -EINVAL;
goto out_free_buff; goto out_free_buff;
} }
@ -220,7 +230,7 @@ static u32 ipu_bridge_parse_rotation(struct acpi_device *adev,
case IPU_SENSOR_ROTATION_INVERTED: case IPU_SENSOR_ROTATION_INVERTED:
return 180; return 180;
default: default:
dev_warn(&adev->dev, dev_warn(ADEV_DEV(adev),
"Unknown rotation %d. Assume 0 degree rotation\n", "Unknown rotation %d. Assume 0 degree rotation\n",
ssdb->degree); ssdb->degree);
return 0; return 0;
@ -230,12 +240,14 @@ static u32 ipu_bridge_parse_rotation(struct acpi_device *adev,
static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_device *adev) static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_device *adev)
{ {
enum v4l2_fwnode_orientation orientation; enum v4l2_fwnode_orientation orientation;
struct acpi_pld_info *pld; struct acpi_pld_info *pld = NULL;
acpi_status status; acpi_status status = AE_ERROR;
#if IS_ENABLED(CONFIG_ACPI)
status = acpi_get_physical_device_location(adev->handle, &pld); status = acpi_get_physical_device_location(adev->handle, &pld);
#endif
if (ACPI_FAILURE(status)) { if (ACPI_FAILURE(status)) {
dev_warn(&adev->dev, "_PLD call failed, using default orientation\n"); dev_warn(ADEV_DEV(adev), "_PLD call failed, using default orientation\n");
return V4L2_FWNODE_ORIENTATION_EXTERNAL; return V4L2_FWNODE_ORIENTATION_EXTERNAL;
} }
@ -253,7 +265,8 @@ static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_dev
orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL;
break; break;
default: default:
dev_warn(&adev->dev, "Unknown _PLD panel val %d\n", pld->panel); dev_warn(ADEV_DEV(adev), "Unknown _PLD panel val %d\n",
pld->panel);
orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL;
break; break;
} }
@ -272,12 +285,12 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor)
return ret; return ret;
if (ssdb.vcmtype > ARRAY_SIZE(ipu_vcm_types)) { if (ssdb.vcmtype > ARRAY_SIZE(ipu_vcm_types)) {
dev_warn(&adev->dev, "Unknown VCM type %d\n", ssdb.vcmtype); dev_warn(ADEV_DEV(adev), "Unknown VCM type %d\n", ssdb.vcmtype);
ssdb.vcmtype = 0; ssdb.vcmtype = 0;
} }
if (ssdb.lanes > IPU_MAX_LANES) { if (ssdb.lanes > IPU_MAX_LANES) {
dev_err(&adev->dev, "Number of lanes in SSDB is invalid\n"); dev_err(ADEV_DEV(adev), "Number of lanes in SSDB is invalid\n");
return -EINVAL; return -EINVAL;
} }
@ -465,8 +478,14 @@ static void ipu_bridge_create_connection_swnodes(struct ipu_bridge *bridge,
sensor->ipu_properties); sensor->ipu_properties);
if (sensor->csi_dev) { if (sensor->csi_dev) {
const char *device_hid = "";
#if IS_ENABLED(CONFIG_ACPI)
device_hid = acpi_device_hid(sensor->ivsc_adev);
#endif
snprintf(sensor->ivsc_name, sizeof(sensor->ivsc_name), "%s-%u", snprintf(sensor->ivsc_name, sizeof(sensor->ivsc_name), "%s-%u",
acpi_device_hid(sensor->ivsc_adev), sensor->link); device_hid, sensor->link);
nodes[SWNODE_IVSC_HID] = NODE_SENSOR(sensor->ivsc_name, nodes[SWNODE_IVSC_HID] = NODE_SENSOR(sensor->ivsc_name,
sensor->ivsc_properties); sensor->ivsc_properties);
@ -631,11 +650,15 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg,
{ {
struct fwnode_handle *fwnode, *primary; struct fwnode_handle *fwnode, *primary;
struct ipu_sensor *sensor; struct ipu_sensor *sensor;
struct acpi_device *adev; struct acpi_device *adev = NULL;
int ret; int ret;
#if IS_ENABLED(CONFIG_ACPI)
for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) {
if (!adev->status.enabled) #else
while (true) {
#endif
if (!ACPI_PTR(adev->status.enabled))
continue; continue;
if (bridge->n_sensors >= IPU_MAX_PORTS) { if (bridge->n_sensors >= IPU_MAX_PORTS) {
@ -671,7 +694,7 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg,
goto err_free_swnodes; goto err_free_swnodes;
} }
sensor->adev = acpi_dev_get(adev); sensor->adev = ACPI_PTR(acpi_dev_get(adev));
primary = acpi_fwnode_handle(adev); primary = acpi_fwnode_handle(adev);
primary->secondary = fwnode; primary->secondary = fwnode;
@ -727,11 +750,16 @@ static int ipu_bridge_ivsc_is_ready(void)
unsigned int i; unsigned int i;
for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) { for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) {
#if IS_ENABLED(CONFIG_ACPI)
const struct ipu_sensor_config *cfg = const struct ipu_sensor_config *cfg =
&ipu_supported_sensors[i]; &ipu_supported_sensors[i];
for_each_acpi_dev_match(sensor_adev, cfg->hid, NULL, -1) { for_each_acpi_dev_match(sensor_adev, cfg->hid, NULL, -1) {
if (!sensor_adev->status.enabled) #else
while (true) {
sensor_adev = NULL;
#endif
if (!ACPI_PTR(sensor_adev->status.enabled))
continue; continue;
adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev); adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev);

View File

@ -4,9 +4,9 @@
* *
* Based partially on Intel IPU4 driver written by * Based partially on Intel IPU4 driver written by
* Sakari Ailus <sakari.ailus@linux.intel.com> * Sakari Ailus <sakari.ailus@linux.intel.com>
* Samu Onkalo <samu.onkalo@intel.com> * Samu Onkalo
* Jouni Högander <jouni.hogander@intel.com> * Jouni Högander <jouni.hogander@intel.com>
* Jouni Ukkonen <jouni.ukkonen@intel.com> * Jouni Ukkonen
* Antti Laakso <antti.laakso@intel.com> * Antti Laakso <antti.laakso@intel.com>
* et al. * et al.
*/ */
@ -1752,11 +1752,6 @@ static int cio2_pci_probe(struct pci_dev *pci_dev,
v4l2_async_nf_init(&cio2->notifier, &cio2->v4l2_dev); v4l2_async_nf_init(&cio2->notifier, &cio2->v4l2_dev);
/* Register notifier for subdevices we care */
r = cio2_parse_firmware(cio2);
if (r)
goto fail_clean_notifier;
r = devm_request_irq(dev, pci_dev->irq, cio2_irq, IRQF_SHARED, r = devm_request_irq(dev, pci_dev->irq, cio2_irq, IRQF_SHARED,
CIO2_NAME, cio2); CIO2_NAME, cio2);
if (r) { if (r) {
@ -1764,6 +1759,11 @@ static int cio2_pci_probe(struct pci_dev *pci_dev,
goto fail_clean_notifier; goto fail_clean_notifier;
} }
/* Register notifier for subdevices we care */
r = cio2_parse_firmware(cio2);
if (r)
goto fail_clean_notifier;
pm_runtime_put_noidle(dev); pm_runtime_put_noidle(dev);
pm_runtime_allow(dev); pm_runtime_allow(dev);
@ -1807,16 +1807,10 @@ static int __maybe_unused cio2_runtime_suspend(struct device *dev)
struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_dev *pci_dev = to_pci_dev(dev);
struct cio2_device *cio2 = pci_get_drvdata(pci_dev); struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
void __iomem *const base = cio2->base; void __iomem *const base = cio2->base;
u16 pm;
writel(CIO2_D0I3C_I3, base + CIO2_REG_D0I3C); writel(CIO2_D0I3C_I3, base + CIO2_REG_D0I3C);
dev_dbg(dev, "cio2 runtime suspend.\n"); dev_dbg(dev, "cio2 runtime suspend.\n");
pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
pm |= CIO2_PMCSR_D3;
pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
return 0; return 0;
} }
@ -1825,15 +1819,10 @@ static int __maybe_unused cio2_runtime_resume(struct device *dev)
struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_dev *pci_dev = to_pci_dev(dev);
struct cio2_device *cio2 = pci_get_drvdata(pci_dev); struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
void __iomem *const base = cio2->base; void __iomem *const base = cio2->base;
u16 pm;
writel(CIO2_D0I3C_RR, base + CIO2_REG_D0I3C); writel(CIO2_D0I3C_RR, base + CIO2_REG_D0I3C);
dev_dbg(dev, "cio2 runtime resume.\n"); dev_dbg(dev, "cio2 runtime resume.\n");
pci_read_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, &pm);
pm = (pm >> CIO2_PMCSR_D0D3_SHIFT) << CIO2_PMCSR_D0D3_SHIFT;
pci_write_config_word(pci_dev, pci_dev->pm_cap + CIO2_PMCSR_OFFSET, pm);
return 0; return 0;
} }
@ -2006,10 +1995,10 @@ static struct pci_driver cio2_pci_driver = {
module_pci_driver(cio2_pci_driver); module_pci_driver(cio2_pci_driver);
MODULE_AUTHOR("Tuukka Toivonen <tuukka.toivonen@intel.com>"); MODULE_AUTHOR("Tuukka Toivonen");
MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
MODULE_AUTHOR("Jian Xu Zheng"); MODULE_AUTHOR("Jian Xu Zheng");
MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>"); MODULE_AUTHOR("Yuning Pu");
MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("IPU3 CIO2 driver"); MODULE_DESCRIPTION("IPU3 CIO2 driver");

View File

@ -320,10 +320,6 @@ struct pci_dev;
#define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4 #define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4
#define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570 #define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570
#define CIO2_PMCSR_OFFSET 4U
#define CIO2_PMCSR_D0D3_SHIFT 2U
#define CIO2_PMCSR_D3 0x3
struct cio2_csi2_timing { struct cio2_csi2_timing {
s32 clk_termen; s32 clk_termen;
s32 clk_settle; s32 clk_settle;

View File

@ -0,0 +1,18 @@
config VIDEO_INTEL_IPU6
tristate "Intel IPU6 driver"
depends on ACPI || COMPILE_TEST
depends on VIDEO_DEV
depends on X86 && X86_64 && HAS_DMA
select DMA_OPS
select IOMMU_IOVA
select VIDEO_V4L2_SUBDEV_API
select MEDIA_CONTROLLER
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.

View File

@ -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

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 - 2024 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);
}

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 - 2024 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

View File

@ -0,0 +1,917 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 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)
{
bool is_vmalloc = is_vmalloc_addr(fw->data);
struct page **pages;
const void *addr;
unsigned long n_pages;
unsigned int i;
int ret;
if (!is_vmalloc && !virt_addr_valid(fw->data))
return -EDOM;
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 = is_vmalloc ?
vmalloc_to_page(addr) : virt_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);
}

View File

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 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;
};
enum ipu6_buttress_ipc_domain {
IPU6_BUTTRESS_IPC_CSE,
IPU6_BUTTRESS_IPC_ISH,
};
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 */

View File

@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 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;
}

View File

@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2015--2024 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 */

View File

@ -0,0 +1,502 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 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,
};

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 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 */

View File

@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 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);

View File

@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013--2024 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

View File

@ -0,0 +1,487 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 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");
}

Some files were not shown because too many files have changed in this diff Show More